From e5fada9cd1c344c9499736dc4604f121a8746118 Mon Sep 17 00:00:00 2001 From: aaronp64 Date: Mon, 23 Sep 2024 10:37:32 -0400 Subject: [PATCH 001/151] Keep existing VectorCompose input values when setting vector type VisualShaderNodeVectorCompose::set_op_type would zero out input port default values for z and w when using vector 3D/4D, updated to keep values for all ports. Zeroing out z and w would cause values for 4D vectors to be lost when loading the visual shader - 3D vectors were not affected by this, since 3D is the default op type, which caused this step to be skipped during loading. However, both 3D and 4D were affected when changing the op type from the drop down in editor. For example, changing from 3D (1, 2, 3) to 4D would result in (1, 2, 0, 0), and changing from 4D (1, 2, 3, 4) to 3D would result in (1, 2, 0), losing previously set values. --- scene/resources/visual_shader_nodes.cpp | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/scene/resources/visual_shader_nodes.cpp b/scene/resources/visual_shader_nodes.cpp index 26666538af4..fa4e566a191 100644 --- a/scene/resources/visual_shader_nodes.cpp +++ b/scene/resources/visual_shader_nodes.cpp @@ -4878,19 +4878,22 @@ void VisualShaderNodeVectorCompose::set_op_type(OpType p_op_type) { case OP_TYPE_VECTOR_3D: { float p1 = get_input_port_default_value(0); float p2 = get_input_port_default_value(1); + float p3 = get_input_port_default_value(2); set_input_port_default_value(0, p1); set_input_port_default_value(1, p2); - set_input_port_default_value(2, 0.0); + set_input_port_default_value(2, p3); } break; case OP_TYPE_VECTOR_4D: { float p1 = get_input_port_default_value(0); float p2 = get_input_port_default_value(1); + float p3 = get_input_port_default_value(2); + float p4 = get_input_port_default_value(3); set_input_port_default_value(0, p1); set_input_port_default_value(1, p2); - set_input_port_default_value(2, 0.0); - set_input_port_default_value(3, 0.0); + set_input_port_default_value(2, p3); + set_input_port_default_value(3, p4); } break; default: break; From 87709cfdf2922dbd5f7ba45694205b8ca7e69fc8 Mon Sep 17 00:00:00 2001 From: Hugo Locurcio Date: Thu, 26 Sep 2024 00:08:06 +0200 Subject: [PATCH 002/151] Remove verbose prints from CameraServer on Linux due to being printed every second --- modules/camera/camera_linux.cpp | 3 --- 1 file changed, 3 deletions(-) diff --git a/modules/camera/camera_linux.cpp b/modules/camera/camera_linux.cpp index 0cfb6b7b9ed..28e86af3b54 100644 --- a/modules/camera/camera_linux.cpp +++ b/modules/camera/camera_linux.cpp @@ -134,17 +134,14 @@ bool CameraLinux::_is_video_capture_device(int p_file_descriptor) { struct v4l2_capability capability; if (ioctl(p_file_descriptor, VIDIOC_QUERYCAP, &capability) == -1) { - print_verbose("Cannot query device"); return false; } if (!(capability.capabilities & V4L2_CAP_VIDEO_CAPTURE)) { - print_verbose(vformat("%s is no video capture device\n", String((char *)capability.card))); return false; } if (!(capability.capabilities & V4L2_CAP_STREAMING)) { - print_verbose(vformat("%s does not support streaming", String((char *)capability.card))); return false; } From 41a1a7f94becabae9e2aab41790735393e69f6ef Mon Sep 17 00:00:00 2001 From: bruvzg <7645683+bruvzg@users.noreply.github.com> Date: Wed, 14 Aug 2024 10:42:59 +0300 Subject: [PATCH 003/151] Reshape and update button on oversampling change. --- scene/gui/button.cpp | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/scene/gui/button.cpp b/scene/gui/button.cpp index 0a5f2ec6c74..bfd5c075cfa 100644 --- a/scene/gui/button.cpp +++ b/scene/gui/button.cpp @@ -210,6 +210,13 @@ void Button::_notification(int p_what) { } break; case NOTIFICATION_DRAW: { + // Reshape and update size min. if text is invalidated by an external source (e.g., oversampling). + if (text_buf.is_valid() && !TS->shaped_text_is_ready(text_buf->get_rid())) { + _shape(); + + update_minimum_size(); + } + const RID ci = get_canvas_item(); const Size2 size = get_size(); From cd269b825f35fca5e2e1bba6bfcb44742b57343e Mon Sep 17 00:00:00 2001 From: Alex Darby Date: Tue, 5 Dec 2023 14:11:09 +0000 Subject: [PATCH 004/151] Make File Dialog case insensitive Change FileDialog and EditorFileDialog to use case insensitive string comparisons when saving files to avoid duplicate file extensions. --- editor/gui/editor_file_dialog.cpp | 6 +++--- scene/gui/file_dialog.cpp | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/editor/gui/editor_file_dialog.cpp b/editor/gui/editor_file_dialog.cpp index 77d0ba7a60a..f603e26bbc9 100644 --- a/editor/gui/editor_file_dialog.cpp +++ b/editor/gui/editor_file_dialog.cpp @@ -127,7 +127,7 @@ void EditorFileDialog::_native_dialog_cb(bool p_ok, const Vector &p_file int filter_slice_count = flt.get_slice_count(","); for (int j = 0; j < filter_slice_count; j++) { String str = (flt.get_slice(",", j).strip_edges()); - if (f.match(str)) { + if (f.matchn(str)) { valid = true; break; } @@ -565,7 +565,7 @@ void EditorFileDialog::_action_pressed() { String flt = filters[i].get_slice(";", 0); for (int j = 0; j < flt.get_slice_count(","); j++) { String str = flt.get_slice(",", j).strip_edges(); - if (f.match(str)) { + if (f.matchn(str)) { valid = true; break; } @@ -584,7 +584,7 @@ void EditorFileDialog::_action_pressed() { int filterSliceCount = flt.get_slice_count(","); for (int j = 0; j < filterSliceCount; j++) { String str = (flt.get_slice(",", j).strip_edges()); - if (f.match(str)) { + if (f.matchn(str)) { valid = true; break; } diff --git a/scene/gui/file_dialog.cpp b/scene/gui/file_dialog.cpp index d8e9d1bcc00..b35b1c5dca5 100644 --- a/scene/gui/file_dialog.cpp +++ b/scene/gui/file_dialog.cpp @@ -138,7 +138,7 @@ void FileDialog::_native_dialog_cb(bool p_ok, const Vector &p_files, int int filter_slice_count = flt.get_slice_count(","); for (int j = 0; j < filter_slice_count; j++) { String str = (flt.get_slice(",", j).strip_edges()); - if (f.match(str)) { + if (f.matchn(str)) { valid = true; break; } @@ -473,7 +473,7 @@ void FileDialog::_action_pressed() { String flt = filters[i].get_slice(";", 0); for (int j = 0; j < flt.get_slice_count(","); j++) { String str = flt.get_slice(",", j).strip_edges(); - if (f.match(str)) { + if (f.matchn(str)) { valid = true; break; } @@ -492,7 +492,7 @@ void FileDialog::_action_pressed() { int filterSliceCount = flt.get_slice_count(","); for (int j = 0; j < filterSliceCount; j++) { String str = (flt.get_slice(",", j).strip_edges()); - if (f.match(str)) { + if (f.matchn(str)) { valid = true; break; } From 7ed90a4f075f2a8c79b967684dd7ba328154ddaa Mon Sep 17 00:00:00 2001 From: bruvzg <7645683+bruvzg@users.noreply.github.com> Date: Tue, 8 Oct 2024 09:58:54 +0300 Subject: [PATCH 005/151] [RTL] Add support for vertical alignment. --- doc/classes/RichTextLabel.xml | 3 + scene/gui/rich_text_label.cpp | 117 ++++++++++++++++++++++++++++++---- scene/gui/rich_text_label.h | 8 ++- 3 files changed, 112 insertions(+), 16 deletions(-) diff --git a/doc/classes/RichTextLabel.xml b/doc/classes/RichTextLabel.xml index 4a2cbbc3d8e..1dc9ce5aa0d 100644 --- a/doc/classes/RichTextLabel.xml +++ b/doc/classes/RichTextLabel.xml @@ -681,6 +681,9 @@ If [code]true[/code], text processing is done in a background thread. + + Controls the text's vertical alignment. Supports top, center, bottom, and fill. Set it to one of the [enum VerticalAlignment] constants. + The number of characters to display. If set to [code]-1[/code], all characters are displayed. This can be useful when animating the text appearing in a dialog box. [b]Note:[/b] Setting this property updates [member visible_ratio] accordingly. diff --git a/scene/gui/rich_text_label.cpp b/scene/gui/rich_text_label.cpp index 26141663c1c..9566e53044c 100644 --- a/scene/gui/rich_text_label.cpp +++ b/scene/gui/rich_text_label.cpp @@ -781,7 +781,7 @@ void RichTextLabel::_set_table_size(ItemTable *p_table, int p_available_width) { } } -int RichTextLabel::_draw_line(ItemFrame *p_frame, int p_line, const Vector2 &p_ofs, int p_width, const Color &p_base_color, int p_outline_size, const Color &p_outline_color, const Color &p_font_shadow_color, int p_shadow_outline_size, const Point2 &p_shadow_ofs, int &r_processed_glyphs) { +int RichTextLabel::_draw_line(ItemFrame *p_frame, int p_line, const Vector2 &p_ofs, int p_width, float p_vsep, const Color &p_base_color, int p_outline_size, const Color &p_outline_color, const Color &p_font_shadow_color, int p_shadow_outline_size, const Point2 &p_shadow_ofs, int &r_processed_glyphs) { ERR_FAIL_NULL_V(p_frame, 0); ERR_FAIL_COND_V(p_line < 0 || p_line >= (int)p_frame->lines.size(), 0); @@ -825,7 +825,7 @@ int RichTextLabel::_draw_line(ItemFrame *p_frame, int p_line, const Vector2 &p_o // Draw text. for (int line = 0; line < l.text_buf->get_line_count(); line++) { if (line > 0) { - off.y += theme_cache.line_separation; + off.y += (theme_cache.line_separation + p_vsep); } if (p_ofs.y + off.y >= ctrl_size.height - v_limit) { @@ -998,7 +998,7 @@ int RichTextLabel::_draw_line(ItemFrame *p_frame, int p_line, const Vector2 &p_o } for (int j = 0; j < (int)frame->lines.size(); j++) { - _draw_line(frame, j, p_ofs + rect.position + off + Vector2(0, frame->lines[j].offset.y), rect.size.x, p_base_color, p_outline_size, p_outline_color, p_font_shadow_color, p_shadow_outline_size, p_shadow_ofs, r_processed_glyphs); + _draw_line(frame, j, p_ofs + rect.position + off + Vector2(0, frame->lines[j].offset.y), rect.size.x, 0, p_base_color, p_outline_size, p_outline_color, p_font_shadow_color, p_shadow_outline_size, p_shadow_ofs, r_processed_glyphs); } idx++; } @@ -1433,11 +1433,46 @@ void RichTextLabel::_find_click(ItemFrame *p_frame, const Point2i &p_click, Item int to_line = main->first_invalid_line.load(); int from_line = _find_first_line(0, to_line, vofs); - Point2 ofs = text_rect.get_position() + Vector2(0, main->lines[from_line].offset.y - vofs); + int total_height = INT32_MAX; + if (to_line && vertical_alignment != VERTICAL_ALIGNMENT_TOP) { + MutexLock lock(main->lines[to_line - 1].text_buf->get_mutex()); + if (theme_cache.line_separation < 0) { + // Do not apply to the last line to avoid cutting text. + total_height = main->lines[to_line - 1].offset.y + main->lines[to_line - 1].text_buf->get_size().y + (main->lines[to_line - 1].text_buf->get_line_count() - 1) * theme_cache.line_separation; + } else { + total_height = main->lines[to_line - 1].offset.y + main->lines[to_line - 1].text_buf->get_size().y + main->lines[to_line - 1].text_buf->get_line_count() * theme_cache.line_separation; + } + } + float vbegin = 0, vsep = 0; + if (text_rect.size.y > total_height) { + switch (vertical_alignment) { + case VERTICAL_ALIGNMENT_TOP: { + // Nothing. + } break; + case VERTICAL_ALIGNMENT_CENTER: { + vbegin = (text_rect.size.y - total_height) / 2; + } break; + case VERTICAL_ALIGNMENT_BOTTOM: { + vbegin = text_rect.size.y - total_height; + } break; + case VERTICAL_ALIGNMENT_FILL: { + int lines = 0; + for (int l = from_line; l < to_line; l++) { + MutexLock lock(main->lines[l].text_buf->get_mutex()); + lines += main->lines[l].text_buf->get_line_count(); + } + if (lines > 1) { + vsep = (text_rect.size.y - total_height) / (lines - 1); + } + } break; + } + } + + Point2 ofs = text_rect.get_position() + Vector2(0, vbegin + main->lines[from_line].offset.y - vofs); while (ofs.y < size.height && from_line < to_line) { MutexLock lock(main->lines[from_line].text_buf->get_mutex()); - _find_click_in_line(p_frame, from_line, ofs, text_rect.size.x, p_click, r_click_frame, r_click_line, r_click_item, r_click_char, false, p_meta); - ofs.y += main->lines[from_line].text_buf->get_size().y + main->lines[from_line].text_buf->get_line_count() * theme_cache.line_separation; + _find_click_in_line(p_frame, from_line, ofs, text_rect.size.x, vsep, p_click, r_click_frame, r_click_line, r_click_item, r_click_char, false, p_meta); + ofs.y += main->lines[from_line].text_buf->get_size().y + main->lines[from_line].text_buf->get_line_count() * (theme_cache.line_separation + vsep); if (((r_click_item != nullptr) && ((*r_click_item) != nullptr)) || ((r_click_frame != nullptr) && ((*r_click_frame) != nullptr))) { if (r_outside != nullptr) { *r_outside = false; @@ -1448,7 +1483,7 @@ void RichTextLabel::_find_click(ItemFrame *p_frame, const Point2i &p_click, Item } } -float RichTextLabel::_find_click_in_line(ItemFrame *p_frame, int p_line, const Vector2 &p_ofs, int p_width, const Point2i &p_click, ItemFrame **r_click_frame, int *r_click_line, Item **r_click_item, int *r_click_char, bool p_table, bool p_meta) { +float RichTextLabel::_find_click_in_line(ItemFrame *p_frame, int p_line, const Vector2 &p_ofs, int p_width, float p_vsep, const Point2i &p_click, ItemFrame **r_click_frame, int *r_click_line, Item **r_click_item, int *r_click_char, bool p_table, bool p_meta) { Vector2 off; bool line_clicked = false; @@ -1553,7 +1588,7 @@ float RichTextLabel::_find_click_in_line(ItemFrame *p_frame, int p_line, const V } if (crect.has_point(p_click)) { for (int j = 0; j < (int)frame->lines.size(); j++) { - _find_click_in_line(frame, j, rect.position + Vector2(frame->padding.position.x, frame->lines[j].offset.y), rect.size.x, p_click, &table_click_frame, &table_click_line, &table_click_item, &table_click_char, true, p_meta); + _find_click_in_line(frame, j, rect.position + Vector2(frame->padding.position.x, frame->lines[j].offset.y), rect.size.x, 0, p_click, &table_click_frame, &table_click_line, &table_click_item, &table_click_char, true, p_meta); if (table_click_frame && table_click_item) { // Save cell detected cell hit data. table_range = Vector2i(INT32_MAX, 0); @@ -1654,7 +1689,7 @@ float RichTextLabel::_find_click_in_line(ItemFrame *p_frame, int p_line, const V return table_offy; } - off.y += TS->shaped_text_get_descent(rid) + theme_cache.line_separation; + off.y += TS->shaped_text_get_descent(rid) + theme_cache.line_separation + p_vsep; } // Text line hit. @@ -1891,22 +1926,58 @@ void RichTextLabel::_notification(int p_what) { int to_line = main->first_invalid_line.load(); int from_line = _find_first_line(0, to_line, vofs); + // Bottom margin for text clipping. + float v_limit = theme_cache.normal_style->get_margin(SIDE_BOTTOM); + + int total_height = INT32_MAX; + if (to_line && vertical_alignment != VERTICAL_ALIGNMENT_TOP) { + MutexLock lock(main->lines[to_line - 1].text_buf->get_mutex()); + if (theme_cache.line_separation < 0) { + // Do not apply to the last line to avoid cutting text. + total_height = main->lines[to_line - 1].offset.y + main->lines[to_line - 1].text_buf->get_size().y + (main->lines[to_line - 1].text_buf->get_line_count() - 1) * theme_cache.line_separation; + } else { + total_height = main->lines[to_line - 1].offset.y + main->lines[to_line - 1].text_buf->get_size().y + main->lines[to_line - 1].text_buf->get_line_count() * theme_cache.line_separation; + } + } + float vbegin = 0, vsep = 0; + if (text_rect.size.y > total_height) { + switch (vertical_alignment) { + case VERTICAL_ALIGNMENT_TOP: { + // Nothing. + } break; + case VERTICAL_ALIGNMENT_CENTER: { + vbegin = (text_rect.size.y - total_height) / 2; + } break; + case VERTICAL_ALIGNMENT_BOTTOM: { + vbegin = text_rect.size.y - total_height; + } break; + case VERTICAL_ALIGNMENT_FILL: { + int lines = 0; + for (int l = from_line; l < to_line; l++) { + MutexLock lock(main->lines[l].text_buf->get_mutex()); + lines += main->lines[l].text_buf->get_line_count(); + } + if (lines > 1) { + vsep = (text_rect.size.y - total_height) / (lines - 1); + } + } break; + } + } + Point2 shadow_ofs(theme_cache.shadow_offset_x, theme_cache.shadow_offset_y); visible_paragraph_count = 0; visible_line_count = 0; - // Bottom margin for text clipping. - float v_limit = theme_cache.normal_style->get_margin(SIDE_BOTTOM); // New cache draw. - Point2 ofs = text_rect.get_position() + Vector2(0, main->lines[from_line].offset.y - vofs); + Point2 ofs = text_rect.get_position() + Vector2(0, vbegin + main->lines[from_line].offset.y - vofs); int processed_glyphs = 0; while (ofs.y < size.height - v_limit && from_line < to_line) { MutexLock lock(main->lines[from_line].text_buf->get_mutex()); visible_paragraph_count++; - visible_line_count += _draw_line(main, from_line, ofs, text_rect.size.x, theme_cache.default_color, theme_cache.outline_size, theme_cache.font_outline_color, theme_cache.font_shadow_color, theme_cache.shadow_outline_size, shadow_ofs, processed_glyphs); - ofs.y += main->lines[from_line].text_buf->get_size().y + main->lines[from_line].text_buf->get_line_count() * theme_cache.line_separation; + visible_line_count += _draw_line(main, from_line, ofs, text_rect.size.x, vsep, theme_cache.default_color, theme_cache.outline_size, theme_cache.font_outline_color, theme_cache.font_shadow_color, theme_cache.shadow_outline_size, shadow_ofs, processed_glyphs); + ofs.y += main->lines[from_line].text_buf->get_size().y + main->lines[from_line].text_buf->get_line_count() * (theme_cache.line_separation + vsep); from_line++; } } break; @@ -5915,6 +5986,21 @@ HorizontalAlignment RichTextLabel::get_horizontal_alignment() const { return default_alignment; } +void RichTextLabel::set_vertical_alignment(VerticalAlignment p_alignment) { + ERR_FAIL_INDEX((int)p_alignment, 4); + + if (vertical_alignment == p_alignment) { + return; + } + + vertical_alignment = p_alignment; + queue_redraw(); +} + +VerticalAlignment RichTextLabel::get_vertical_alignment() const { + return vertical_alignment; +} + void RichTextLabel::set_justification_flags(BitField p_flags) { _stop_thread(); @@ -6167,6 +6253,8 @@ void RichTextLabel::_bind_methods() { ClassDB::bind_method(D_METHOD("set_horizontal_alignment", "alignment"), &RichTextLabel::set_horizontal_alignment); ClassDB::bind_method(D_METHOD("get_horizontal_alignment"), &RichTextLabel::get_horizontal_alignment); + ClassDB::bind_method(D_METHOD("set_vertical_alignment", "alignment"), &RichTextLabel::set_vertical_alignment); + ClassDB::bind_method(D_METHOD("get_vertical_alignment"), &RichTextLabel::get_vertical_alignment); ClassDB::bind_method(D_METHOD("set_justification_flags", "justification_flags"), &RichTextLabel::set_justification_flags); ClassDB::bind_method(D_METHOD("get_justification_flags"), &RichTextLabel::get_justification_flags); ClassDB::bind_method(D_METHOD("set_tab_stops", "tab_stops"), &RichTextLabel::set_tab_stops); @@ -6293,6 +6381,7 @@ void RichTextLabel::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::BOOL, "shortcut_keys_enabled"), "set_shortcut_keys_enabled", "is_shortcut_keys_enabled"); ADD_PROPERTY(PropertyInfo(Variant::INT, "horizontal_alignment", PROPERTY_HINT_ENUM, "Left,Center,Right,Fill"), "set_horizontal_alignment", "get_horizontal_alignment"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "vertical_alignment", PROPERTY_HINT_ENUM, "Top,Center,Bottom,Fill"), "set_vertical_alignment", "get_vertical_alignment"); ADD_PROPERTY(PropertyInfo(Variant::INT, "justification_flags", PROPERTY_HINT_FLAGS, "Kashida Justification:1,Word Justification:2,Justify Only After Last Tab:8,Skip Last Line:32,Skip Last Line With Visible Characters:64,Do Not Skip Single Line:128"), "set_justification_flags", "get_justification_flags"); ADD_PROPERTY(PropertyInfo(Variant::PACKED_FLOAT32_ARRAY, "tab_stops"), "set_tab_stops", "get_tab_stops"); diff --git a/scene/gui/rich_text_label.h b/scene/gui/rich_text_label.h index a01da02b273..5dab0d885a6 100644 --- a/scene/gui/rich_text_label.h +++ b/scene/gui/rich_text_label.h @@ -481,6 +481,7 @@ class RichTextLabel : public Control { bool use_selected_font_color = false; HorizontalAlignment default_alignment = HORIZONTAL_ALIGNMENT_LEFT; + VerticalAlignment vertical_alignment = VERTICAL_ALIGNMENT_TOP; BitField default_jst_flags = TextServer::JUSTIFICATION_WORD_BOUND | TextServer::JUSTIFICATION_KASHIDA | TextServer::JUSTIFICATION_SKIP_LAST_LINE | TextServer::JUSTIFICATION_DO_NOT_SKIP_SINGLE_LINE; PackedFloat32Array default_tab_stops; @@ -561,8 +562,8 @@ class RichTextLabel : public Control { void _set_table_size(ItemTable *p_table, int p_available_width); void _update_line_font(ItemFrame *p_frame, int p_line, const Ref &p_base_font, int p_base_font_size); - int _draw_line(ItemFrame *p_frame, int p_line, const Vector2 &p_ofs, int p_width, const Color &p_base_color, int p_outline_size, const Color &p_outline_color, const Color &p_font_shadow_color, int p_shadow_outline_size, const Point2 &p_shadow_ofs, int &r_processed_glyphs); - float _find_click_in_line(ItemFrame *p_frame, int p_line, const Vector2 &p_ofs, int p_width, const Point2i &p_click, ItemFrame **r_click_frame = nullptr, int *r_click_line = nullptr, Item **r_click_item = nullptr, int *r_click_char = nullptr, bool p_table = false, bool p_meta = false); + int _draw_line(ItemFrame *p_frame, int p_line, const Vector2 &p_ofs, int p_width, float p_vsep, const Color &p_base_color, int p_outline_size, const Color &p_outline_color, const Color &p_font_shadow_color, int p_shadow_outline_size, const Point2 &p_shadow_ofs, int &r_processed_glyphs); + float _find_click_in_line(ItemFrame *p_frame, int p_line, const Vector2 &p_ofs, int p_width, float p_vsep, const Point2i &p_click, ItemFrame **r_click_frame = nullptr, int *r_click_line = nullptr, Item **r_click_item = nullptr, int *r_click_char = nullptr, bool p_table = false, bool p_meta = false); String _roman(int p_num, bool p_capitalize) const; String _letters(int p_num, bool p_capitalize) const; @@ -816,6 +817,9 @@ class RichTextLabel : public Control { void set_horizontal_alignment(HorizontalAlignment p_alignment); HorizontalAlignment get_horizontal_alignment() const; + void set_vertical_alignment(VerticalAlignment p_alignment); + VerticalAlignment get_vertical_alignment() const; + void set_justification_flags(BitField p_flags); BitField get_justification_flags() const; From af926854455bba29658271dee7b8d0ce7556bfaa Mon Sep 17 00:00:00 2001 From: Marius Hanl Date: Mon, 2 Sep 2024 00:28:44 +0200 Subject: [PATCH 006/151] Fix jumping to editor help does not scroll correctly sometimes --- editor/editor_help.cpp | 6 +----- editor/plugins/script_editor_plugin.cpp | 3 +-- 2 files changed, 2 insertions(+), 7 deletions(-) diff --git a/editor/editor_help.cpp b/editor/editor_help.cpp index cfe257fcfc8..baefc9b2f8b 100644 --- a/editor/editor_help.cpp +++ b/editor/editor_help.cpp @@ -2378,11 +2378,7 @@ void EditorHelp::_help_callback(const String &p_topic) { } if (class_desc->is_finished()) { - // call_deferred() is not enough. - if (class_desc->is_connected(SceneStringName(draw), callable_mp(class_desc, &RichTextLabel::scroll_to_paragraph))) { - class_desc->disconnect(SceneStringName(draw), callable_mp(class_desc, &RichTextLabel::scroll_to_paragraph)); - } - class_desc->connect(SceneStringName(draw), callable_mp(class_desc, &RichTextLabel::scroll_to_paragraph).bind(line), CONNECT_ONE_SHOT | CONNECT_DEFERRED); + class_desc->scroll_to_paragraph(line); } else { scroll_to = line; } diff --git a/editor/plugins/script_editor_plugin.cpp b/editor/plugins/script_editor_plugin.cpp index 7e0331d15c1..ad71e6d3808 100644 --- a/editor/plugins/script_editor_plugin.cpp +++ b/editor/plugins/script_editor_plugin.cpp @@ -3607,14 +3607,13 @@ void ScriptEditor::_help_class_goto(const String &p_desc) { eh->set_name(cname); tab_container->add_child(eh); + _go_to_tab(tab_container->get_tab_count() - 1); eh->go_to_help(p_desc); eh->connect("go_to_help", callable_mp(this, &ScriptEditor::_help_class_goto)); _add_recent_script(eh->get_class()); _sort_list_on_update = true; _update_script_names(); _save_layout(); - - callable_mp(this, &ScriptEditor::_help_tab_goto).call_deferred(cname, p_desc); } bool ScriptEditor::_help_tab_goto(const String &p_name, const String &p_desc) { From 503574441e1d9d40da0dc13a50ed9fea5790e218 Mon Sep 17 00:00:00 2001 From: bruvzg <7645683+bruvzg@users.noreply.github.com> Date: Wed, 23 Oct 2024 10:15:20 +0300 Subject: [PATCH 007/151] [RTL] Allow setting image alignment as separate bbcode argument. --- scene/gui/rich_text_label.cpp | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/scene/gui/rich_text_label.cpp b/scene/gui/rich_text_label.cpp index 26141663c1c..a406ea3d895 100644 --- a/scene/gui/rich_text_label.cpp +++ b/scene/gui/rich_text_label.cpp @@ -4853,6 +4853,38 @@ void RichTextLabel::append_text(const String &p_bbcode) { height = bbcode_value.substr(sep + 1).to_int(); } } else { + OptionMap::Iterator align_option = bbcode_options.find("align"); + if (align_option) { + Vector subtag = _split_unquoted(align_option->value, U','); + _normalize_subtags(subtag); + + if (subtag.size() > 1) { + if (subtag[0] == "top" || subtag[0] == "t") { + alignment = INLINE_ALIGNMENT_TOP_TO; + } else if (subtag[0] == "center" || subtag[0] == "c") { + alignment = INLINE_ALIGNMENT_CENTER_TO; + } else if (subtag[0] == "bottom" || subtag[0] == "b") { + alignment = INLINE_ALIGNMENT_BOTTOM_TO; + } + if (subtag[1] == "top" || subtag[1] == "t") { + alignment |= INLINE_ALIGNMENT_TO_TOP; + } else if (subtag[1] == "center" || subtag[1] == "c") { + alignment |= INLINE_ALIGNMENT_TO_CENTER; + } else if (subtag[1] == "baseline" || subtag[1] == "l") { + alignment |= INLINE_ALIGNMENT_TO_BASELINE; + } else if (subtag[1] == "bottom" || subtag[1] == "b") { + alignment |= INLINE_ALIGNMENT_TO_BOTTOM; + } + } else if (subtag.size() > 0) { + if (subtag[0] == "top" || subtag[0] == "t") { + alignment = INLINE_ALIGNMENT_TOP; + } else if (subtag[0] == "center" || subtag[0] == "c") { + alignment = INLINE_ALIGNMENT_CENTER; + } else if (subtag[0] == "bottom" || subtag[0] == "b") { + alignment = INLINE_ALIGNMENT_BOTTOM; + } + } + } OptionMap::Iterator width_option = bbcode_options.find("width"); if (width_option) { width = width_option->value.to_int(); From cb94652c3413f8a0da16d1db6b920958d0e0fe5a Mon Sep 17 00:00:00 2001 From: yds Date: Sat, 26 Oct 2024 02:45:54 -0300 Subject: [PATCH 008/151] Fix `MultiMesh` errors in editor and resource duplication --- scene/resources/multimesh.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/scene/resources/multimesh.cpp b/scene/resources/multimesh.cpp index bf3caa1edd5..2eb74ef15af 100644 --- a/scene/resources/multimesh.cpp +++ b/scene/resources/multimesh.cpp @@ -195,6 +195,9 @@ Vector MultiMesh::_get_custom_data_array() const { #endif // DISABLE_DEPRECATED void MultiMesh::set_buffer(const Vector &p_buffer) { + if (instance_count == 0) { + return; + } RS::get_singleton()->multimesh_set_buffer(multimesh, p_buffer); } From 335b42d4372653412e63a16bc220372a03be7c4f Mon Sep 17 00:00:00 2001 From: bruvzg <7645683+bruvzg@users.noreply.github.com> Date: Sun, 27 Oct 2024 16:54:03 +0200 Subject: [PATCH 009/151] Remove button number limit from Windows dialog_show() implementation. --- platform/windows/display_server_windows.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/platform/windows/display_server_windows.cpp b/platform/windows/display_server_windows.cpp index fc49b63ddee..0273c9899c8 100644 --- a/platform/windows/display_server_windows.cpp +++ b/platform/windows/display_server_windows.cpp @@ -2743,7 +2743,7 @@ Error DisplayServerWindows::dialog_show(String p_title, String p_description, Ve config.pszWindowTitle = (LPCWSTR)(title.get_data()); config.pszContent = (LPCWSTR)(message.get_data()); - const int button_count = MIN((int)buttons.size(), 8); + const int button_count = buttons.size(); config.cButtons = button_count; // No dynamic stack array size :( From db1c1d43e3c21a6cd981215380818d34c9fc9b55 Mon Sep 17 00:00:00 2001 From: bruvzg <7645683+bruvzg@users.noreply.github.com> Date: Mon, 28 Oct 2024 11:34:42 +0200 Subject: [PATCH 010/151] [Misc] Check for the available and installed Vulkan SDK versions before downloading and installing. --- misc/scripts/install_vulkan_sdk_macos.sh | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/misc/scripts/install_vulkan_sdk_macos.sh b/misc/scripts/install_vulkan_sdk_macos.sh index 6e9fa69eeac..2410a02c4ff 100755 --- a/misc/scripts/install_vulkan_sdk_macos.sh +++ b/misc/scripts/install_vulkan_sdk_macos.sh @@ -3,6 +3,28 @@ set -euo pipefail IFS=$'\n\t' +# Check currently installed and latest available Vulkan SDK versions. +if [ -d "$HOME/VulkanSDK" ]; then + if command -v jq 2>&1 >/dev/null; then + curl -L "https://sdk.lunarg.com/sdk/download/latest/mac/config.json" -o /tmp/vulkan-sdk.json + + new_ver=`jq -r '.version' /tmp/vulkan-sdk.json` + new_ver=`echo "$new_ver" | awk -F. '{ printf("%d%02d%04d%02d\n", $1,$2,$3,$4); }';` + + rm -f /tmp/vulkan-sdk.json + + for f in $HOME/VulkanSDK/*; do + if [ -d "$f" ]; then + f=`echo "${f##*/}" | awk -F. '{ printf("%d%02d%04d%02d\n", $1,$2,$3,$4); }';` + if [ $f -ge $new_ver ]; then + echo 'Latest or newer Vulkan SDK is already installed. Skipping installation.' + exit 0 + fi + fi + done + fi +fi + # Download and install the Vulkan SDK. curl -L "https://sdk.lunarg.com/sdk/download/latest/mac/vulkan-sdk.zip" -o /tmp/vulkan-sdk.zip unzip /tmp/vulkan-sdk.zip -d /tmp From 4036270f8d2e19f8e2c71cd05800d6cd0af5e2ed Mon Sep 17 00:00:00 2001 From: jpetersen Date: Mon, 23 Sep 2024 10:01:49 -0700 Subject: [PATCH 011/151] Support for XCode 8+ feature PROVISIONING_PROFILE_SPECIFIER MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit https://developer.apple.com/documentation/xcode/build-settings-reference\#Provisioning-Profile Used to allow for specific provisioning profile to be specified by name instead of UUID. Needed to solve this problem where uuid wasn’t disambiguating: https://stackoverflow.com/questions/45051712/signing-app-with-xcodebuild-command-line-with-provisioning-profile-fails Allows for specification for release and debug versions through environment variables or through export template attributes. Debug: EnvironmentVariable: GODOT_IOS_PROFILE_SPECIFIER_DEBUG Export template: “application/provisioning_profile_specifier_debug” Release: EnvironmentalVariable: GODOT_IOS_PROFILE_SPECIFIER_RELEASE Export Template: “application/provisioning_profile_specifier_release” --- .gitignore | 1 + .../godot_ios.xcodeproj/project.pbxproj | 2 ++ .../doc_classes/EditorExportPlatformIOS.xml | 8 +++++ platform/ios/export/export_plugin.cpp | 34 ++++++++++++++++--- platform/ios/export/export_plugin.h | 2 ++ 5 files changed, 43 insertions(+), 4 deletions(-) diff --git a/.gitignore b/.gitignore index 32a43b8c637..976fd0a437c 100644 --- a/.gitignore +++ b/.gitignore @@ -216,6 +216,7 @@ xcuserdata/ *.xcscmblueprint *.xccheckout *.xcodeproj/* +!misc/misc/dist/ios_xcode/godot_ios.xcodeproj/project.pbxproj ############################## ### Visual Studio specific ### diff --git a/misc/dist/ios_xcode/godot_ios.xcodeproj/project.pbxproj b/misc/dist/ios_xcode/godot_ios.xcodeproj/project.pbxproj index a5de7e88727..1975bde1852 100644 --- a/misc/dist/ios_xcode/godot_ios.xcodeproj/project.pbxproj +++ b/misc/dist/ios_xcode/godot_ios.xcodeproj/project.pbxproj @@ -339,6 +339,7 @@ MARKETING_VERSION = $short_version; CURRENT_PROJECT_VERSION = $version; PROVISIONING_PROFILE = "$provisioning_profile_uuid_debug"; + PROVISIONING_PROFILE_SPECIFIER = "$provisioning_profile_specifier_debug"; TARGETED_DEVICE_FAMILY = "$targeted_device_family"; VALID_ARCHS = "arm64 x86_64"; WRAPPER_EXTENSION = app; @@ -374,6 +375,7 @@ MARKETING_VERSION = $short_version; CURRENT_PROJECT_VERSION = $version; PROVISIONING_PROFILE = "$provisioning_profile_uuid_release"; + PROVISIONING_PROFILE_SPECIFIER = "$provisioning_profile_specifier_release"; TARGETED_DEVICE_FAMILY = "$targeted_device_family"; VALID_ARCHS = "arm64 x86_64"; WRAPPER_EXTENSION = app; diff --git a/platform/ios/doc_classes/EditorExportPlatformIOS.xml b/platform/ios/doc_classes/EditorExportPlatformIOS.xml index 9e6f191faad..6d2c74d222a 100644 --- a/platform/ios/doc_classes/EditorExportPlatformIOS.xml +++ b/platform/ios/doc_classes/EditorExportPlatformIOS.xml @@ -50,6 +50,14 @@ Minimum version of iOS required for this application to run in the [code]major.minor.patch[/code] or [code]major.minor[/code] format, can only contain numeric characters ([code]0-9[/code]) and periods ([code].[/code]). + + Name of the provisioning profile. Sets XCode PROVISIONING_PROFILE_SPECIFIER for debug. [url=https://developer.apple.com/documentation/xcode/build-settings-reference#Provisioning-Profile]Used for manual provisioning[/url]. + Can be overridden with the environment variable [code]GODOT_IOS_PROFILE_SPECIFIER_DEBUG[/code]. + + + Name of the provisioning profile. Sets XCode PROVISIONING_PROFILE_SPECIFIER for release. [url=https://developer.apple.com/documentation/xcode/build-settings-reference#Provisioning-Profile]Used for manual provisioning[/url]. + Can be overridden with the environment variable [code]GODOT_IOS_PROFILE_SPECIFIER_RELEASE[/code]. + UUID of the provisioning profile. If left empty, Xcode will download or create a provisioning profile automatically. See [url=https://developer.apple.com/help/account/manage-profiles/edit-download-or-delete-profiles]Edit, download, or delete provisioning profiles[/url]. Can be overridden with the environment variable [code]GODOT_IOS_PROVISIONING_PROFILE_UUID_DEBUG[/code]. diff --git a/platform/ios/export/export_plugin.cpp b/platform/ios/export/export_plugin.cpp index d6cd2e0f3c3..11cbc36c934 100644 --- a/platform/ios/export/export_plugin.cpp +++ b/platform/ios/export/export_plugin.cpp @@ -272,11 +272,13 @@ void EditorExportPlatformIOS::get_export_options(List *r_options) r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/app_store_team_id"), "", false, true)); - r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/provisioning_profile_uuid_debug", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_SECRET), "")); - r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/code_sign_identity_debug", PROPERTY_HINT_PLACEHOLDER_TEXT, "iPhone Developer"), "")); r_options->push_back(ExportOption(PropertyInfo(Variant::INT, "application/export_method_debug", PROPERTY_HINT_ENUM, "App Store,Development,Ad-Hoc,Enterprise"), 1)); - r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/provisioning_profile_uuid_release", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_SECRET), "")); + r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/code_sign_identity_debug", PROPERTY_HINT_PLACEHOLDER_TEXT, "iPhone Developer"), "")); r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/code_sign_identity_release", PROPERTY_HINT_PLACEHOLDER_TEXT, "iPhone Distribution"), "")); + r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/provisioning_profile_uuid_debug", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_SECRET), "")); + r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/provisioning_profile_uuid_release", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_SECRET), "")); + r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/provisioning_profile_specifier_debug", PROPERTY_HINT_PLACEHOLDER_TEXT, ""), "")); + r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/provisioning_profile_specifier_release", PROPERTY_HINT_PLACEHOLDER_TEXT, ""), "")); r_options->push_back(ExportOption(PropertyInfo(Variant::INT, "application/export_method_release", PROPERTY_HINT_ENUM, "App Store,Development,Ad-Hoc,Enterprise"), 0)); r_options->push_back(ExportOption(PropertyInfo(Variant::INT, "application/targeted_device_family", PROPERTY_HINT_ENUM, "iPhone,iPad,iPhone & iPad"), 2)); @@ -409,6 +411,15 @@ void EditorExportPlatformIOS::_fix_config_file(const Ref &p_ String rel_sign_id = p_preset->get("application/code_sign_identity_release").operator String().is_empty() ? "iPhone Distribution" : p_preset->get("application/code_sign_identity_release"); bool dbg_manual = !p_preset->get_or_env("application/provisioning_profile_uuid_debug", ENV_IOS_PROFILE_UUID_DEBUG).operator String().is_empty() || (dbg_sign_id != "iPhone Developer" && dbg_sign_id != "iPhone Distribution"); bool rel_manual = !p_preset->get_or_env("application/provisioning_profile_uuid_release", ENV_IOS_PROFILE_UUID_RELEASE).operator String().is_empty() || (rel_sign_id != "iPhone Developer" && rel_sign_id != "iPhone Distribution"); + + bool valid_dbg_specifier = false; + bool valid_rel_specifier = false; + Variant provisioning_profile_specifier_dbg_variant = p_preset->get_or_env("application/provisioning_profile_specifier_debug", ENV_IOS_PROFILE_SPECIFIER_DEBUG, &valid_dbg_specifier); + dbg_manual |= valid_dbg_specifier; + + Variant provisioning_profile_specifier_rel_variant = p_preset->get_or_env("application/provisioning_profile_specifier_release", ENV_IOS_PROFILE_SPECIFIER_RELEASE, &valid_rel_specifier); + rel_manual |= valid_rel_specifier; + String str; String strnew; str.parse_utf8((const char *)pfile.ptr(), pfile.size()); @@ -443,6 +454,15 @@ void EditorExportPlatformIOS::_fix_config_file(const Ref &p_ } else if (lines[i].contains("$export_method")) { int export_method = p_preset->get(p_debug ? "application/export_method_debug" : "application/export_method_release"); strnew += lines[i].replace("$export_method", export_method_string[export_method]) + "\n"; + } else if (lines[i].contains("$provisioning_profile_specifier_debug")) { + String specifier = provisioning_profile_specifier_dbg_variant.get_type() != Variant::NIL ? provisioning_profile_specifier_dbg_variant : ""; + strnew += lines[i].replace("$provisioning_profile_specifier_debug", specifier) + "\n"; + } else if (lines[i].contains("$provisioning_profile_specifier_release")) { + String specifier = provisioning_profile_specifier_rel_variant.get_type() != Variant::NIL ? provisioning_profile_specifier_rel_variant : ""; + strnew += lines[i].replace("$provisioning_profile_specifier_release", specifier) + "\n"; + } else if (lines[i].contains("$provisioning_profile_specifier")) { + String specifier = p_debug ? provisioning_profile_specifier_dbg_variant : provisioning_profile_specifier_rel_variant; + strnew += lines[i].replace("$provisioning_profile_specifier", specifier) + "\n"; } else if (lines[i].contains("$provisioning_profile_uuid_release")) { strnew += lines[i].replace("$provisioning_profile_uuid_release", p_preset->get_or_env("application/provisioning_profile_uuid_release", ENV_IOS_PROFILE_UUID_RELEASE)) + "\n"; } else if (lines[i].contains("$provisioning_profile_uuid_debug")) { @@ -460,7 +480,13 @@ void EditorExportPlatformIOS::_fix_config_file(const Ref &p_ strnew += lines[i].replace("$code_sign_style_release", "Automatic") + "\n"; } } else if (lines[i].contains("$provisioning_profile_uuid")) { - String uuid = p_debug ? p_preset->get_or_env("application/provisioning_profile_uuid_debug", ENV_IOS_PROFILE_UUID_DEBUG) : p_preset->get_or_env("application/provisioning_profile_uuid_release", ENV_IOS_PROFILE_UUID_RELEASE); + bool valid = false; + String uuid = p_debug ? p_preset->get_or_env("application/provisioning_profile_uuid_debug", ENV_IOS_PROFILE_UUID_DEBUG, &valid) : p_preset->get_or_env("application/provisioning_profile_uuid_release", ENV_IOS_PROFILE_UUID_RELEASE, &valid); + if (!valid || uuid.is_empty()) { + Variant variant = p_debug ? provisioning_profile_specifier_dbg_variant : provisioning_profile_specifier_rel_variant; + valid = p_debug ? valid_dbg_specifier : valid_rel_specifier; + uuid = valid ? variant : ""; + } strnew += lines[i].replace("$provisioning_profile_uuid", uuid) + "\n"; } else if (lines[i].contains("$code_sign_identity_debug")) { strnew += lines[i].replace("$code_sign_identity_debug", dbg_sign_id) + "\n"; diff --git a/platform/ios/export/export_plugin.h b/platform/ios/export/export_plugin.h index db7c0553ddc..0d78047f7cb 100644 --- a/platform/ios/export/export_plugin.h +++ b/platform/ios/export/export_plugin.h @@ -54,6 +54,8 @@ // of these is set, they will override the values set in the credentials file. const String ENV_IOS_PROFILE_UUID_DEBUG = "GODOT_IOS_PROVISIONING_PROFILE_UUID_DEBUG"; const String ENV_IOS_PROFILE_UUID_RELEASE = "GODOT_IOS_PROVISIONING_PROFILE_UUID_RELEASE"; +const String ENV_IOS_PROFILE_SPECIFIER_DEBUG = "GODOT_IOS_PROFILE_SPECIFIER_DEBUG"; +const String ENV_IOS_PROFILE_SPECIFIER_RELEASE = "GODOT_IOS_PROFILE_SPECIFIER_RELEASE"; class EditorExportPlatformIOS : public EditorExportPlatform { GDCLASS(EditorExportPlatformIOS, EditorExportPlatform); From bf9f78c353b5904facac75b7c45e065a6e4b131e Mon Sep 17 00:00:00 2001 From: bruvzg <7645683+bruvzg@users.noreply.github.com> Date: Tue, 29 Oct 2024 09:32:49 +0200 Subject: [PATCH 012/151] [Windows] Fix restoreing fullscreen window. --- platform/windows/display_server_windows.cpp | 48 ++++++++++++++++++--- platform/windows/display_server_windows.h | 3 +- 2 files changed, 45 insertions(+), 6 deletions(-) diff --git a/platform/windows/display_server_windows.cpp b/platform/windows/display_server_windows.cpp index fc49b63ddee..9b62a598471 100644 --- a/platform/windows/display_server_windows.cpp +++ b/platform/windows/display_server_windows.cpp @@ -2187,15 +2187,21 @@ void DisplayServerWindows::window_set_mode(WindowMode p_mode, WindowID p_window) ERR_FAIL_COND(!windows.has(p_window)); WindowData &wd = windows[p_window]; + bool was_fullscreen = wd.fullscreen; + wd.was_fullscreen_pre_min = false; + if (wd.fullscreen && p_mode != WINDOW_MODE_FULLSCREEN && p_mode != WINDOW_MODE_EXCLUSIVE_FULLSCREEN) { RECT rect; wd.fullscreen = false; wd.multiwindow_fs = false; - wd.maximized = wd.was_maximized; + + // Restore previous maximized state. + wd.maximized = wd.was_maximized_pre_fs; _update_window_style(p_window, false); + // Restore window rect after exiting fullscreen. if (wd.pre_fs_valid) { rect = wd.pre_fs_rect; } else { @@ -2203,7 +2209,6 @@ void DisplayServerWindows::window_set_mode(WindowMode p_mode, WindowID p_window) rect.right = wd.width; rect.top = 0; rect.bottom = wd.height; - wd.pre_fs_valid = true; } ShowWindow(wd.hWnd, SW_RESTORE); @@ -2231,6 +2236,7 @@ void DisplayServerWindows::window_set_mode(WindowMode p_mode, WindowID p_window) ShowWindow(wd.hWnd, SW_MINIMIZE); wd.maximized = false; wd.minimized = true; + wd.was_fullscreen_pre_min = was_fullscreen; } if (p_mode == WINDOW_MODE_EXCLUSIVE_FULLSCREEN) { @@ -2245,10 +2251,14 @@ void DisplayServerWindows::window_set_mode(WindowMode p_mode, WindowID p_window) if (wd.minimized || wd.maximized) { ShowWindow(wd.hWnd, SW_RESTORE); } - wd.was_maximized = wd.maximized; - if (wd.pre_fs_valid) { + // Save previous maximized stare. + wd.was_maximized_pre_fs = wd.maximized; + + if (!was_fullscreen) { + // Save non-fullscreen rect before entering fullscreen. GetWindowRect(wd.hWnd, &wd.pre_fs_rect); + wd.pre_fs_valid = true; } int cs = window_get_current_screen(p_window); @@ -5139,6 +5149,22 @@ LRESULT DisplayServerWindows::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARA ClientToScreen(window.hWnd, (POINT *)&crect.right); ClipCursor(&crect); } + + if (!window.minimized && window.was_fullscreen_pre_min) { + // Restore fullscreen mode if window was in fullscreen before it was minimized. + int cs = window_get_current_screen(window_id); + Point2 pos = screen_get_position(cs) + _get_screens_origin(); + Size2 size = screen_get_size(cs); + + window.was_fullscreen_pre_min = false; + window.fullscreen = true; + window.maximized = false; + window.minimized = false; + + _update_window_style(window_id, false); + + MoveWindow(window.hWnd, pos.x, pos.y, size.width, size.height, TRUE); + } } // Return here to prevent WM_MOVE and WM_SIZE from being sent @@ -5672,7 +5698,19 @@ DisplayServer::WindowID DisplayServerWindows::_create_window(WindowMode p_mode, wd.multiwindow_fs = true; } } - if (p_mode != WINDOW_MODE_FULLSCREEN && p_mode != WINDOW_MODE_EXCLUSIVE_FULLSCREEN) { + + if (p_mode == WINDOW_MODE_FULLSCREEN || p_mode == WINDOW_MODE_EXCLUSIVE_FULLSCREEN) { + // Save initial non-fullscreen rect. + Rect2i srect = screen_get_usable_rect(rq_screen); + Point2i wpos = p_rect.position; + if (srect != Rect2i()) { + wpos = wpos.clamp(srect.position, srect.position + srect.size - p_rect.size / 3); + } + + wd.pre_fs_rect.left = wpos.x + offset.x; + wd.pre_fs_rect.right = wpos.x + p_rect.size.x + offset.x; + wd.pre_fs_rect.top = wpos.y + offset.y; + wd.pre_fs_rect.bottom = wpos.y + p_rect.size.y + offset.y; wd.pre_fs_valid = true; } diff --git a/platform/windows/display_server_windows.h b/platform/windows/display_server_windows.h index fc72e05b1d6..a24f5e48e87 100644 --- a/platform/windows/display_server_windows.h +++ b/platform/windows/display_server_windows.h @@ -468,7 +468,8 @@ class DisplayServerWindows : public DisplayServer { bool resizable = true; bool window_focused = false; int activate_state = 0; - bool was_maximized = false; + bool was_maximized_pre_fs = false; + bool was_fullscreen_pre_min = false; bool always_on_top = false; bool no_focus = false; bool exclusive = false; From e6eeaf28df19c9e8b5acef44c1b38f174b1fce99 Mon Sep 17 00:00:00 2001 From: yds Date: Tue, 29 Oct 2024 19:11:19 -0300 Subject: [PATCH 013/151] Add editor setting to stop the bottom panel from switching to the stack trace --- doc/classes/EditorSettings.xml | 3 +++ editor/debugger/script_editor_debugger.cpp | 2 +- editor/editor_settings.cpp | 1 + 3 files changed, 5 insertions(+), 1 deletion(-) diff --git a/doc/classes/EditorSettings.xml b/doc/classes/EditorSettings.xml index a5097521dc4..ec04cfec963 100644 --- a/doc/classes/EditorSettings.xml +++ b/doc/classes/EditorSettings.xml @@ -188,6 +188,9 @@ If [code]true[/code], automatically switches to the [b]Remote[/b] scene tree when running the project from the editor. If [code]false[/code], stays on the [b]Local[/b] scene tree when running the project from the editor. [b]Warning:[/b] Enabling this setting can cause stuttering when running a project with a large amount of nodes (typically a few thousands of nodes or more), even if the editor window isn't focused. This is due to the remote scene tree being updated every second regardless of whether the editor is focused. + + If [code]true[/code], automatically switches to the [b]Stack Trace[/b] panel when the debugger hits a breakpoint or steps. + If [code]true[/code], enables collection of profiling data from non-GDScript Godot functions, such as engine class methods. Enabling this slows execution while profiling further. diff --git a/editor/debugger/script_editor_debugger.cpp b/editor/debugger/script_editor_debugger.cpp index 73c59707d23..b2d7044f459 100644 --- a/editor/debugger/script_editor_debugger.cpp +++ b/editor/debugger/script_editor_debugger.cpp @@ -315,7 +315,7 @@ void ScriptEditorDebugger::_thread_debug_enter(uint64_t p_thread_id) { ThreadDebugged &td = threads_debugged[p_thread_id]; _set_reason_text(td.error, MESSAGE_ERROR); emit_signal(SNAME("breaked"), true, td.can_debug, td.error, td.has_stackdump); - if (!td.error.is_empty()) { + if (!td.error.is_empty() && EDITOR_GET("debugger/auto_switch_to_stack_trace")) { tabs->set_current_tab(0); } inspector->clear_cache(); // Take a chance to force remote objects update. diff --git a/editor/editor_settings.cpp b/editor/editor_settings.cpp index 12a7c3a2ff8..b763c010c2d 100644 --- a/editor/editor_settings.cpp +++ b/editor/editor_settings.cpp @@ -965,6 +965,7 @@ void EditorSettings::_load_defaults(Ref p_extra_config) { /* Debugger/profiler */ EDITOR_SETTING_BASIC(Variant::BOOL, PROPERTY_HINT_NONE, "debugger/auto_switch_to_remote_scene_tree", false, "") + EDITOR_SETTING_BASIC(Variant::BOOL, PROPERTY_HINT_NONE, "debugger/auto_switch_to_stack_trace", true, "") EDITOR_SETTING(Variant::INT, PROPERTY_HINT_RANGE, "debugger/profiler_frame_history_size", 3600, "60,10000,1") EDITOR_SETTING(Variant::INT, PROPERTY_HINT_RANGE, "debugger/profiler_frame_max_functions", 64, "16,512,1") EDITOR_SETTING(Variant::INT, PROPERTY_HINT_RANGE, "debugger/profiler_target_fps", 60, "1,1000,1") From e81a2afbc49715eea0f82875721b1cc5f99414a9 Mon Sep 17 00:00:00 2001 From: bruvzg <7645683+bruvzg@users.noreply.github.com> Date: Wed, 30 Oct 2024 11:14:11 +0200 Subject: [PATCH 014/151] [TextServer] Reset subpixel shift on blank glyphs and import option to enable/disable it. --- doc/classes/FontFile.xml | 3 ++ doc/classes/ResourceImporterDynamicFont.xml | 3 ++ doc/classes/SystemFont.xml | 3 ++ doc/classes/TextServer.xml | 15 +++++++ doc/classes/TextServerExtension.xml | 17 ++++++++ .../import/dynamic_font_import_settings.cpp | 5 +++ .../import/resource_importer_dynamic_font.cpp | 6 +++ editor/import/resource_importer_imagefont.cpp | 1 + modules/text_server_adv/text_server_adv.cpp | 28 ++++++++++++- modules/text_server_adv/text_server_adv.h | 10 ++++- modules/text_server_fb/text_server_fb.cpp | 17 ++++++++ modules/text_server_fb/text_server_fb.h | 10 ++++- scene/resources/font.cpp | 41 +++++++++++++++++++ scene/resources/font.h | 8 ++++ servers/text/text_server_extension.cpp | 13 ++++++ servers/text/text_server_extension.h | 5 +++ servers/text_server.cpp | 3 ++ servers/text_server.h | 3 ++ 18 files changed, 185 insertions(+), 6 deletions(-) diff --git a/doc/classes/FontFile.xml b/doc/classes/FontFile.xml index c230bf5ad28..e7a724cecd4 100644 --- a/doc/classes/FontFile.xml +++ b/doc/classes/FontFile.xml @@ -631,6 +631,9 @@ Font hinting mode. Used by dynamic fonts only. + + If set to [code]true[/code], when aligning glyphs to the pixel boundaries rounding remainders are accumulated to ensure more uniform glyph distribution. This setting has no effect if subpixel positioning is enabled. + The width of the range around the shape between the minimum and maximum representable signed distance. If using font outlines, [member msdf_pixel_range] must be set to at least [i]twice[/i] the size of the largest font outline. The default [member msdf_pixel_range] value of [code]16[/code] allows outline sizes up to [code]8[/code] to look correct. diff --git a/doc/classes/ResourceImporterDynamicFont.xml b/doc/classes/ResourceImporterDynamicFont.xml index 3727bed8e5f..ee7504aed89 100644 --- a/doc/classes/ResourceImporterDynamicFont.xml +++ b/doc/classes/ResourceImporterDynamicFont.xml @@ -44,6 +44,9 @@ [b]Light:[/b] Sharp result by snapping glyph edges to pixels on the Y axis only. [b]Full:[/b] Sharpest by snapping glyph edges to pixels on both X and Y axes. + + If set to [code]true[/code], when aligning glyphs to the pixel boundaries rounding remainders are accumulated to ensure more uniform glyph distribution. This setting has no effect if subpixel positioning is enabled. + Override the list of languages supported by this font. If left empty, this is supplied by the font metadata. There is usually no need to change this. See also [member script_support]. diff --git a/doc/classes/SystemFont.xml b/doc/classes/SystemFont.xml index 38d6e27c856..b91ae74eae8 100644 --- a/doc/classes/SystemFont.xml +++ b/doc/classes/SystemFont.xml @@ -43,6 +43,9 @@ Font hinting mode. + + If set to [code]true[/code], when aligning glyphs to the pixel boundaries rounding remainders are accumulated to ensure more uniform glyph distribution. This setting has no effect if subpixel positioning is enabled. + The width of the range around the shape between the minimum and maximum representable signed distance. If using font outlines, [member msdf_pixel_range] must be set to at least [i]twice[/i] the size of the largest font outline. The default [member msdf_pixel_range] value of [code]16[/code] allows outline sizes up to [code]8[/code] to look correct. diff --git a/doc/classes/TextServer.xml b/doc/classes/TextServer.xml index d76e65b6184..309d9e69398 100644 --- a/doc/classes/TextServer.xml +++ b/doc/classes/TextServer.xml @@ -317,6 +317,13 @@ Returns the font hinting mode. Used by dynamic fonts only. + + + + + Returns glyph position rounding behavior. If set to [code]true[/code], when aligning glyphs to the pixel boundaries rounding remainders are accumulated to ensure more uniform glyph distribution. This setting has no effect if subpixel positioning is enabled. + + @@ -824,6 +831,14 @@ Sets font hinting mode. Used by dynamic fonts only. + + + + + + Sets glyph position rounding behavior. If set to [code]true[/code], when aligning glyphs to the pixel boundaries rounding remainders are accumulated to ensure more uniform glyph distribution. This setting has no effect if subpixel positioning is enabled. + + diff --git a/doc/classes/TextServerExtension.xml b/doc/classes/TextServerExtension.xml index 3c27404f8e3..fc0749c7484 100644 --- a/doc/classes/TextServerExtension.xml +++ b/doc/classes/TextServerExtension.xml @@ -329,6 +329,14 @@ Returns the font hinting mode. Used by dynamic fonts only. + + + + + [b]Optional.[/b] + Returns glyph position rounding behavior. If set to [code]true[/code], when aligning glyphs to the pixel boundaries rounding remainders are accumulated to ensure more uniform glyph distribution. This setting has no effect if subpixel positioning is enabled. + + @@ -904,6 +912,15 @@ Sets font hinting mode. Used by dynamic fonts only. + + + + + + [b]Optional.[/b] + Sets glyph position rounding behavior. If set to [code]true[/code], when aligning glyphs to the pixel boundaries rounding remainders are accumulated to ensure more uniform glyph distribution. This setting has no effect if subpixel positioning is enabled. + + diff --git a/editor/import/dynamic_font_import_settings.cpp b/editor/import/dynamic_font_import_settings.cpp index 8bbad91b68d..19dafb7c42c 100644 --- a/editor/import/dynamic_font_import_settings.cpp +++ b/editor/import/dynamic_font_import_settings.cpp @@ -494,6 +494,8 @@ void DynamicFontImportSettingsDialog::_main_prop_changed(const String &p_edited_ font_preview->set_hinting((TextServer::Hinting)import_settings_data->get("hinting").operator int()); } else if (p_edited_property == "subpixel_positioning") { font_preview->set_subpixel_positioning((TextServer::SubpixelPositioning)import_settings_data->get("subpixel_positioning").operator int()); + } else if (p_edited_property == "keep_rounding_remainders") { + font_preview->set_keep_rounding_remainders(import_settings_data->get("keep_rounding_remainders")); } else if (p_edited_property == "oversampling") { font_preview->set_oversampling(import_settings_data->get("oversampling")); } @@ -960,6 +962,7 @@ void DynamicFontImportSettingsDialog::_re_import() { main_settings["force_autohinter"] = import_settings_data->get("force_autohinter"); main_settings["hinting"] = import_settings_data->get("hinting"); main_settings["subpixel_positioning"] = import_settings_data->get("subpixel_positioning"); + main_settings["keep_rounding_remainders"] = import_settings_data->get("keep_rounding_remainders"); main_settings["oversampling"] = import_settings_data->get("oversampling"); main_settings["fallbacks"] = import_settings_data->get("fallbacks"); main_settings["compress"] = import_settings_data->get("compress"); @@ -1236,6 +1239,7 @@ void DynamicFontImportSettingsDialog::open_settings(const String &p_path) { font_preview->set_force_autohinter(import_settings_data->get("force_autohinter")); font_preview->set_hinting((TextServer::Hinting)import_settings_data->get("hinting").operator int()); font_preview->set_subpixel_positioning((TextServer::SubpixelPositioning)import_settings_data->get("subpixel_positioning").operator int()); + font_preview->set_keep_rounding_remainders(import_settings_data->get("keep_rounding_remainders")); font_preview->set_oversampling(import_settings_data->get("oversampling")); } font_preview_label->add_theme_font_override(SceneStringName(font), font_preview); @@ -1268,6 +1272,7 @@ DynamicFontImportSettingsDialog::DynamicFontImportSettingsDialog() { options_general.push_back(ResourceImporter::ImportOption(PropertyInfo(Variant::BOOL, "force_autohinter"), false)); options_general.push_back(ResourceImporter::ImportOption(PropertyInfo(Variant::INT, "hinting", PROPERTY_HINT_ENUM, "None,Light,Normal"), 1)); options_general.push_back(ResourceImporter::ImportOption(PropertyInfo(Variant::INT, "subpixel_positioning", PROPERTY_HINT_ENUM, "Disabled,Auto,One Half of a Pixel,One Quarter of a Pixel"), 1)); + options_general.push_back(ResourceImporter::ImportOption(PropertyInfo(Variant::BOOL, "keep_rounding_remainders"), true)); options_general.push_back(ResourceImporter::ImportOption(PropertyInfo(Variant::FLOAT, "oversampling", PROPERTY_HINT_RANGE, "0,10,0.1"), 0.0)); options_general.push_back(ResourceImporter::ImportOption(PropertyInfo(Variant::NIL, "Metadata Overrides", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_GROUP), Variant())); diff --git a/editor/import/resource_importer_dynamic_font.cpp b/editor/import/resource_importer_dynamic_font.cpp index fa222b27902..21fe9b4faa5 100644 --- a/editor/import/resource_importer_dynamic_font.cpp +++ b/editor/import/resource_importer_dynamic_font.cpp @@ -85,6 +85,9 @@ bool ResourceImporterDynamicFont::get_option_visibility(const String &p_path, co if (p_option == "subpixel_positioning" && bool(p_options["multichannel_signed_distance_field"])) { return false; } + if (p_option == "keep_rounding_remainders" && bool(p_options["multichannel_signed_distance_field"])) { + return false; + } return true; } @@ -119,6 +122,7 @@ void ResourceImporterDynamicFont::get_import_options(const String &p_path, List< r_options->push_back(ImportOption(PropertyInfo(Variant::BOOL, "force_autohinter"), false)); r_options->push_back(ImportOption(PropertyInfo(Variant::INT, "hinting", PROPERTY_HINT_ENUM, "None,Light,Normal"), 1)); r_options->push_back(ImportOption(PropertyInfo(Variant::INT, "subpixel_positioning", PROPERTY_HINT_ENUM, "Disabled,Auto,One Half of a Pixel,One Quarter of a Pixel,Auto (Except Pixel Fonts)"), 4)); + r_options->push_back(ImportOption(PropertyInfo(Variant::BOOL, "keep_rounding_remainders"), true)); r_options->push_back(ImportOption(PropertyInfo(Variant::FLOAT, "oversampling", PROPERTY_HINT_RANGE, "0,10,0.1"), 0.0)); r_options->push_back(ImportOption(PropertyInfo(Variant::NIL, "Fallbacks", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_GROUP), Variant())); @@ -156,6 +160,7 @@ Error ResourceImporterDynamicFont::import(const String &p_source_file, const Str bool allow_system_fallback = p_options["allow_system_fallback"]; int hinting = p_options["hinting"]; int subpixel_positioning = p_options["subpixel_positioning"]; + bool keep_rounding_remainders = p_options["keep_rounding_remainders"]; real_t oversampling = p_options["oversampling"]; Array fallbacks = p_options["fallbacks"]; @@ -213,6 +218,7 @@ Error ResourceImporterDynamicFont::import(const String &p_source_file, const Str } } font->set_subpixel_positioning((TextServer::SubpixelPositioning)subpixel_positioning); + font->set_keep_rounding_remainders(keep_rounding_remainders); Dictionary langs = p_options["language_support"]; for (int i = 0; i < langs.size(); i++) { diff --git a/editor/import/resource_importer_imagefont.cpp b/editor/import/resource_importer_imagefont.cpp index f01381904da..01b3935fe41 100644 --- a/editor/import/resource_importer_imagefont.cpp +++ b/editor/import/resource_importer_imagefont.cpp @@ -112,6 +112,7 @@ Error ResourceImporterImageFont::import(const String &p_source_file, const Strin font->set_multichannel_signed_distance_field(false); font->set_fixed_size(chr_height); font->set_subpixel_positioning(TextServer::SUBPIXEL_POSITIONING_DISABLED); + font->set_keep_rounding_remainders(true); font->set_force_autohinter(false); font->set_allow_system_fallback(false); font->set_hinting(TextServer::HINTING_NONE); diff --git a/modules/text_server_adv/text_server_adv.cpp b/modules/text_server_adv/text_server_adv.cpp index 1c6e62650a3..7c50dc6c6e2 100644 --- a/modules/text_server_adv/text_server_adv.cpp +++ b/modules/text_server_adv/text_server_adv.cpp @@ -2467,6 +2467,22 @@ TextServer::SubpixelPositioning TextServerAdvanced::_font_get_subpixel_positioni return fd->subpixel_positioning; } +void TextServerAdvanced::_font_set_keep_rounding_remainders(const RID &p_font_rid, bool p_keep_rounding_remainders) { + FontAdvanced *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL(fd); + + MutexLock lock(fd->mutex); + fd->keep_rounding_remainders = p_keep_rounding_remainders; +} + +bool TextServerAdvanced::_font_get_keep_rounding_remainders(const RID &p_font_rid) const { + FontAdvanced *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL_V(fd, false); + + MutexLock lock(fd->mutex); + return fd->keep_rounding_remainders; +} + void TextServerAdvanced::_font_set_embolden(const RID &p_font_rid, double p_strength) { FontAdvanced *fd = _get_font_data(p_font_rid); ERR_FAIL_NULL(fd); @@ -5195,6 +5211,7 @@ RID TextServerAdvanced::_find_sys_font_for_text(const RID &p_fdef, const String _font_set_force_autohinter(sysf.rid, key.force_autohinter); _font_set_hinting(sysf.rid, key.hinting); _font_set_subpixel_positioning(sysf.rid, key.subpixel_positioning); + _font_set_keep_rounding_remainders(sysf.rid, key.keep_rounding_remainders); _font_set_variation_coordinates(sysf.rid, var); _font_set_oversampling(sysf.rid, key.oversampling); _font_set_embolden(sysf.rid, key.embolden); @@ -6233,6 +6250,9 @@ void TextServerAdvanced::_shape_run(ShapedTextDataAdvanced *p_sd, int64_t p_star #endif gl.index = glyph_info[i].codepoint; + if ((p_sd->text[glyph_info[i].cluster] == 0x0009) || u_isblank(p_sd->text[glyph_info[i].cluster]) || is_linebreak(p_sd->text[glyph_info[i].cluster])) { + adv_rem = 0.0; // Reset on blank. + } if (gl.index != 0) { FontGlyph fgl; _ensure_glyph(fd, fss, gl.index | mod, fgl); @@ -6254,12 +6274,16 @@ void TextServerAdvanced::_shape_run(ShapedTextDataAdvanced *p_sd, int64_t p_star } else { double full_adv = adv_rem + ((double)glyph_pos[i].x_advance / (64.0 / scale) + ea); gl.advance = Math::round(full_adv); - adv_rem = full_adv - gl.advance; + if (fd->keep_rounding_remainders) { + adv_rem = full_adv - gl.advance; + } } } else { double full_adv = adv_rem + ((double)glyph_pos[i].y_advance / (64.0 / scale)); gl.advance = -Math::round(full_adv); - adv_rem = full_adv + gl.advance; + if (fd->keep_rounding_remainders) { + adv_rem = full_adv + gl.advance; + } } if (p_sd->orientation == ORIENTATION_HORIZONTAL) { gl.y_off += _font_get_baseline_offset(gl.font_rid) * (double)(_font_get_ascent(gl.font_rid, gl.font_size) + _font_get_descent(gl.font_rid, gl.font_size)); diff --git a/modules/text_server_adv/text_server_adv.h b/modules/text_server_adv/text_server_adv.h index def57b9372e..4f08356ca7c 100644 --- a/modules/text_server_adv/text_server_adv.h +++ b/modules/text_server_adv/text_server_adv.h @@ -328,6 +328,7 @@ class TextServerAdvanced : public TextServerExtension { bool force_autohinter = false; TextServer::Hinting hinting = TextServer::HINTING_LIGHT; TextServer::SubpixelPositioning subpixel_positioning = TextServer::SUBPIXEL_POSITIONING_AUTO; + bool keep_rounding_remainders = true; Dictionary variation_coordinates; double oversampling = 0.0; double embolden = 0.0; @@ -588,6 +589,7 @@ class TextServerAdvanced : public TextServerExtension { int fixed_size = 0; TextServer::Hinting hinting = TextServer::HINTING_LIGHT; TextServer::SubpixelPositioning subpixel_positioning = TextServer::SUBPIXEL_POSITIONING_AUTO; + bool keep_rounding_remainders = true; Dictionary variation_coordinates; double oversampling = 0.0; double embolden = 0.0; @@ -596,7 +598,7 @@ class TextServerAdvanced : public TextServerExtension { double baseline_offset = 0.0; bool operator==(const SystemFontKey &p_b) const { - return (font_name == p_b.font_name) && (antialiasing == p_b.antialiasing) && (italic == p_b.italic) && (disable_embedded_bitmaps == p_b.disable_embedded_bitmaps) && (mipmaps == p_b.mipmaps) && (msdf == p_b.msdf) && (force_autohinter == p_b.force_autohinter) && (weight == p_b.weight) && (stretch == p_b.stretch) && (msdf_range == p_b.msdf_range) && (msdf_source_size == p_b.msdf_source_size) && (fixed_size == p_b.fixed_size) && (hinting == p_b.hinting) && (subpixel_positioning == p_b.subpixel_positioning) && (variation_coordinates == p_b.variation_coordinates) && (oversampling == p_b.oversampling) && (embolden == p_b.embolden) && (transform == p_b.transform) && (extra_spacing[SPACING_TOP] == p_b.extra_spacing[SPACING_TOP]) && (extra_spacing[SPACING_BOTTOM] == p_b.extra_spacing[SPACING_BOTTOM]) && (extra_spacing[SPACING_SPACE] == p_b.extra_spacing[SPACING_SPACE]) && (extra_spacing[SPACING_GLYPH] == p_b.extra_spacing[SPACING_GLYPH]) && (baseline_offset == p_b.baseline_offset); + return (font_name == p_b.font_name) && (antialiasing == p_b.antialiasing) && (italic == p_b.italic) && (disable_embedded_bitmaps == p_b.disable_embedded_bitmaps) && (mipmaps == p_b.mipmaps) && (msdf == p_b.msdf) && (force_autohinter == p_b.force_autohinter) && (weight == p_b.weight) && (stretch == p_b.stretch) && (msdf_range == p_b.msdf_range) && (msdf_source_size == p_b.msdf_source_size) && (fixed_size == p_b.fixed_size) && (hinting == p_b.hinting) && (subpixel_positioning == p_b.subpixel_positioning) && (keep_rounding_remainders == p_b.keep_rounding_remainders) && (variation_coordinates == p_b.variation_coordinates) && (oversampling == p_b.oversampling) && (embolden == p_b.embolden) && (transform == p_b.transform) && (extra_spacing[SPACING_TOP] == p_b.extra_spacing[SPACING_TOP]) && (extra_spacing[SPACING_BOTTOM] == p_b.extra_spacing[SPACING_BOTTOM]) && (extra_spacing[SPACING_SPACE] == p_b.extra_spacing[SPACING_SPACE]) && (extra_spacing[SPACING_GLYPH] == p_b.extra_spacing[SPACING_GLYPH]) && (baseline_offset == p_b.baseline_offset); } SystemFontKey(const String &p_font_name, bool p_italic, int p_weight, int p_stretch, RID p_font, const TextServerAdvanced *p_fb) { @@ -614,6 +616,7 @@ class TextServerAdvanced : public TextServerExtension { force_autohinter = p_fb->_font_is_force_autohinter(p_font); hinting = p_fb->_font_get_hinting(p_font); subpixel_positioning = p_fb->_font_get_subpixel_positioning(p_font); + keep_rounding_remainders = p_fb->_font_get_keep_rounding_remainders(p_font); variation_coordinates = p_fb->_font_get_variation_coordinates(p_font); oversampling = p_fb->_font_get_oversampling(p_font); embolden = p_fb->_font_get_embolden(p_font); @@ -656,7 +659,7 @@ class TextServerAdvanced : public TextServerExtension { hash = hash_murmur3_one_32(p_a.extra_spacing[SPACING_SPACE], hash); hash = hash_murmur3_one_32(p_a.extra_spacing[SPACING_GLYPH], hash); hash = hash_murmur3_one_double(p_a.baseline_offset, hash); - return hash_fmix32(hash_murmur3_one_32(((int)p_a.mipmaps) | ((int)p_a.msdf << 1) | ((int)p_a.italic << 2) | ((int)p_a.force_autohinter << 3) | ((int)p_a.hinting << 4) | ((int)p_a.subpixel_positioning << 8) | ((int)p_a.antialiasing << 12) | ((int)p_a.disable_embedded_bitmaps << 14), hash)); + return hash_fmix32(hash_murmur3_one_32(((int)p_a.mipmaps) | ((int)p_a.msdf << 1) | ((int)p_a.italic << 2) | ((int)p_a.force_autohinter << 3) | ((int)p_a.hinting << 4) | ((int)p_a.subpixel_positioning << 8) | ((int)p_a.antialiasing << 12) | ((int)p_a.disable_embedded_bitmaps << 14) | ((int)p_a.keep_rounding_remainders << 15), hash)); } }; mutable HashMap system_fonts; @@ -800,6 +803,9 @@ class TextServerAdvanced : public TextServerExtension { MODBIND2(font_set_subpixel_positioning, const RID &, SubpixelPositioning); MODBIND1RC(SubpixelPositioning, font_get_subpixel_positioning, const RID &); + MODBIND2(font_set_keep_rounding_remainders, const RID &, bool); + MODBIND1RC(bool, font_get_keep_rounding_remainders, const RID &); + MODBIND2(font_set_embolden, const RID &, double); MODBIND1RC(double, font_get_embolden, const RID &); diff --git a/modules/text_server_fb/text_server_fb.cpp b/modules/text_server_fb/text_server_fb.cpp index d30b2aae199..e2d89ba3df4 100644 --- a/modules/text_server_fb/text_server_fb.cpp +++ b/modules/text_server_fb/text_server_fb.cpp @@ -1468,6 +1468,22 @@ TextServer::SubpixelPositioning TextServerFallback::_font_get_subpixel_positioni return fd->subpixel_positioning; } +void TextServerFallback::_font_set_keep_rounding_remainders(const RID &p_font_rid, bool p_keep_rounding_remainders) { + FontFallback *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL(fd); + + MutexLock lock(fd->mutex); + fd->keep_rounding_remainders = p_keep_rounding_remainders; +} + +bool TextServerFallback::_font_get_keep_rounding_remainders(const RID &p_font_rid) const { + FontFallback *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL_V(fd, false); + + MutexLock lock(fd->mutex); + return fd->keep_rounding_remainders; +} + void TextServerFallback::_font_set_embolden(const RID &p_font_rid, double p_strength) { FontFallback *fd = _get_font_data(p_font_rid); ERR_FAIL_NULL(fd); @@ -4007,6 +4023,7 @@ RID TextServerFallback::_find_sys_font_for_text(const RID &p_fdef, const String _font_set_force_autohinter(sysf.rid, key.force_autohinter); _font_set_hinting(sysf.rid, key.hinting); _font_set_subpixel_positioning(sysf.rid, key.subpixel_positioning); + _font_set_keep_rounding_remainders(sysf.rid, key.keep_rounding_remainders); _font_set_variation_coordinates(sysf.rid, var); _font_set_oversampling(sysf.rid, key.oversampling); _font_set_embolden(sysf.rid, key.embolden); diff --git a/modules/text_server_fb/text_server_fb.h b/modules/text_server_fb/text_server_fb.h index 56626c1f6c5..62b003a1ec9 100644 --- a/modules/text_server_fb/text_server_fb.h +++ b/modules/text_server_fb/text_server_fb.h @@ -270,6 +270,7 @@ class TextServerFallback : public TextServerExtension { bool allow_system_fallback = true; TextServer::Hinting hinting = TextServer::HINTING_LIGHT; TextServer::SubpixelPositioning subpixel_positioning = TextServer::SUBPIXEL_POSITIONING_AUTO; + bool keep_rounding_remainders = true; Dictionary variation_coordinates; double oversampling = 0.0; double embolden = 0.0; @@ -495,6 +496,7 @@ class TextServerFallback : public TextServerExtension { int fixed_size = 0; TextServer::Hinting hinting = TextServer::HINTING_LIGHT; TextServer::SubpixelPositioning subpixel_positioning = TextServer::SUBPIXEL_POSITIONING_AUTO; + bool keep_rounding_remainders = true; Dictionary variation_coordinates; double oversampling = 0.0; double embolden = 0.0; @@ -503,7 +505,7 @@ class TextServerFallback : public TextServerExtension { double baseline_offset = 0.0; bool operator==(const SystemFontKey &p_b) const { - return (font_name == p_b.font_name) && (antialiasing == p_b.antialiasing) && (italic == p_b.italic) && (disable_embedded_bitmaps == p_b.disable_embedded_bitmaps) && (mipmaps == p_b.mipmaps) && (msdf == p_b.msdf) && (force_autohinter == p_b.force_autohinter) && (weight == p_b.weight) && (stretch == p_b.stretch) && (msdf_range == p_b.msdf_range) && (msdf_source_size == p_b.msdf_source_size) && (fixed_size == p_b.fixed_size) && (hinting == p_b.hinting) && (subpixel_positioning == p_b.subpixel_positioning) && (variation_coordinates == p_b.variation_coordinates) && (oversampling == p_b.oversampling) && (embolden == p_b.embolden) && (transform == p_b.transform) && (extra_spacing[SPACING_TOP] == p_b.extra_spacing[SPACING_TOP]) && (extra_spacing[SPACING_BOTTOM] == p_b.extra_spacing[SPACING_BOTTOM]) && (extra_spacing[SPACING_SPACE] == p_b.extra_spacing[SPACING_SPACE]) && (extra_spacing[SPACING_GLYPH] == p_b.extra_spacing[SPACING_GLYPH]) && (baseline_offset == p_b.baseline_offset); + return (font_name == p_b.font_name) && (antialiasing == p_b.antialiasing) && (italic == p_b.italic) && (disable_embedded_bitmaps == p_b.disable_embedded_bitmaps) && (mipmaps == p_b.mipmaps) && (msdf == p_b.msdf) && (force_autohinter == p_b.force_autohinter) && (weight == p_b.weight) && (stretch == p_b.stretch) && (msdf_range == p_b.msdf_range) && (msdf_source_size == p_b.msdf_source_size) && (fixed_size == p_b.fixed_size) && (hinting == p_b.hinting) && (subpixel_positioning == p_b.subpixel_positioning) && (keep_rounding_remainders == p_b.keep_rounding_remainders) && (variation_coordinates == p_b.variation_coordinates) && (oversampling == p_b.oversampling) && (embolden == p_b.embolden) && (transform == p_b.transform) && (extra_spacing[SPACING_TOP] == p_b.extra_spacing[SPACING_TOP]) && (extra_spacing[SPACING_BOTTOM] == p_b.extra_spacing[SPACING_BOTTOM]) && (extra_spacing[SPACING_SPACE] == p_b.extra_spacing[SPACING_SPACE]) && (extra_spacing[SPACING_GLYPH] == p_b.extra_spacing[SPACING_GLYPH]) && (baseline_offset == p_b.baseline_offset); } SystemFontKey(const String &p_font_name, bool p_italic, int p_weight, int p_stretch, RID p_font, const TextServerFallback *p_fb) { @@ -521,6 +523,7 @@ class TextServerFallback : public TextServerExtension { force_autohinter = p_fb->_font_is_force_autohinter(p_font); hinting = p_fb->_font_get_hinting(p_font); subpixel_positioning = p_fb->_font_get_subpixel_positioning(p_font); + keep_rounding_remainders = p_fb->_font_get_keep_rounding_remainders(p_font); variation_coordinates = p_fb->_font_get_variation_coordinates(p_font); oversampling = p_fb->_font_get_oversampling(p_font); embolden = p_fb->_font_get_embolden(p_font); @@ -563,7 +566,7 @@ class TextServerFallback : public TextServerExtension { hash = hash_murmur3_one_32(p_a.extra_spacing[SPACING_SPACE], hash); hash = hash_murmur3_one_32(p_a.extra_spacing[SPACING_GLYPH], hash); hash = hash_murmur3_one_double(p_a.baseline_offset, hash); - return hash_fmix32(hash_murmur3_one_32(((int)p_a.mipmaps) | ((int)p_a.msdf << 1) | ((int)p_a.italic << 2) | ((int)p_a.force_autohinter << 3) | ((int)p_a.hinting << 4) | ((int)p_a.subpixel_positioning << 8) | ((int)p_a.antialiasing << 12) | ((int)p_a.disable_embedded_bitmaps << 14), hash)); + return hash_fmix32(hash_murmur3_one_32(((int)p_a.mipmaps) | ((int)p_a.msdf << 1) | ((int)p_a.italic << 2) | ((int)p_a.force_autohinter << 3) | ((int)p_a.hinting << 4) | ((int)p_a.subpixel_positioning << 8) | ((int)p_a.antialiasing << 12) | ((int)p_a.disable_embedded_bitmaps << 14) | ((int)p_a.keep_rounding_remainders << 15), hash)); } }; mutable HashMap system_fonts; @@ -659,6 +662,9 @@ class TextServerFallback : public TextServerExtension { MODBIND2(font_set_subpixel_positioning, const RID &, SubpixelPositioning); MODBIND1RC(SubpixelPositioning, font_get_subpixel_positioning, const RID &); + MODBIND2(font_set_keep_rounding_remainders, const RID &, bool); + MODBIND1RC(bool, font_get_keep_rounding_remainders, const RID &); + MODBIND2(font_set_embolden, const RID &, double); MODBIND1RC(double, font_get_embolden, const RID &); diff --git a/scene/resources/font.cpp b/scene/resources/font.cpp index 5e4136f4493..ce429ae8d40 100644 --- a/scene/resources/font.cpp +++ b/scene/resources/font.cpp @@ -605,6 +605,7 @@ _FORCE_INLINE_ void FontFile::_ensure_rid(int p_cache_index, int p_make_linked_f TS->font_set_allow_system_fallback(cache[p_cache_index], allow_system_fallback); TS->font_set_hinting(cache[p_cache_index], hinting); TS->font_set_subpixel_positioning(cache[p_cache_index], subpixel_positioning); + TS->font_set_keep_rounding_remainders(cache[p_cache_index], keep_rounding_remainders); TS->font_set_oversampling(cache[p_cache_index], oversampling); } } @@ -934,6 +935,9 @@ void FontFile::_bind_methods() { ClassDB::bind_method(D_METHOD("set_subpixel_positioning", "subpixel_positioning"), &FontFile::set_subpixel_positioning); ClassDB::bind_method(D_METHOD("get_subpixel_positioning"), &FontFile::get_subpixel_positioning); + ClassDB::bind_method(D_METHOD("set_keep_rounding_remainders", "keep_rounding_remainders"), &FontFile::set_keep_rounding_remainders); + ClassDB::bind_method(D_METHOD("get_keep_rounding_remainders"), &FontFile::get_keep_rounding_remainders); + ClassDB::bind_method(D_METHOD("set_oversampling", "oversampling"), &FontFile::set_oversampling); ClassDB::bind_method(D_METHOD("get_oversampling"), &FontFile::get_oversampling); @@ -1044,6 +1048,7 @@ void FontFile::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::INT, "font_stretch", PROPERTY_HINT_RANGE, "50,200,25", PROPERTY_USAGE_STORAGE), "set_font_stretch", "get_font_stretch"); ADD_PROPERTY(PropertyInfo(Variant::INT, "subpixel_positioning", PROPERTY_HINT_ENUM, "Disabled,Auto,One Half of a Pixel,One Quarter of a Pixel", PROPERTY_USAGE_STORAGE), "set_subpixel_positioning", "get_subpixel_positioning"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "keep_rounding_remainders", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_STORAGE), "set_keep_rounding_remainders", "get_keep_rounding_remainders"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "multichannel_signed_distance_field", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_STORAGE), "set_multichannel_signed_distance_field", "is_multichannel_signed_distance_field"); ADD_PROPERTY(PropertyInfo(Variant::INT, "msdf_pixel_range", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_STORAGE), "set_msdf_pixel_range", "get_msdf_pixel_range"); ADD_PROPERTY(PropertyInfo(Variant::INT, "msdf_size", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_STORAGE), "set_msdf_size", "get_msdf_size"); @@ -1411,6 +1416,7 @@ void FontFile::reset_state() { allow_system_fallback = true; hinting = TextServer::HINTING_LIGHT; subpixel_positioning = TextServer::SUBPIXEL_POSITIONING_DISABLED; + keep_rounding_remainders = true; msdf_pixel_range = 14; msdf_size = 128; fixed_size = 0; @@ -2296,6 +2302,21 @@ TextServer::SubpixelPositioning FontFile::get_subpixel_positioning() const { return subpixel_positioning; } +void FontFile::set_keep_rounding_remainders(bool p_keep_rounding_remainders) { + if (keep_rounding_remainders != p_keep_rounding_remainders) { + keep_rounding_remainders = p_keep_rounding_remainders; + for (int i = 0; i < cache.size(); i++) { + _ensure_rid(i); + TS->font_set_keep_rounding_remainders(cache[i], keep_rounding_remainders); + } + emit_changed(); + } +} + +bool FontFile::get_keep_rounding_remainders() const { + return keep_rounding_remainders; +} + void FontFile::set_oversampling(real_t p_oversampling) { if (oversampling != p_oversampling) { oversampling = p_oversampling; @@ -3085,6 +3106,9 @@ void SystemFont::_bind_methods() { ClassDB::bind_method(D_METHOD("set_subpixel_positioning", "subpixel_positioning"), &SystemFont::set_subpixel_positioning); ClassDB::bind_method(D_METHOD("get_subpixel_positioning"), &SystemFont::get_subpixel_positioning); + ClassDB::bind_method(D_METHOD("set_keep_rounding_remainders", "keep_rounding_remainders"), &SystemFont::set_keep_rounding_remainders); + ClassDB::bind_method(D_METHOD("get_keep_rounding_remainders"), &SystemFont::get_keep_rounding_remainders); + ClassDB::bind_method(D_METHOD("set_multichannel_signed_distance_field", "msdf"), &SystemFont::set_multichannel_signed_distance_field); ClassDB::bind_method(D_METHOD("is_multichannel_signed_distance_field"), &SystemFont::is_multichannel_signed_distance_field); @@ -3116,6 +3140,7 @@ void SystemFont::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::BOOL, "force_autohinter"), "set_force_autohinter", "is_force_autohinter"); ADD_PROPERTY(PropertyInfo(Variant::INT, "hinting", PROPERTY_HINT_ENUM, "None,Light,Normal"), "set_hinting", "get_hinting"); ADD_PROPERTY(PropertyInfo(Variant::INT, "subpixel_positioning", PROPERTY_HINT_ENUM, "Disabled,Auto,One Half of a Pixel,One Quarter of a Pixel"), "set_subpixel_positioning", "get_subpixel_positioning"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "keep_rounding_remainders"), "set_keep_rounding_remainders", "get_keep_rounding_remainders"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "multichannel_signed_distance_field"), "set_multichannel_signed_distance_field", "is_multichannel_signed_distance_field"); ADD_PROPERTY(PropertyInfo(Variant::INT, "msdf_pixel_range"), "set_msdf_pixel_range", "get_msdf_pixel_range"); ADD_PROPERTY(PropertyInfo(Variant::INT, "msdf_size"), "set_msdf_size", "get_msdf_size"); @@ -3220,6 +3245,7 @@ void SystemFont::_update_base_font() { file->set_allow_system_fallback(allow_system_fallback); file->set_hinting(hinting); file->set_subpixel_positioning(subpixel_positioning); + file->set_keep_rounding_remainders(keep_rounding_remainders); file->set_multichannel_signed_distance_field(msdf); file->set_msdf_pixel_range(msdf_pixel_range); file->set_msdf_size(msdf_size); @@ -3263,6 +3289,7 @@ void SystemFont::reset_state() { allow_system_fallback = true; hinting = TextServer::HINTING_LIGHT; subpixel_positioning = TextServer::SUBPIXEL_POSITIONING_DISABLED; + keep_rounding_remainders = true; oversampling = 0.f; msdf = false; @@ -3416,6 +3443,20 @@ TextServer::SubpixelPositioning SystemFont::get_subpixel_positioning() const { return subpixel_positioning; } +void SystemFont::set_keep_rounding_remainders(bool p_keep_rounding_remainders) { + if (keep_rounding_remainders != p_keep_rounding_remainders) { + keep_rounding_remainders = p_keep_rounding_remainders; + if (base_font.is_valid()) { + base_font->set_keep_rounding_remainders(keep_rounding_remainders); + } + emit_changed(); + } +} + +bool SystemFont::get_keep_rounding_remainders() const { + return keep_rounding_remainders; +} + void SystemFont::set_multichannel_signed_distance_field(bool p_msdf) { if (msdf != p_msdf) { msdf = p_msdf; diff --git a/scene/resources/font.h b/scene/resources/font.h index 68c391c35e4..9e31b8fd8ed 100644 --- a/scene/resources/font.h +++ b/scene/resources/font.h @@ -197,6 +197,7 @@ class FontFile : public Font { bool allow_system_fallback = true; TextServer::Hinting hinting = TextServer::HINTING_LIGHT; TextServer::SubpixelPositioning subpixel_positioning = TextServer::SUBPIXEL_POSITIONING_AUTO; + bool keep_rounding_remainders = true; real_t oversampling = 0.f; #ifndef DISABLE_DEPRECATED @@ -280,6 +281,9 @@ class FontFile : public Font { virtual void set_subpixel_positioning(TextServer::SubpixelPositioning p_subpixel); virtual TextServer::SubpixelPositioning get_subpixel_positioning() const; + virtual void set_keep_rounding_remainders(bool p_keep_rounding_remainders); + virtual bool get_keep_rounding_remainders() const; + virtual void set_oversampling(real_t p_oversampling); virtual real_t get_oversampling() const; @@ -480,6 +484,7 @@ class SystemFont : public Font { bool allow_system_fallback = true; TextServer::Hinting hinting = TextServer::HINTING_LIGHT; TextServer::SubpixelPositioning subpixel_positioning = TextServer::SUBPIXEL_POSITIONING_AUTO; + bool keep_rounding_remainders = true; real_t oversampling = 0.f; bool msdf = false; int msdf_pixel_range = 16; @@ -518,6 +523,9 @@ class SystemFont : public Font { virtual void set_subpixel_positioning(TextServer::SubpixelPositioning p_subpixel); virtual TextServer::SubpixelPositioning get_subpixel_positioning() const; + virtual void set_keep_rounding_remainders(bool p_keep_rounding_remainders); + virtual bool get_keep_rounding_remainders() const; + virtual void set_oversampling(real_t p_oversampling); virtual real_t get_oversampling() const; diff --git a/servers/text/text_server_extension.cpp b/servers/text/text_server_extension.cpp index 1c0d518e75a..17e92b8c8a6 100644 --- a/servers/text/text_server_extension.cpp +++ b/servers/text/text_server_extension.cpp @@ -113,6 +113,9 @@ void TextServerExtension::_bind_methods() { GDVIRTUAL_BIND(_font_set_subpixel_positioning, "font_rid", "subpixel_positioning"); GDVIRTUAL_BIND(_font_get_subpixel_positioning, "font_rid"); + GDVIRTUAL_BIND(_font_set_keep_rounding_remainders, "font_rid", "keep_rounding_remainders"); + GDVIRTUAL_BIND(_font_get_keep_rounding_remainders, "font_rid"); + GDVIRTUAL_BIND(_font_set_embolden, "font_rid", "strength"); GDVIRTUAL_BIND(_font_get_embolden, "font_rid"); @@ -640,6 +643,16 @@ TextServer::SubpixelPositioning TextServerExtension::font_get_subpixel_positioni return ret; } +void TextServerExtension::font_set_keep_rounding_remainders(const RID &p_font_rid, bool p_keep_rounding_remainders) { + GDVIRTUAL_CALL(_font_set_keep_rounding_remainders, p_font_rid, p_keep_rounding_remainders); +} + +bool TextServerExtension::font_get_keep_rounding_remainders(const RID &p_font_rid) const { + bool ret = true; + GDVIRTUAL_CALL(_font_get_keep_rounding_remainders, p_font_rid, ret); + return ret; +} + void TextServerExtension::font_set_embolden(const RID &p_font_rid, double p_strength) { GDVIRTUAL_CALL(_font_set_embolden, p_font_rid, p_strength); } diff --git a/servers/text/text_server_extension.h b/servers/text/text_server_extension.h index bd803be8aad..fd668ab4e57 100644 --- a/servers/text/text_server_extension.h +++ b/servers/text/text_server_extension.h @@ -168,6 +168,11 @@ class TextServerExtension : public TextServer { GDVIRTUAL2(_font_set_subpixel_positioning, RID, SubpixelPositioning); GDVIRTUAL1RC(SubpixelPositioning, _font_get_subpixel_positioning, RID); + virtual void font_set_keep_rounding_remainders(const RID &p_font_rid, bool p_keep_rounding_remainders) override; + virtual bool font_get_keep_rounding_remainders(const RID &p_font_rid) const override; + GDVIRTUAL2(_font_set_keep_rounding_remainders, RID, bool); + GDVIRTUAL1RC(bool, _font_get_keep_rounding_remainders, RID); + virtual void font_set_embolden(const RID &p_font_rid, double p_strength) override; virtual double font_get_embolden(const RID &p_font_rid) const override; GDVIRTUAL2(_font_set_embolden, RID, double); diff --git a/servers/text_server.cpp b/servers/text_server.cpp index d2cf4674ae0..8e4f3fd21fa 100644 --- a/servers/text_server.cpp +++ b/servers/text_server.cpp @@ -269,6 +269,9 @@ void TextServer::_bind_methods() { ClassDB::bind_method(D_METHOD("font_set_subpixel_positioning", "font_rid", "subpixel_positioning"), &TextServer::font_set_subpixel_positioning); ClassDB::bind_method(D_METHOD("font_get_subpixel_positioning", "font_rid"), &TextServer::font_get_subpixel_positioning); + ClassDB::bind_method(D_METHOD("font_set_keep_rounding_remainders", "font_rid", "keep_rounding_remainders"), &TextServer::font_set_keep_rounding_remainders); + ClassDB::bind_method(D_METHOD("font_get_keep_rounding_remainders", "font_rid"), &TextServer::font_get_keep_rounding_remainders); + ClassDB::bind_method(D_METHOD("font_set_embolden", "font_rid", "strength"), &TextServer::font_set_embolden); ClassDB::bind_method(D_METHOD("font_get_embolden", "font_rid"), &TextServer::font_get_embolden); diff --git a/servers/text_server.h b/servers/text_server.h index f448d62cb22..46b263c54ef 100644 --- a/servers/text_server.h +++ b/servers/text_server.h @@ -315,6 +315,9 @@ class TextServer : public RefCounted { virtual void font_set_subpixel_positioning(const RID &p_font_rid, SubpixelPositioning p_subpixel) = 0; virtual SubpixelPositioning font_get_subpixel_positioning(const RID &p_font_rid) const = 0; + virtual void font_set_keep_rounding_remainders(const RID &p_font_rid, bool p_keep_rounding_remainders) = 0; + virtual bool font_get_keep_rounding_remainders(const RID &p_font_rid) const = 0; + virtual void font_set_embolden(const RID &p_font_rid, double p_strength) = 0; virtual double font_get_embolden(const RID &p_font_rid) const = 0; From 25067420131ba93fd02fbfced3775d204965c901 Mon Sep 17 00:00:00 2001 From: bruvzg <7645683+bruvzg@users.noreply.github.com> Date: Fri, 1 Nov 2024 23:44:14 +0200 Subject: [PATCH 015/151] [Editor] Copy encryption and script settings on export preset duplication. --- editor/export/project_export.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/editor/export/project_export.cpp b/editor/export/project_export.cpp index a3cd6523e98..e8aa591f4a6 100644 --- a/editor/export/project_export.cpp +++ b/editor/export/project_export.cpp @@ -684,6 +684,12 @@ void ProjectExportDialog::_duplicate_preset() { preset->set_exclude_filter(current->get_exclude_filter()); preset->set_patches(current->get_patches()); preset->set_custom_features(current->get_custom_features()); + preset->set_enc_in_filter(current->get_enc_in_filter()); + preset->set_enc_ex_filter(current->get_enc_ex_filter()); + preset->set_enc_pck(current->get_enc_pck()); + preset->set_enc_directory(current->get_enc_directory()); + preset->set_script_encryption_key(current->get_script_encryption_key()); + preset->set_script_export_mode(current->get_script_export_mode()); for (const KeyValue &E : current->get_values()) { preset->set(E.key, E.value); From 6c6521348732580afd45413f6bc0c5f63dfa6ce6 Mon Sep 17 00:00:00 2001 From: "ocean (they/them)" Date: Sat, 1 Jul 2023 22:56:23 -0400 Subject: [PATCH 016/151] Extend `Curve` to allow for arbitrary domains --- doc/classes/AnimationNodeOneShot.xml | 4 +- .../AnimationNodeStateMachineTransition.xml | 2 +- doc/classes/AnimationNodeTransition.xml | 2 +- doc/classes/CPUParticles2D.xml | 28 +-- doc/classes/CPUParticles3D.xml | 24 +-- doc/classes/Curve.xml | 31 ++- doc/classes/CurveTexture.xml | 4 +- doc/classes/CurveXYZTexture.xml | 8 +- doc/classes/Line2D.xml | 2 +- doc/classes/RibbonTrailMesh.xml | 2 +- doc/classes/TubeTrailMesh.xml | 2 +- editor/plugins/curve_editor_plugin.cpp | 184 ++++++++---------- editor/plugins/curve_editor_plugin.h | 23 ++- scene/resources/curve.cpp | 136 +++++++++---- scene/resources/curve.h | 18 +- tests/scene/test_curve.h | 172 +++++++++++++++- 16 files changed, 444 insertions(+), 198 deletions(-) diff --git a/doc/classes/AnimationNodeOneShot.xml b/doc/classes/AnimationNodeOneShot.xml index b2a8002d742..28bea47e219 100644 --- a/doc/classes/AnimationNodeOneShot.xml +++ b/doc/classes/AnimationNodeOneShot.xml @@ -70,14 +70,14 @@ If [code]true[/code], breaks the loop at the end of the loop cycle for transition, even if the animation is looping. - Determines how cross-fading between animations is eased. If empty, the transition will be linear. + Determines how cross-fading between animations is eased. If empty, the transition will be linear. Should be a unit [Curve]. The fade-in duration. For example, setting this to [code]1.0[/code] for a 5 second length animation will produce a cross-fade that starts at 0 second and ends at 1 second during the animation. [b]Note:[/b] [AnimationNodeOneShot] transitions the current state after the end of the fading. When [AnimationNodeOutput] is considered as the most upstream, so the [member fadein_time] is scaled depending on the downstream delta. For example, if this value is set to [code]1.0[/code] and a [AnimationNodeTimeScale] with a value of [code]2.0[/code] is chained downstream, the actual processing time will be 0.5 second. - Determines how cross-fading between animations is eased. If empty, the transition will be linear. + Determines how cross-fading between animations is eased. If empty, the transition will be linear. Should be a unit [Curve]. The fade-out duration. For example, setting this to [code]1.0[/code] for a 5 second length animation will produce a cross-fade that starts at 4 second and ends at 5 second during the animation. diff --git a/doc/classes/AnimationNodeStateMachineTransition.xml b/doc/classes/AnimationNodeStateMachineTransition.xml index 7bd0bd7e7e2..b1f1e7ab614 100644 --- a/doc/classes/AnimationNodeStateMachineTransition.xml +++ b/doc/classes/AnimationNodeStateMachineTransition.xml @@ -41,7 +41,7 @@ The transition type. - Ease curve for better control over cross-fade between this state and the next. + Ease curve for better control over cross-fade between this state and the next. Should be a unit [Curve]. The time to cross-fade between this state and the next. diff --git a/doc/classes/AnimationNodeTransition.xml b/doc/classes/AnimationNodeTransition.xml index 382166d823e..af80fef73a2 100644 --- a/doc/classes/AnimationNodeTransition.xml +++ b/doc/classes/AnimationNodeTransition.xml @@ -96,7 +96,7 @@ The number of enabled input ports for this animation node. - Determines how cross-fading between animations is eased. If empty, the transition will be linear. + Determines how cross-fading between animations is eased. If empty, the transition will be linear. Should be a unit [Curve]. Cross-fading time (in seconds) between each animation connected to the inputs. diff --git a/doc/classes/CPUParticles2D.xml b/doc/classes/CPUParticles2D.xml index 99411c73aa7..558a5674d23 100644 --- a/doc/classes/CPUParticles2D.xml +++ b/doc/classes/CPUParticles2D.xml @@ -57,7 +57,7 @@ - Sets the [Curve] of the parameter specified by [enum Parameter]. + Sets the [Curve] of the parameter specified by [enum Parameter]. Should be a unit [Curve]. @@ -90,7 +90,7 @@ Number of particles emitted in one emission cycle. - Each particle's rotation will be animated along this [Curve]. + Each particle's rotation will be animated along this [Curve]. Should be a unit [Curve]. Maximum initial rotation applied to each particle, in degrees. @@ -99,7 +99,7 @@ Minimum equivalent of [member angle_max]. - Each particle's angular velocity will vary along this [Curve]. + Each particle's angular velocity will vary along this [Curve]. Should be a unit [Curve]. Maximum initial angular velocity (rotation speed) applied to each particle in [i]degrees[/i] per second. @@ -108,7 +108,7 @@ Minimum equivalent of [member angular_velocity_max]. - Each particle's animation offset will vary along this [Curve]. + Each particle's animation offset will vary along this [Curve]. Should be a unit [Curve]. Maximum animation offset that corresponds to frame index in the texture. [code]0[/code] is the first frame, [code]1[/code] is the last one. See [member CanvasItemMaterial.particles_animation]. @@ -117,7 +117,7 @@ Minimum equivalent of [member anim_offset_max]. - Each particle's animation speed will vary along this [Curve]. + Each particle's animation speed will vary along this [Curve]. Should be a unit [Curve]. Maximum particle animation speed. Animation speed of [code]1[/code] means that the particles will make full [code]0[/code] to [code]1[/code] offset cycle during lifetime, [code]2[/code] means [code]2[/code] cycles etc. @@ -136,7 +136,7 @@ Each particle's color will vary along this [Gradient] (multiplied with [member color]). - Damping will vary along this [Curve]. + Damping will vary along this [Curve]. Should be a unit [Curve]. The maximum rate at which particles lose velocity. For example value of [code]100[/code] means that the particle will go from [code]100[/code] velocity to [code]0[/code] in [code]1[/code] second. @@ -184,7 +184,7 @@ Gravity applied to every particle. - Each particle's hue will vary along this [Curve]. + Each particle's hue will vary along this [Curve]. Should be a unit [Curve]. Maximum initial hue variation applied to each particle. It will shift the particle color's hue. @@ -205,7 +205,7 @@ Particle lifetime randomness ratio. - Each particle's linear acceleration will vary along this [Curve]. + Each particle's linear acceleration will vary along this [Curve]. Should be a unit [Curve]. Maximum linear acceleration applied to each particle in the direction of motion. @@ -220,7 +220,7 @@ If [code]true[/code], only one emission cycle occurs. If set [code]true[/code] during a cycle, emission will stop at the cycle's end. - Each particle's orbital velocity will vary along this [Curve]. + Each particle's orbital velocity will vary along this [Curve]. Should be a unit [Curve]. Maximum orbital velocity applied to each particle. Makes the particles circle around origin. Specified in number of full rotations around origin per second. @@ -235,7 +235,7 @@ Particle system starts as if it had already run for this many seconds. - Each particle's radial acceleration will vary along this [Curve]. + Each particle's radial acceleration will vary along this [Curve]. Should be a unit [Curve]. Maximum radial acceleration applied to each particle. Makes particle accelerate away from the origin or towards it if negative. @@ -247,7 +247,7 @@ Emission lifetime randomness ratio. - Each particle's scale will vary along this [Curve]. + Each particle's scale will vary along this [Curve]. Should be a unit [Curve]. Maximum initial scale applied to each particle. @@ -256,11 +256,11 @@ Minimum equivalent of [member scale_amount_max]. - Each particle's horizontal scale will vary along this [Curve]. + Each particle's horizontal scale will vary along this [Curve]. Should be a unit [Curve]. [member split_scale] must be enabled. - Each particle's vertical scale will vary along this [Curve]. + Each particle's vertical scale will vary along this [Curve]. Should be a unit [Curve]. [member split_scale] must be enabled. @@ -273,7 +273,7 @@ Each particle's initial direction range from [code]+spread[/code] to [code]-spread[/code] degrees. - Each particle's tangential acceleration will vary along this [Curve]. + Each particle's tangential acceleration will vary along this [Curve]. Should be a unit [Curve]. Maximum tangential acceleration applied to each particle. Tangential acceleration is perpendicular to the particle's velocity giving the particles a swirling motion. diff --git a/doc/classes/CPUParticles3D.xml b/doc/classes/CPUParticles3D.xml index 04ee95457c4..805299556a8 100644 --- a/doc/classes/CPUParticles3D.xml +++ b/doc/classes/CPUParticles3D.xml @@ -63,7 +63,7 @@ - Sets the [Curve] of the parameter specified by [enum Parameter]. + Sets the [Curve] of the parameter specified by [enum Parameter]. Should be a unit [Curve]. @@ -96,7 +96,7 @@ Number of particles emitted in one emission cycle. - Each particle's rotation will be animated along this [Curve]. + Each particle's rotation will be animated along this [Curve]. Should be a unit [Curve]. Maximum angle. @@ -105,7 +105,7 @@ Minimum angle. - Each particle's angular velocity (rotation speed) will vary along this [Curve] over its lifetime. + Each particle's angular velocity (rotation speed) will vary along this [Curve] over its lifetime. Should be a unit [Curve]. Maximum initial angular velocity (rotation speed) applied to each particle in [i]degrees[/i] per second. @@ -114,7 +114,7 @@ Minimum initial angular velocity (rotation speed) applied to each particle in [i]degrees[/i] per second. - Each particle's animation offset will vary along this [Curve]. + Each particle's animation offset will vary along this [Curve]. Should be a unit [Curve]. Maximum animation offset. @@ -123,7 +123,7 @@ Minimum animation offset. - Each particle's animation speed will vary along this [Curve]. + Each particle's animation speed will vary along this [Curve]. Should be a unit [Curve]. Maximum particle animation speed. @@ -144,7 +144,7 @@ [b]Note:[/b] [member color_ramp] multiplies the particle mesh's vertex colors. To have a visible effect on a [BaseMaterial3D], [member BaseMaterial3D.vertex_color_use_as_albedo] [i]must[/i] be [code]true[/code]. For a [ShaderMaterial], [code]ALBEDO *= COLOR.rgb;[/code] must be inserted in the shader's [code]fragment()[/code] function. Otherwise, [member color_ramp] will have no visible effect. - Damping will vary along this [Curve]. + Damping will vary along this [Curve]. Should be a unit [Curve]. Maximum damping. @@ -212,7 +212,7 @@ Gravity applied to every particle. - Each particle's hue will vary along this [Curve]. + Each particle's hue will vary along this [Curve]. Should be a unit [Curve]. Maximum hue variation. @@ -233,7 +233,7 @@ Particle lifetime randomness ratio. - Each particle's linear acceleration will vary along this [Curve]. + Each particle's linear acceleration will vary along this [Curve]. Should be a unit [Curve]. Maximum linear acceleration. @@ -251,7 +251,7 @@ If [code]true[/code], only one emission cycle occurs. If set [code]true[/code] during a cycle, emission will stop at the cycle's end. - Each particle's orbital velocity will vary along this [Curve]. + Each particle's orbital velocity will vary along this [Curve]. Should be a unit [Curve]. Maximum orbit velocity. @@ -272,7 +272,7 @@ Particle system starts as if it had already run for this many seconds. - Each particle's radial acceleration will vary along this [Curve]. + Each particle's radial acceleration will vary along this [Curve]. Should be a unit [Curve]. Maximum radial acceleration. @@ -284,7 +284,7 @@ Emission lifetime randomness ratio. - Each particle's scale will vary along this [Curve]. + Each particle's scale will vary along this [Curve]. Should be a unit [Curve]. Maximum scale. @@ -311,7 +311,7 @@ Each particle's initial direction range from [code]+spread[/code] to [code]-spread[/code] degrees. Applied to X/Z plane and Y/Z planes. - Each particle's tangential acceleration will vary along this [Curve]. + Each particle's tangential acceleration will vary along this [Curve]. Should be a unit [Curve]. Maximum tangent acceleration. diff --git a/doc/classes/Curve.xml b/doc/classes/Curve.xml index 25b06f10638..5246e0ba670 100644 --- a/doc/classes/Curve.xml +++ b/doc/classes/Curve.xml @@ -4,8 +4,8 @@ A mathematical curve. - This resource describes a mathematical curve by defining a set of points and tangents at each point. By default, it ranges between [code]0[/code] and [code]1[/code] on the Y axis and positions points relative to the [code]0.5[/code] Y position. - See also [Gradient] which is designed for color interpolation. See also [Curve2D] and [Curve3D]. + This resource describes a mathematical curve by defining a set of points and tangents at each point. By default, it ranges between [code]0[/code] and [code]1[/code] on the X and Y axes, but these ranges can be changed. + Please note that many resources and nodes assume they are given [i]unit curves[/i]. A unit curve is a curve whose domain (the X axis) is between [code]0[/code] and [code]1[/code]. Some examples of unit curve usage are [member CPUParticles2D.angle_curve] and [member Line2D.width_curve]. @@ -39,6 +39,12 @@ Removes all points from the curve. + + + + Returns the difference between [member min_domain] and [member max_domain]. + + @@ -74,6 +80,12 @@ Returns the right tangent angle (in degrees) for the point at [param index]. + + + + Returns the difference between [member min_value] and [member max_value]. + + @@ -148,17 +160,28 @@ The number of points to include in the baked (i.e. cached) curve data. + + The maximum domain (x-coordinate) that points can have. + - The maximum value the curve can reach. + The maximum value (y-coordinate) that points can have. Tangents can cause higher values between points. + + + The minimum domain (x-coordinate) that points can have. - The minimum value the curve can reach. + The minimum value (y-coordinate) that points can have. Tangents can cause lower values between points. The number of points describing the curve. + + + Emitted when [member max_domain] or [member min_domain] is changed. + + Emitted when [member max_value] or [member min_value] is changed. diff --git a/doc/classes/CurveTexture.xml b/doc/classes/CurveTexture.xml index 8cb2384da3b..ed4a2f2d357 100644 --- a/doc/classes/CurveTexture.xml +++ b/doc/classes/CurveTexture.xml @@ -4,14 +4,14 @@ A 1D texture where pixel brightness corresponds to points on a curve. - A 1D texture where pixel brightness corresponds to points on a [Curve] resource, either in grayscale or in red. This visual representation simplifies the task of saving curves as image files. + A 1D texture where pixel brightness corresponds to points on a unit [Curve] resource, either in grayscale or in red. This visual representation simplifies the task of saving curves as image files. If you need to store up to 3 curves within a single texture, use [CurveXYZTexture] instead. See also [GradientTexture1D] and [GradientTexture2D]. - The [Curve] that is rendered onto the texture. + The [Curve] that is rendered onto the texture. Should be a unit [Curve]. diff --git a/doc/classes/CurveXYZTexture.xml b/doc/classes/CurveXYZTexture.xml index 8353ed90920..472ce7bb99a 100644 --- a/doc/classes/CurveXYZTexture.xml +++ b/doc/classes/CurveXYZTexture.xml @@ -4,20 +4,20 @@ A 1D texture where the red, green, and blue color channels correspond to points on 3 curves. - A 1D texture where the red, green, and blue color channels correspond to points on 3 [Curve] resources. Compared to using separate [CurveTexture]s, this further simplifies the task of saving curves as image files. + A 1D texture where the red, green, and blue color channels correspond to points on 3 unit [Curve] resources. Compared to using separate [CurveTexture]s, this further simplifies the task of saving curves as image files. If you only need to store one curve within a single texture, use [CurveTexture] instead. See also [GradientTexture1D] and [GradientTexture2D]. - The [Curve] that is rendered onto the texture's red channel. + The [Curve] that is rendered onto the texture's red channel. Should be a unit [Curve]. - The [Curve] that is rendered onto the texture's green channel. + The [Curve] that is rendered onto the texture's green channel. Should be a unit [Curve]. - The [Curve] that is rendered onto the texture's blue channel. + The [Curve] that is rendered onto the texture's blue channel. Should be a unit [Curve]. diff --git a/doc/classes/Line2D.xml b/doc/classes/Line2D.xml index a553e79746e..825462d21a7 100644 --- a/doc/classes/Line2D.xml +++ b/doc/classes/Line2D.xml @@ -101,7 +101,7 @@ The polyline's width. - The polyline's width curve. The width of the polyline over its length will be equivalent to the value of the width curve over its domain. + The polyline's width curve. The width of the polyline over its length will be equivalent to the value of the width curve over its domain. The width curve should be a unit [Curve]. diff --git a/doc/classes/RibbonTrailMesh.xml b/doc/classes/RibbonTrailMesh.xml index 74523f3c390..113787d3a4e 100644 --- a/doc/classes/RibbonTrailMesh.xml +++ b/doc/classes/RibbonTrailMesh.xml @@ -13,7 +13,7 @@ - Determines the size of the ribbon along its length. The size of a particular section segment is obtained by multiplying the baseline [member size] by the value of this curve at the given distance. For values smaller than [code]0[/code], the faces will be inverted. + Determines the size of the ribbon along its length. The size of a particular section segment is obtained by multiplying the baseline [member size] by the value of this curve at the given distance. For values smaller than [code]0[/code], the faces will be inverted. Should be a unit [Curve]. The length of a section of the ribbon. diff --git a/doc/classes/TubeTrailMesh.xml b/doc/classes/TubeTrailMesh.xml index bf16b3d16af..4408280f42a 100644 --- a/doc/classes/TubeTrailMesh.xml +++ b/doc/classes/TubeTrailMesh.xml @@ -19,7 +19,7 @@ If [code]true[/code], generates a cap at the top of the tube. This can be set to [code]false[/code] to speed up generation and rendering when the cap is never seen by the camera. - Determines the radius of the tube along its length. The radius of a particular section ring is obtained by multiplying the baseline [member radius] by the value of this curve at the given distance. For values smaller than [code]0[/code], the faces will be inverted. + Determines the radius of the tube along its length. The radius of a particular section ring is obtained by multiplying the baseline [member radius] by the value of this curve at the given distance. For values smaller than [code]0[/code], the faces will be inverted. Should be a unit [Curve]. The number of sides on the tube. For example, a value of [code]5[/code] means the tube will be pentagonal. Higher values result in a more detailed tube at the cost of performance. diff --git a/editor/plugins/curve_editor_plugin.cpp b/editor/plugins/curve_editor_plugin.cpp index 67006af44b4..aaf7c427f0f 100644 --- a/editor/plugins/curve_editor_plugin.cpp +++ b/editor/plugins/curve_editor_plugin.cpp @@ -64,6 +64,7 @@ void CurveEdit::set_curve(Ref p_curve) { if (curve.is_valid()) { curve->disconnect_changed(callable_mp(this, &CurveEdit::_curve_changed)); curve->disconnect(Curve::SIGNAL_RANGE_CHANGED, callable_mp(this, &CurveEdit::_curve_changed)); + curve->disconnect(Curve::SIGNAL_DOMAIN_CHANGED, callable_mp(this, &CurveEdit::_curve_changed)); } curve = p_curve; @@ -71,6 +72,7 @@ void CurveEdit::set_curve(Ref p_curve) { if (curve.is_valid()) { curve->connect_changed(callable_mp(this, &CurveEdit::_curve_changed)); curve->connect(Curve::SIGNAL_RANGE_CHANGED, callable_mp(this, &CurveEdit::_curve_changed)); + curve->connect(Curve::SIGNAL_DOMAIN_CHANGED, callable_mp(this, &CurveEdit::_curve_changed)); } // Note: if you edit a curve, then set another, and try to undo, @@ -226,10 +228,10 @@ void CurveEdit::gui_input(const Ref &p_event) { } } else if (grabbing == GRAB_NONE) { // Adding a new point. Insert a temporary point for the user to adjust, so it's not in the undo/redo. - Vector2 new_pos = get_world_pos(mpos).clamp(Vector2(0.0, curve->get_min_value()), Vector2(1.0, curve->get_max_value())); + Vector2 new_pos = get_world_pos(mpos).clamp(Vector2(curve->get_min_domain(), curve->get_min_value()), Vector2(curve->get_max_domain(), curve->get_max_value())); if (snap_enabled || mb->is_command_or_control_pressed()) { - new_pos.x = Math::snapped(new_pos.x, 1.0 / snap_count); - new_pos.y = Math::snapped(new_pos.y - curve->get_min_value(), curve->get_range() / snap_count) + curve->get_min_value(); + new_pos.x = Math::snapped(new_pos.x - curve->get_min_domain(), curve->get_domain_range() / snap_count) + curve->get_min_domain(); + new_pos.y = Math::snapped(new_pos.y - curve->get_min_value(), curve->get_value_range() / snap_count) + curve->get_min_value(); } new_pos.x = get_offset_without_collision(selected_index, new_pos.x, mpos.x >= get_view_pos(new_pos).x); @@ -276,11 +278,11 @@ void CurveEdit::gui_input(const Ref &p_event) { if (selected_index != -1) { if (selected_tangent_index == TANGENT_NONE) { // Drag point. - Vector2 new_pos = get_world_pos(mpos).clamp(Vector2(0.0, curve->get_min_value()), Vector2(1.0, curve->get_max_value())); + Vector2 new_pos = get_world_pos(mpos).clamp(Vector2(curve->get_min_domain(), curve->get_min_value()), Vector2(curve->get_max_domain(), curve->get_max_value())); if (snap_enabled || mm->is_command_or_control_pressed()) { - new_pos.x = Math::snapped(new_pos.x, 1.0 / snap_count); - new_pos.y = Math::snapped(new_pos.y - curve->get_min_value(), curve->get_range() / snap_count) + curve->get_min_value(); + new_pos.x = Math::snapped(new_pos.x - curve->get_min_domain(), curve->get_domain_range() / snap_count) + curve->get_min_domain(); + new_pos.y = Math::snapped(new_pos.y - curve->get_min_value(), curve->get_value_range() / snap_count) + curve->get_min_value(); } // Allow to snap to axes with Shift. @@ -295,8 +297,8 @@ void CurveEdit::gui_input(const Ref &p_event) { // Allow to constraint the point between the adjacent two with Alt. if (mm->is_alt_pressed()) { - float prev_point_offset = (selected_index > 0) ? (curve->get_point_position(selected_index - 1).x + 0.00001) : 0.0; - float next_point_offset = (selected_index < curve->get_point_count() - 1) ? (curve->get_point_position(selected_index + 1).x - 0.00001) : 1.0; + float prev_point_offset = (selected_index > 0) ? (curve->get_point_position(selected_index - 1).x + 0.00001) : curve->get_min_domain(); + float next_point_offset = (selected_index < curve->get_point_count() - 1) ? (curve->get_point_position(selected_index + 1).x - 0.00001) : curve->get_max_domain(); new_pos.x = CLAMP(new_pos.x, prev_point_offset, next_point_offset); } @@ -357,37 +359,39 @@ void CurveEdit::use_preset(int p_preset_id) { Array previous_data = curve->get_data(); curve->clear_points(); - float min_value = curve->get_min_value(); - float max_value = curve->get_max_value(); + const float min_y = curve->get_min_value(); + const float max_y = curve->get_max_value(); + const float min_x = curve->get_min_domain(); + const float max_x = curve->get_max_domain(); switch (p_preset_id) { case PRESET_CONSTANT: - curve->add_point(Vector2(0, (min_value + max_value) / 2.0)); - curve->add_point(Vector2(1, (min_value + max_value) / 2.0)); + curve->add_point(Vector2(min_x, (min_y + max_y) / 2.0)); + curve->add_point(Vector2(max_x, (min_y + max_y) / 2.0)); curve->set_point_right_mode(0, Curve::TANGENT_LINEAR); curve->set_point_left_mode(1, Curve::TANGENT_LINEAR); break; case PRESET_LINEAR: - curve->add_point(Vector2(0, min_value)); - curve->add_point(Vector2(1, max_value)); + curve->add_point(Vector2(min_x, min_y)); + curve->add_point(Vector2(max_x, max_y)); curve->set_point_right_mode(0, Curve::TANGENT_LINEAR); curve->set_point_left_mode(1, Curve::TANGENT_LINEAR); break; case PRESET_EASE_IN: - curve->add_point(Vector2(0, min_value)); - curve->add_point(Vector2(1, max_value), curve->get_range() * 1.4, 0); + curve->add_point(Vector2(min_x, min_y)); + curve->add_point(Vector2(max_x, max_y), curve->get_value_range() / curve->get_domain_range() * 1.4, 0); break; case PRESET_EASE_OUT: - curve->add_point(Vector2(0, min_value), 0, curve->get_range() * 1.4); - curve->add_point(Vector2(1, max_value)); + curve->add_point(Vector2(min_x, min_y), 0, curve->get_value_range() / curve->get_domain_range() * 1.4); + curve->add_point(Vector2(max_x, max_y)); break; case PRESET_SMOOTHSTEP: - curve->add_point(Vector2(0, min_value)); - curve->add_point(Vector2(1, max_value)); + curve->add_point(Vector2(min_x, min_y)); + curve->add_point(Vector2(max_x, max_y)); break; default: @@ -411,7 +415,7 @@ void CurveEdit::_curve_changed() { } } -int CurveEdit::get_point_at(Vector2 p_pos) const { +int CurveEdit::get_point_at(const Vector2 &p_pos) const { if (curve.is_null()) { return -1; } @@ -432,7 +436,7 @@ int CurveEdit::get_point_at(Vector2 p_pos) const { return closest_idx; } -CurveEdit::TangentIndex CurveEdit::get_tangent_at(Vector2 p_pos) const { +CurveEdit::TangentIndex CurveEdit::get_tangent_at(const Vector2 &p_pos) const { if (curve.is_null() || selected_index < 0) { return TANGENT_NONE; } @@ -491,7 +495,7 @@ float CurveEdit::get_offset_without_collision(int p_current_index, float p_offse return safe_offset; } -void CurveEdit::add_point(Vector2 p_pos) { +void CurveEdit::add_point(const Vector2 &p_pos) { ERR_FAIL_COND(curve.is_null()); // Add a point to get its index, then remove it immediately. Trick to feed the UndoRedo. @@ -531,7 +535,7 @@ void CurveEdit::remove_point(int p_index) { undo_redo->commit_action(); } -void CurveEdit::set_point_position(int p_index, Vector2 p_pos) { +void CurveEdit::set_point_position(int p_index, const Vector2 &p_pos) { ERR_FAIL_COND(curve.is_null()); ERR_FAIL_INDEX_MSG(p_index, curve->get_point_count(), "Curve point is out of bounds."); @@ -657,10 +661,12 @@ void CurveEdit::update_view_transform() { const real_t margin = font->get_height(font_size) + 2 * EDSCALE; + float min_x = curve.is_valid() ? curve->get_min_domain() : 0.0; + float max_x = curve.is_valid() ? curve->get_max_domain() : 1.0; float min_y = curve.is_valid() ? curve->get_min_value() : 0.0; float max_y = curve.is_valid() ? curve->get_max_value() : 1.0; - const Rect2 world_rect = Rect2(Curve::MIN_X, min_y, Curve::MAX_X, max_y - min_y); + const Rect2 world_rect = Rect2(min_x, min_y, max_x - min_x, max_y - min_y); const Size2 view_margin(margin, margin); const Size2 view_size = get_size() - view_margin * 2; const Vector2 scale = view_size / world_rect.size; @@ -707,70 +713,56 @@ Vector2 CurveEdit::get_tangent_view_pos(int p_index, TangentIndex p_tangent) con return tangent_view_pos; } -Vector2 CurveEdit::get_view_pos(Vector2 p_world_pos) const { +Vector2 CurveEdit::get_view_pos(const Vector2 &p_world_pos) const { return _world_to_view.xform(p_world_pos); } -Vector2 CurveEdit::get_world_pos(Vector2 p_view_pos) const { +Vector2 CurveEdit::get_world_pos(const Vector2 &p_view_pos) const { return _world_to_view.affine_inverse().xform(p_view_pos); } // Uses non-baked points, but takes advantage of ordered iteration to be faster. -template -static void plot_curve_accurate(const Curve &curve, float step, Vector2 scaling, T plot_func) { - if (curve.get_point_count() <= 1) { - // Not enough points to make a curve, so it's just a straight line. - // The added tiny vectors make the drawn line stay exactly within the bounds in practice. - float y = curve.sample(0); - plot_func(Vector2(0, y) * scaling + Vector2(0.5, 0), Vector2(1.f, y) * scaling - Vector2(1.5, 0), true); +void CurveEdit::plot_curve_accurate(float p_step, const Color &p_line_color, const Color &p_edge_line_color) { + const real_t min_x = curve->get_min_domain(); + const real_t max_x = curve->get_max_domain(); + if (curve->get_point_count() <= 1) { // Draw single line through entire plot. + real_t y = curve->sample(0); + draw_line(get_view_pos(Vector2(min_x, y)) + Vector2(0.5, 0), get_view_pos(Vector2(max_x, y)) - Vector2(1.5, 0), p_line_color, LINE_WIDTH, true); + return; + } - } else { - Vector2 first_point = curve.get_point_position(0); - Vector2 last_point = curve.get_point_position(curve.get_point_count() - 1); - - // Edge lines - plot_func(Vector2(0, first_point.y) * scaling + Vector2(0.5, 0), first_point * scaling, false); - plot_func(Vector2(Curve::MAX_X, last_point.y) * scaling - Vector2(1.5, 0), last_point * scaling, false); - - // Draw section by section, so that we get maximum precision near points. - // It's an accurate representation, but slower than using the baked one. - for (int i = 1; i < curve.get_point_count(); ++i) { - Vector2 a = curve.get_point_position(i - 1); - Vector2 b = curve.get_point_position(i); - - Vector2 pos = a; - Vector2 prev_pos = a; - - float scaled_step = step / scaling.x; - float samples = (b.x - a.x) / scaled_step; - - for (int j = 1; j < samples; j++) { - float x = j * scaled_step; - pos.x = a.x + x; - pos.y = curve.sample_local_nocheck(i - 1, x); - plot_func(prev_pos * scaling, pos * scaling, true); - prev_pos = pos; - } + Vector2 first_point = curve->get_point_position(0); + Vector2 last_point = curve->get_point_position(curve->get_point_count() - 1); - plot_func(prev_pos * scaling, b * scaling, true); - } - } -} + // Transform pixels-per-step into curve domain. Only works for non-rotated transforms. + const float world_step_size = p_step / _world_to_view.get_scale().x; + + // Edge lines. + draw_line(get_view_pos(Vector2(min_x, first_point.y)) + Vector2(0.5, 0), get_view_pos(first_point), p_edge_line_color, LINE_WIDTH, true); + draw_line(get_view_pos(last_point), get_view_pos(Vector2(max_x, last_point.y)) - Vector2(1.5, 0), p_edge_line_color, LINE_WIDTH, true); -struct CanvasItemPlotCurve { - CanvasItem &ci; - Color color1; - Color color2; + // Draw section by section, so that we get maximum precision near points. + // It's an accurate representation, but slower than using the baked one. + for (int i = 1; i < curve->get_point_count(); ++i) { + Vector2 a = curve->get_point_position(i - 1); + Vector2 b = curve->get_point_position(i); - CanvasItemPlotCurve(CanvasItem &p_ci, Color p_color1, Color p_color2) : - ci(p_ci), - color1(p_color1), - color2(p_color2) {} + Vector2 pos = a; + Vector2 prev_pos = a; - void operator()(Vector2 pos0, Vector2 pos1, bool in_definition) { - ci.draw_line(pos0, pos1, in_definition ? color1 : color2, 0.5, true); + float samples = (b.x - a.x) / world_step_size; + + for (int j = 1; j < samples; j++) { + float x = j * world_step_size; + pos.x = a.x + x; + pos.y = curve->sample_local_nocheck(i - 1, x); + draw_line(get_view_pos(prev_pos), get_view_pos(pos), p_line_color, LINE_WIDTH, true); + prev_pos = pos; + } + + draw_line(get_view_pos(prev_pos), get_view_pos(b), p_line_color, LINE_WIDTH, true); } -}; +} void CurveEdit::_redraw() { if (curve.is_null()) { @@ -784,7 +776,7 @@ void CurveEdit::_redraw() { Vector2 view_size = get_rect().size; draw_style_box(get_theme_stylebox(SceneStringName(panel), SNAME("Tree")), Rect2(Point2(), view_size)); - // Draw snapping grid, then primary grid. + // Draw primary grid. draw_set_transform_matrix(_world_to_view); Vector2 min_edge = get_world_pos(Vector2(0, view_size.y)); @@ -794,15 +786,15 @@ void CurveEdit::_redraw() { const Color grid_color = get_theme_color(SNAME("mono_color"), EditorStringName(Editor)) * Color(1, 1, 1, 0.1); const Vector2i grid_steps = Vector2i(4, 2); - const Vector2 step_size = Vector2(1, curve->get_range()) / grid_steps; + const Vector2 step_size = Vector2(curve->get_domain_range(), curve->get_value_range()) / grid_steps; draw_line(Vector2(min_edge.x, curve->get_min_value()), Vector2(max_edge.x, curve->get_min_value()), grid_color_primary); draw_line(Vector2(max_edge.x, curve->get_max_value()), Vector2(min_edge.x, curve->get_max_value()), grid_color_primary); - draw_line(Vector2(0, min_edge.y), Vector2(0, max_edge.y), grid_color_primary); - draw_line(Vector2(1, max_edge.y), Vector2(1, min_edge.y), grid_color_primary); + draw_line(Vector2(curve->get_min_domain(), min_edge.y), Vector2(curve->get_min_domain(), max_edge.y), grid_color_primary); + draw_line(Vector2(curve->get_max_domain(), max_edge.y), Vector2(curve->get_max_domain(), min_edge.y), grid_color_primary); for (int i = 1; i < grid_steps.x; i++) { - real_t x = i * step_size.x; + real_t x = curve->get_min_domain() + i * step_size.x; draw_line(Vector2(x, min_edge.y), Vector2(x, max_edge.y), grid_color); } @@ -819,30 +811,26 @@ void CurveEdit::_redraw() { float font_height = font->get_height(font_size); Color text_color = get_theme_color(SceneStringName(font_color), EditorStringName(Editor)); + int pad = Math::round(2 * EDSCALE); + for (int i = 0; i <= grid_steps.x; ++i) { - real_t x = i * step_size.x; - draw_string(font, get_view_pos(Vector2(x - step_size.x / 2, curve->get_min_value())) + Vector2(0, font_height - Math::round(2 * EDSCALE)), String::num(x, 2), HORIZONTAL_ALIGNMENT_CENTER, get_view_pos(Vector2(step_size.x, 0)).x, font_size, text_color); + real_t x = curve->get_min_domain() + i * step_size.x; + draw_string(font, get_view_pos(Vector2(x, curve->get_min_value())) + Vector2(pad, font_height - pad), String::num(x, 2), HORIZONTAL_ALIGNMENT_CENTER, -1, font_size, text_color); } for (int i = 0; i <= grid_steps.y; ++i) { real_t y = curve->get_min_value() + i * step_size.y; - draw_string(font, get_view_pos(Vector2(0, y)) + Vector2(2, -2), String::num(y, 2), HORIZONTAL_ALIGNMENT_LEFT, -1, font_size, text_color); + draw_string(font, get_view_pos(Vector2(curve->get_min_domain(), y)) + Vector2(pad, -pad), String::num(y, 2), HORIZONTAL_ALIGNMENT_LEFT, -1, font_size, text_color); } - // Draw curve. - - // An unusual transform so we can offset the curve before scaling it up, allowing the curve to be antialiased. - // The scaling up ensures that the curve rendering doesn't break when we use a quad line to draw it. - draw_set_transform_matrix(Transform2D(0, get_view_pos(Vector2(0, 0)))); + // Draw curve in view coordinates. Curve world-to-view point conversion happens in plot_curve_accurate(). const Color line_color = get_theme_color(SceneStringName(font_color), EditorStringName(Editor)); const Color edge_line_color = get_theme_color(SceneStringName(font_color), EditorStringName(Editor)) * Color(1, 1, 1, 0.75); - CanvasItemPlotCurve plot_func(*this, line_color, edge_line_color); - plot_curve_accurate(**curve, 2.f, (get_view_pos(Vector2(1, curve->get_max_value())) - get_view_pos(Vector2(0, curve->get_min_value()))) / Vector2(1, curve->get_range()), plot_func); + plot_curve_accurate(STEP_SIZE, line_color, edge_line_color); // Draw points, except for the selected one. - draw_set_transform_matrix(Transform2D()); bool shift_pressed = Input::get_singleton()->is_key_pressed(Key::SHIFT); @@ -934,8 +922,8 @@ void CurveEdit::_redraw() { draw_set_transform_matrix(_world_to_view); if (Input::get_singleton()->is_key_pressed(Key::ALT) && grabbing != GRAB_NONE && selected_tangent_index == TANGENT_NONE) { - float prev_point_offset = (selected_index > 0) ? curve->get_point_position(selected_index - 1).x : 0.0; - float next_point_offset = (selected_index < curve->get_point_count() - 1) ? curve->get_point_position(selected_index + 1).x : 1.0; + float prev_point_offset = (selected_index > 0) ? curve->get_point_position(selected_index - 1).x : curve->get_min_domain(); + float next_point_offset = (selected_index < curve->get_point_count() - 1) ? curve->get_point_position(selected_index + 1).x : curve->get_max_domain(); draw_line(Vector2(prev_point_offset, curve->get_min_value()), Vector2(prev_point_offset, curve->get_max_value()), Color(point_color, 0.6)); draw_line(Vector2(next_point_offset, curve->get_min_value()), Vector2(next_point_offset, curve->get_max_value()), Color(point_color, 0.6)); @@ -943,7 +931,7 @@ void CurveEdit::_redraw() { if (shift_pressed && grabbing != GRAB_NONE && selected_tangent_index == TANGENT_NONE) { draw_line(Vector2(initial_grab_pos.x, curve->get_min_value()), Vector2(initial_grab_pos.x, curve->get_max_value()), get_theme_color(SNAME("axis_x_color"), EditorStringName(Editor)).darkened(0.4)); - draw_line(Vector2(0, initial_grab_pos.y), Vector2(1, initial_grab_pos.y), get_theme_color(SNAME("axis_y_color"), EditorStringName(Editor)).darkened(0.4)); + draw_line(Vector2(curve->get_min_domain(), initial_grab_pos.y), Vector2(curve->get_max_domain(), initial_grab_pos.y), get_theme_color(SNAME("axis_y_color"), EditorStringName(Editor)).darkened(0.4)); } } @@ -1079,15 +1067,15 @@ Ref CurvePreviewGenerator::generate(const Ref &p_from, cons Color line_color = EditorInterface::get_singleton()->get_editor_theme()->get_color(SceneStringName(font_color), EditorStringName(Editor)); // Set the first pixel of the thumbnail. - float v = (curve->sample_baked(0) - curve->get_min_value()) / curve->get_range(); + float v = (curve->sample_baked(curve->get_min_domain()) - curve->get_min_value()) / curve->get_value_range(); int y = CLAMP(im.get_height() - v * im.get_height(), 0, im.get_height() - 1); im.set_pixel(0, y, line_color); // Plot a line towards the next point. int prev_y = y; for (int x = 1; x < im.get_width(); ++x) { - float t = static_cast(x) / im.get_width(); - v = (curve->sample_baked(t) - curve->get_min_value()) / curve->get_range(); + float t = static_cast(x) / im.get_width() * curve->get_domain_range() + curve->get_min_domain(); + v = (curve->sample_baked(t) - curve->get_min_value()) / curve->get_value_range(); y = CLAMP(im.get_height() - v * im.get_height(), 0, im.get_height() - 1); Vector points = Geometry2D::bresenham_line(Point2i(x - 1, prev_y), Point2i(x, y)); diff --git a/editor/plugins/curve_editor_plugin.h b/editor/plugins/curve_editor_plugin.h index c844f420296..8fc87ee04e3 100644 --- a/editor/plugins/curve_editor_plugin.h +++ b/editor/plugins/curve_editor_plugin.h @@ -78,14 +78,14 @@ class CurveEdit : public Control { virtual void gui_input(const Ref &p_event) override; void _curve_changed(); - int get_point_at(Vector2 p_pos) const; - TangentIndex get_tangent_at(Vector2 p_pos) const; + int get_point_at(const Vector2 &p_pos) const; + TangentIndex get_tangent_at(const Vector2 &p_pos) const; float get_offset_without_collision(int p_current_index, float p_offset, bool p_prioritize_right = true); - void add_point(Vector2 p_pos); + void add_point(const Vector2 &p_pos); void remove_point(int p_index); - void set_point_position(int p_index, Vector2 p_pos); + void set_point_position(int p_index, const Vector2 &p_pos); void set_point_tangents(int p_index, float p_left, float p_right); void set_point_left_tangent(int p_index, float p_tangent); @@ -94,17 +94,20 @@ class CurveEdit : public Control { void update_view_transform(); + void plot_curve_accurate(float p_step, const Color &p_line_color, const Color &p_edge_line_color); + void set_selected_index(int p_index); - void set_selected_tangent_index(TangentIndex p_tangent); Vector2 get_tangent_view_pos(int p_index, TangentIndex p_tangent) const; - Vector2 get_view_pos(Vector2 p_world_pos) const; - Vector2 get_world_pos(Vector2 p_view_pos) const; + Vector2 get_view_pos(const Vector2 &p_world_pos) const; + Vector2 get_world_pos(const Vector2 &p_view_pos) const; void _redraw(); private: const float ASPECT_RATIO = 6.f / 13.f; + const float LINE_WIDTH = 0.5f; + const int STEP_SIZE = 2; // Number of pixels between plot points. Transform2D _world_to_view; @@ -136,9 +139,9 @@ class CurveEdit : public Control { }; GrabMode grabbing = GRAB_NONE; Vector2 initial_grab_pos; - int initial_grab_index; - float initial_grab_left_tangent; - float initial_grab_right_tangent; + int initial_grab_index = -1; + float initial_grab_left_tangent = 0; + float initial_grab_right_tangent = 0; bool snap_enabled = false; int snap_count = 10; diff --git a/scene/resources/curve.cpp b/scene/resources/curve.cpp index 8926eb1d511..67bd1ef5775 100644 --- a/scene/resources/curve.cpp +++ b/scene/resources/curve.cpp @@ -33,6 +33,7 @@ #include "core/math/math_funcs.h" const char *Curve::SIGNAL_RANGE_CHANGED = "range_changed"; +const char *Curve::SIGNAL_DOMAIN_CHANGED = "domain_changed"; Curve::Curve() { } @@ -56,14 +57,11 @@ void Curve::set_point_count(int p_count) { } int Curve::_add_point(Vector2 p_position, real_t p_left_tangent, real_t p_right_tangent, TangentMode p_left_mode, TangentMode p_right_mode) { - // Add a point and preserve order + // Add a point and preserve order. - // Curve bounds is in 0..1 - if (p_position.x > MAX_X) { - p_position.x = MAX_X; - } else if (p_position.x < MIN_X) { - p_position.x = MIN_X; - } + // Points must remain within the given value and domain ranges. + p_position.x = CLAMP(p_position.x, _min_domain, _max_domain); + p_position.y = CLAMP(p_position.y, _min_value, _max_value); int ret = -1; @@ -88,11 +86,11 @@ int Curve::_add_point(Vector2 p_position, real_t p_left_tangent, real_t p_right_ int i = get_index(p_position.x); if (i == 0 && p_position.x < _points[0].position.x) { - // Insert before anything else + // Insert before anything else. _points.insert(0, Point(p_position, p_left_tangent, p_right_tangent, p_left_mode, p_right_mode)); ret = 0; } else { - // Insert between i and i+1 + // Insert between i and i+1. ++i; _points.insert(i, Point(p_position, p_left_tangent, p_right_tangent, p_left_mode, p_right_mode)); ret = i; @@ -121,7 +119,7 @@ int Curve::add_point_no_update(Vector2 p_position, real_t p_left_tangent, real_t } int Curve::get_index(real_t p_offset) const { - // Lower-bound float binary search + // Lower-bound float binary search. int imin = 0; int imax = _points.size() - 1; @@ -134,16 +132,14 @@ int Curve::get_index(real_t p_offset) const { if (a < p_offset && b < p_offset) { imin = m; - } else if (a > p_offset) { imax = m; - } else { return m; } } - // Will happen if the offset is out of bounds + // Will happen if the offset is out of bounds. if (p_offset > _points[imax].position.x) { return imax; } @@ -305,30 +301,80 @@ void Curve::update_auto_tangents(int p_index) { } } +#define MIN_X_RANGE 0.01 #define MIN_Y_RANGE 0.01 +Array Curve::get_limits() const { + Array output; + output.resize(4); + + output[0] = _min_value; + output[1] = _max_value; + output[2] = _min_domain; + output[3] = _max_domain; + + return output; +} + +void Curve::set_limits(const Array &p_input) { + if (p_input.size() != 4) { + WARN_PRINT_ED(vformat(R"(Could not find Curve limit values when deserializing "%s". Resetting limits to default values.)", this->get_path())); + _min_value = 0; + _max_value = 1; + _min_domain = 0; + _max_domain = 1; + return; + } + + // Do not use setters because we don't want to enforce their logical constraints during deserialization. + _min_value = p_input[0]; + _max_value = p_input[1]; + _min_domain = p_input[2]; + _max_domain = p_input[3]; +} + void Curve::set_min_value(real_t p_min) { - if (_minmax_set_once & 0b11 && p_min > _max_value - MIN_Y_RANGE) { - _min_value = _max_value - MIN_Y_RANGE; - } else { - _minmax_set_once |= 0b10; // first bit is "min set" - _min_value = p_min; + _min_value = MIN(p_min, _max_value - MIN_Y_RANGE); + + for (const Point &p : _points) { + _min_value = MIN(_min_value, p.position.y); } - // Note: min and max are indicative values, - // it's still possible that existing points are out of range at this point. + emit_signal(SNAME(SIGNAL_RANGE_CHANGED)); } void Curve::set_max_value(real_t p_max) { - if (_minmax_set_once & 0b11 && p_max < _min_value + MIN_Y_RANGE) { - _max_value = _min_value + MIN_Y_RANGE; - } else { - _minmax_set_once |= 0b01; // second bit is "max set" - _max_value = p_max; + _max_value = MAX(p_max, _min_value + MIN_Y_RANGE); + + for (const Point &p : _points) { + _max_value = MAX(_max_value, p.position.y); } + emit_signal(SNAME(SIGNAL_RANGE_CHANGED)); } +void Curve::set_min_domain(real_t p_min) { + _min_domain = MIN(p_min, _max_domain - MIN_X_RANGE); + + if (_points.size() > 0 && _min_domain > _points[0].position.x) { + _min_domain = _points[0].position.x; + } + + mark_dirty(); + emit_signal(SNAME(SIGNAL_DOMAIN_CHANGED)); +} + +void Curve::set_max_domain(real_t p_max) { + _max_domain = MAX(p_max, _min_domain + MIN_X_RANGE); + + if (_points.size() > 0 && _max_domain < _points[_points.size() - 1].position.x) { + _max_domain = _points[_points.size() - 1].position.x; + } + + mark_dirty(); + emit_signal(SNAME(SIGNAL_DOMAIN_CHANGED)); +} + real_t Curve::sample(real_t p_offset) const { if (_points.size() == 0) { return 0; @@ -370,7 +416,7 @@ real_t Curve::sample_local_nocheck(int p_index, real_t p_local_offset) const { * d1 == d2 == d3 == d / 3 */ - // Control points are chosen at equal distances + // Control points are chosen at equal distances. real_t d = b.position.x - a.position.x; if (Math::is_zero_approx(d)) { return b.position.y; @@ -458,7 +504,7 @@ void Curve::bake() { _baked_cache.resize(_bake_resolution); for (int i = 1; i < _bake_resolution - 1; ++i) { - real_t x = i / static_cast(_bake_resolution - 1); + real_t x = get_domain_range() * i / static_cast(_bake_resolution - 1) + _min_domain; real_t y = sample(x); _baked_cache.write[i] = y; } @@ -480,11 +526,11 @@ void Curve::set_bake_resolution(int p_resolution) { real_t Curve::sample_baked(real_t p_offset) const { if (_baked_cache_dirty) { - // Last-second bake if not done already + // Last-second bake if not done already. const_cast(this)->bake(); } - // Special cases if the cache is too small + // Special cases if the cache is too small. if (_baked_cache.size() == 0) { if (_points.size() == 0) { return 0; @@ -494,8 +540,8 @@ real_t Curve::sample_baked(real_t p_offset) const { return _baked_cache[0]; } - // Get interpolation index - real_t fi = p_offset * (_baked_cache.size() - 1); + // Get interpolation index. + real_t fi = (p_offset - _min_domain) / get_domain_range() * (_baked_cache.size() - 1); int i = Math::floor(fi); if (i < 0) { i = 0; @@ -505,7 +551,7 @@ real_t Curve::sample_baked(real_t p_offset) const { fi = 0; } - // Sample + // Sample. if (i + 1 < _baked_cache.size()) { real_t t = fi - i; return Math::lerp(_baked_cache[i], _baked_cache[i + 1], t); @@ -628,6 +674,14 @@ void Curve::_bind_methods() { ClassDB::bind_method(D_METHOD("set_min_value", "min"), &Curve::set_min_value); ClassDB::bind_method(D_METHOD("get_max_value"), &Curve::get_max_value); ClassDB::bind_method(D_METHOD("set_max_value", "max"), &Curve::set_max_value); + ClassDB::bind_method(D_METHOD("get_value_range"), &Curve::get_value_range); + ClassDB::bind_method(D_METHOD("get_min_domain"), &Curve::get_min_domain); + ClassDB::bind_method(D_METHOD("set_min_domain", "min"), &Curve::set_min_domain); + ClassDB::bind_method(D_METHOD("get_max_domain"), &Curve::get_max_domain); + ClassDB::bind_method(D_METHOD("set_max_domain", "max"), &Curve::set_max_domain); + ClassDB::bind_method(D_METHOD("get_domain_range"), &Curve::get_domain_range); + ClassDB::bind_method(D_METHOD("_get_limits"), &Curve::get_limits); + ClassDB::bind_method(D_METHOD("_set_limits", "data"), &Curve::set_limits); ClassDB::bind_method(D_METHOD("clean_dupes"), &Curve::clean_dupes); ClassDB::bind_method(D_METHOD("bake"), &Curve::bake); ClassDB::bind_method(D_METHOD("get_bake_resolution"), &Curve::get_bake_resolution); @@ -635,13 +689,17 @@ void Curve::_bind_methods() { ClassDB::bind_method(D_METHOD("_get_data"), &Curve::get_data); ClassDB::bind_method(D_METHOD("_set_data", "data"), &Curve::set_data); - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "min_value", PROPERTY_HINT_RANGE, "-1024,1024,0.01"), "set_min_value", "get_min_value"); - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "max_value", PROPERTY_HINT_RANGE, "-1024,1024,0.01"), "set_max_value", "get_max_value"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "min_domain", PROPERTY_HINT_RANGE, "-1024,1024,0.01,or_greater,or_less", PROPERTY_USAGE_EDITOR), "set_min_domain", "get_min_domain"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "max_domain", PROPERTY_HINT_RANGE, "-1024,1024,0.01,or_greater,or_less", PROPERTY_USAGE_EDITOR), "set_max_domain", "get_max_domain"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "min_value", PROPERTY_HINT_RANGE, "-1024,1024,0.01,or_greater,or_less", PROPERTY_USAGE_EDITOR), "set_min_value", "get_min_value"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "max_value", PROPERTY_HINT_RANGE, "-1024,1024,0.01,or_greater,or_less", PROPERTY_USAGE_EDITOR), "set_max_value", "get_max_value"); + ADD_PROPERTY(PropertyInfo(Variant::NIL, "_limits", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_INTERNAL), "_set_limits", "_get_limits"); ADD_PROPERTY(PropertyInfo(Variant::INT, "bake_resolution", PROPERTY_HINT_RANGE, "1,1000,1"), "set_bake_resolution", "get_bake_resolution"); ADD_PROPERTY(PropertyInfo(Variant::INT, "_data", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_INTERNAL), "_set_data", "_get_data"); ADD_ARRAY_COUNT("Points", "point_count", "set_point_count", "get_point_count", "point_"); ADD_SIGNAL(MethodInfo(SIGNAL_RANGE_CHANGED)); + ADD_SIGNAL(MethodInfo(SIGNAL_DOMAIN_CHANGED)); BIND_ENUM_CONSTANT(TANGENT_FREE); BIND_ENUM_CONSTANT(TANGENT_LINEAR); @@ -852,7 +910,7 @@ void Curve2D::_bake() const { return; } - // Tessellate curve to (almost) even length segments + // Tessellate curve to (almost) even length segments. { Vector> midpoints = _tessellate_even_length(10, bake_interval); @@ -1592,7 +1650,7 @@ void Curve3D::_bake() const { return; } - // Step 1: Tessellate curve to (almost) even length segments + // Step 1: Tessellate curve to (almost) even length segments. { Vector> midpoints = _tessellate_even_length(10, bake_interval); @@ -1653,7 +1711,7 @@ void Curve3D::_bake() const { return; } - // Step 2: Calculate the up vectors and the whole local reference frame + // Step 2: Calculate the up vectors and the whole local reference frame. // // See Dougan, Carl. "The parallel transport frame." Game Programming Gems 2 (2001): 215-219. // for an example discussing about why not the Frenet frame. @@ -1689,7 +1747,7 @@ void Curve3D::_bake() const { Basis rotate; rotate.rotate_to_align(-frame_prev.get_column(2), forward); frame = rotate * frame_prev; - frame.orthonormalize(); // guard against float error accumulation + frame.orthonormalize(); // Guard against float error accumulation. up_write[idx] = frame.get_column(1); frame_prev = frame; @@ -1941,7 +1999,7 @@ real_t Curve3D::sample_baked_tilt(real_t p_offset) const { return baked_tilt_cache.get(0); } - p_offset = CLAMP(p_offset, 0.0, get_baked_length()); // PathFollower implement wrapping logic + p_offset = CLAMP(p_offset, 0.0, get_baked_length()); // PathFollower implement wrapping logic. Curve3D::Interval interval = _find_interval(p_offset); return _sample_baked_tilt(interval); diff --git a/scene/resources/curve.h b/scene/resources/curve.h index 6da337a93f8..7a57a96f282 100644 --- a/scene/resources/curve.h +++ b/scene/resources/curve.h @@ -38,10 +38,8 @@ class Curve : public Resource { GDCLASS(Curve, Resource); public: - static const int MIN_X = 0.f; - static const int MAX_X = 1.f; - static const char *SIGNAL_RANGE_CHANGED; + static const char *SIGNAL_DOMAIN_CHANGED; enum TangentMode { TANGENT_FREE = 0, @@ -101,11 +99,18 @@ class Curve : public Resource { real_t get_min_value() const { return _min_value; } void set_min_value(real_t p_min); - real_t get_max_value() const { return _max_value; } void set_max_value(real_t p_max); + real_t get_value_range() const { return _max_value - _min_value; } + + real_t get_min_domain() const { return _min_domain; } + void set_min_domain(real_t p_min); + real_t get_max_domain() const { return _max_domain; } + void set_max_domain(real_t p_max); + real_t get_domain_range() const { return _max_domain - _min_domain; } - real_t get_range() const { return _max_value - _min_value; } + Array get_limits() const; + void set_limits(const Array &p_input); real_t sample(real_t p_offset) const; real_t sample_local_nocheck(int p_index, real_t p_local_offset) const; @@ -156,7 +161,8 @@ class Curve : public Resource { int _bake_resolution = 100; real_t _min_value = 0.0; real_t _max_value = 1.0; - int _minmax_set_once = 0b00; // Encodes whether min and max have been set a first time, first bit for min and second for max. + real_t _min_domain = 0.0; + real_t _max_domain = 1.0; }; VARIANT_ENUM_CAST(Curve::TangentMode) diff --git a/tests/scene/test_curve.h b/tests/scene/test_curve.h index d67550f9f72..4c179cc59a6 100644 --- a/tests/scene/test_curve.h +++ b/tests/scene/test_curve.h @@ -55,7 +55,7 @@ TEST_CASE("[Curve] Default curve") { "Default curve should return the expected value at offset 1.0."); } -TEST_CASE("[Curve] Custom curve with free tangents") { +TEST_CASE("[Curve] Custom unit curve with free tangents") { Ref curve = memnew(Curve); // "Sawtooth" curve with an open ending towards the 1.0 offset. curve->add_point(Vector2(0, 0)); @@ -136,7 +136,90 @@ TEST_CASE("[Curve] Custom curve with free tangents") { "Custom free curve should return the expected baked value at offset 0.6 after clearing all points."); } -TEST_CASE("[Curve] Custom curve with linear tangents") { +TEST_CASE("[Curve] Custom non-unit curve with free tangents") { + Ref curve = memnew(Curve); + curve->set_min_domain(-100.0); + curve->set_max_domain(100.0); + // "Sawtooth" curve with an open ending towards the 100 offset. + curve->add_point(Vector2(-100, 0)); + curve->add_point(Vector2(-50, 1)); + curve->add_point(Vector2(0, 0)); + curve->add_point(Vector2(50, 1)); + curve->set_bake_resolution(11); + + CHECK_MESSAGE( + Math::is_zero_approx(curve->get_point_left_tangent(0)), + "get_point_left_tangent() should return the expected value for point index 0."); + CHECK_MESSAGE( + Math::is_zero_approx(curve->get_point_right_tangent(0)), + "get_point_right_tangent() should return the expected value for point index 0."); + CHECK_MESSAGE( + curve->get_point_left_mode(0) == Curve::TangentMode::TANGENT_FREE, + "get_point_left_mode() should return the expected value for point index 0."); + CHECK_MESSAGE( + curve->get_point_right_mode(0) == Curve::TangentMode::TANGENT_FREE, + "get_point_right_mode() should return the expected value for point index 0."); + + CHECK_MESSAGE( + curve->get_point_count() == 4, + "Custom free curve should contain the expected number of points."); + + CHECK_MESSAGE( + Math::is_zero_approx(curve->sample(-200)), + "Custom free curve should return the expected value at offset -200."); + CHECK_MESSAGE( + curve->sample(0.1 * curve->get_domain_range() + curve->get_min_domain()) == doctest::Approx((real_t)0.352), + "Custom free curve should return the expected value at offset equivalent to a unit curve's 0.1."); + CHECK_MESSAGE( + curve->sample(0.4 * curve->get_domain_range() + curve->get_min_domain()) == doctest::Approx((real_t)0.352), + "Custom free curve should return the expected value at offset equivalent to a unit curve's 0.4."); + CHECK_MESSAGE( + curve->sample(0.7 * curve->get_domain_range() + curve->get_min_domain()) == doctest::Approx((real_t)0.896), + "Custom free curve should return the expected value at offset equivalent to a unit curve's 0.7."); + CHECK_MESSAGE( + curve->sample(1 * curve->get_domain_range() + curve->get_min_domain()) == doctest::Approx(1), + "Custom free curve should return the expected value at offset equivalent to a unit curve's 1."); + CHECK_MESSAGE( + curve->sample(2 * curve->get_domain_range() + curve->get_min_domain()) == doctest::Approx(1), + "Custom free curve should return the expected value at offset equivalent to a unit curve's 2."); + + CHECK_MESSAGE( + Math::is_zero_approx(curve->sample_baked(-200)), + "Custom free curve should return the expected baked value at offset equivalent to a unit curve's -0.1."); + CHECK_MESSAGE( + curve->sample_baked(0.1 * curve->get_domain_range() + curve->get_min_domain()) == doctest::Approx((real_t)0.352), + "Custom free curve should return the expected baked value at offset equivalent to a unit curve's 0.1."); + CHECK_MESSAGE( + curve->sample_baked(0.4 * curve->get_domain_range() + curve->get_min_domain()) == doctest::Approx((real_t)0.352), + "Custom free curve should return the expected baked value at offset equivalent to a unit curve's 0.4."); + CHECK_MESSAGE( + curve->sample_baked(0.7 * curve->get_domain_range() + curve->get_min_domain()) == doctest::Approx((real_t)0.896), + "Custom free curve should return the expected baked value at offset equivalent to a unit curve's 0.7."); + CHECK_MESSAGE( + curve->sample_baked(1 * curve->get_domain_range() + curve->get_min_domain()) == doctest::Approx(1), + "Custom free curve should return the expected baked value at offset equivalent to a unit curve's 1."); + CHECK_MESSAGE( + curve->sample_baked(2 * curve->get_domain_range() + curve->get_min_domain()) == doctest::Approx(1), + "Custom free curve should return the expected baked value at offset equivalent to a unit curve's 2."); + + curve->remove_point(1); + CHECK_MESSAGE( + curve->sample(0.1 * curve->get_domain_range() + curve->get_min_domain()) == doctest::Approx(0), + "Custom free curve should return the expected value at offset equivalent to a unit curve's 0.1 after removing point at index 1."); + CHECK_MESSAGE( + curve->sample_baked(0.1 * curve->get_domain_range() + curve->get_min_domain()) == doctest::Approx(0), + "Custom free curve should return the expected baked value at offset equivalent to a unit curve's 0.1 after removing point at index 1."); + + curve->clear_points(); + CHECK_MESSAGE( + curve->sample(0.6 * curve->get_domain_range() + curve->get_min_domain()) == doctest::Approx(0), + "Custom free curve should return the expected value at offset 0.6 after clearing all points."); + CHECK_MESSAGE( + curve->sample_baked(0.6 * curve->get_domain_range() + curve->get_min_domain()) == doctest::Approx(0), + "Custom free curve should return the expected baked value at offset 0.6 after clearing all points."); +} + +TEST_CASE("[Curve] Custom unit curve with linear tangents") { Ref curve = memnew(Curve); // "Sawtooth" curve with an open ending towards the 1.0 offset. curve->add_point(Vector2(0, 0), 0, 0, Curve::TangentMode::TANGENT_LINEAR, Curve::TangentMode::TANGENT_LINEAR); @@ -219,6 +302,91 @@ TEST_CASE("[Curve] Custom curve with linear tangents") { "Custom free curve should return the expected baked value at offset 0.7 after removing point at invalid index 10."); } +TEST_CASE("[Curve] Custom non-unit curve with linear tangents") { + Ref curve = memnew(Curve); + curve->set_min_domain(-100.0); + curve->set_max_domain(100.0); + // "Sawtooth" curve with an open ending towards the 100 offset. + curve->add_point(Vector2(-100, 0), 0, 0, Curve::TangentMode::TANGENT_LINEAR, Curve::TangentMode::TANGENT_LINEAR); + curve->add_point(Vector2(-50, 1), 0, 0, Curve::TangentMode::TANGENT_LINEAR, Curve::TangentMode::TANGENT_LINEAR); + curve->add_point(Vector2(0, 0), 0, 0, Curve::TangentMode::TANGENT_LINEAR, Curve::TangentMode::TANGENT_LINEAR); + curve->add_point(Vector2(50, 1), 0, 0, Curve::TangentMode::TANGENT_LINEAR, Curve::TangentMode::TANGENT_LINEAR); + + CHECK_MESSAGE( + curve->get_point_left_tangent(3) == doctest::Approx(1.f / 50), + "get_point_left_tangent() should return the expected value for point index 3."); + CHECK_MESSAGE( + Math::is_zero_approx(curve->get_point_right_tangent(3)), + "get_point_right_tangent() should return the expected value for point index 3."); + CHECK_MESSAGE( + curve->get_point_left_mode(3) == Curve::TangentMode::TANGENT_LINEAR, + "get_point_left_mode() should return the expected value for point index 3."); + CHECK_MESSAGE( + curve->get_point_right_mode(3) == Curve::TangentMode::TANGENT_LINEAR, + "get_point_right_mode() should return the expected value for point index 3."); + + ERR_PRINT_OFF; + CHECK_MESSAGE( + Math::is_zero_approx(curve->get_point_right_tangent(300)), + "get_point_right_tangent() should return the expected value for invalid point index 300."); + CHECK_MESSAGE( + curve->get_point_left_mode(-12345) == Curve::TangentMode::TANGENT_FREE, + "get_point_left_mode() should return the expected value for invalid point index -12345."); + ERR_PRINT_ON; + + CHECK_MESSAGE( + curve->get_point_count() == 4, + "Custom linear unit curve should contain the expected number of points."); + + CHECK_MESSAGE( + Math::is_zero_approx(curve->sample(-0.1 * curve->get_domain_range() + curve->get_min_domain())), + "Custom linear curve should return the expected value at offset equivalent to a unit curve's -0.1."); + CHECK_MESSAGE( + curve->sample(0.1 * curve->get_domain_range() + curve->get_min_domain()) == doctest::Approx((real_t)0.4), + "Custom linear curve should return the expected value at offset equivalent to a unit curve's 0.1."); + CHECK_MESSAGE( + curve->sample(0.4 * curve->get_domain_range() + curve->get_min_domain()) == doctest::Approx((real_t)0.4), + "Custom linear curve should return the expected value at offset equivalent to a unit curve's 0.4."); + CHECK_MESSAGE( + curve->sample(0.7 * curve->get_domain_range() + curve->get_min_domain()) == doctest::Approx((real_t)0.8), + "Custom linear curve should return the expected value at offset equivalent to a unit curve's 0.7."); + CHECK_MESSAGE( + curve->sample(1 * curve->get_domain_range() + curve->get_min_domain()) == doctest::Approx(1), + "Custom linear curve should return the expected value at offset equivalent to a unit curve's 1.0."); + CHECK_MESSAGE( + curve->sample(2 * curve->get_domain_range() + curve->get_min_domain()) == doctest::Approx(1), + "Custom linear curve should return the expected value at offset equivalent to a unit curve's 2.0."); + + CHECK_MESSAGE( + Math::is_zero_approx(curve->sample_baked(-0.1 * curve->get_domain_range() + curve->get_min_domain())), + "Custom linear curve should return the expected baked value at offset equivalent to a unit curve's -0.1."); + CHECK_MESSAGE( + curve->sample_baked(0.1 * curve->get_domain_range() + curve->get_min_domain()) == doctest::Approx((real_t)0.4), + "Custom linear curve should return the expected baked value at offset equivalent to a unit curve's 0.1."); + CHECK_MESSAGE( + curve->sample_baked(0.4 * curve->get_domain_range() + curve->get_min_domain()) == doctest::Approx((real_t)0.4), + "Custom linear curve should return the expected baked value at offset equivalent to a unit curve's 0.4."); + CHECK_MESSAGE( + curve->sample_baked(0.7 * curve->get_domain_range() + curve->get_min_domain()) == doctest::Approx((real_t)0.8), + "Custom linear curve should return the expected baked value at offset equivalent to a unit curve's 0.7."); + CHECK_MESSAGE( + curve->sample_baked(1 * curve->get_domain_range() + curve->get_min_domain()) == doctest::Approx(1), + "Custom linear curve should return the expected baked value at offset equivalent to a unit curve's 1.0."); + CHECK_MESSAGE( + curve->sample_baked(2 * curve->get_domain_range() + curve->get_min_domain()) == doctest::Approx(1), + "Custom linear curve should return the expected baked value at offset equivalent to a unit curve's 2.0."); + + ERR_PRINT_OFF; + curve->remove_point(10); + ERR_PRINT_ON; + CHECK_MESSAGE( + curve->sample(0.7 * curve->get_domain_range() + curve->get_min_domain()) == doctest::Approx((real_t)0.8), + "Custom free curve should return the expected value at offset equivalent to a unit curve's 0.7 after removing point at invalid index 10."); + CHECK_MESSAGE( + curve->sample_baked(0.7 * curve->get_domain_range() + curve->get_min_domain()) == doctest::Approx((real_t)0.8), + "Custom free curve should return the expected baked value at offset equivalent to a unit curve's 0.7 after removing point at invalid index 10."); +} + TEST_CASE("[Curve] Straight line offset test") { Ref curve = memnew(Curve); curve->add_point(Vector2(0, 0)); From dafba55c69a562844099b144a6eac5010e053d48 Mon Sep 17 00:00:00 2001 From: Hugo Locurcio Date: Sat, 2 Nov 2024 18:25:17 +0100 Subject: [PATCH 017/151] Don't tint editor bottom panel icons when hovered or pressed This prevents the error/warning icons from turning gray or green, making them hard to recognize. A similar mechanism is already used for EditorLog filter button icons. This also fixes typos in FileDialog theme color assignment (`icon_color_pressed` instead of `icon_pressed_color`). The exposed theme item names remain the same. --- editor/editor_log.h | 2 -- editor/themes/editor_theme_manager.cpp | 7 +++++++ scene/gui/file_dialog.cpp | 4 ++-- 3 files changed, 9 insertions(+), 4 deletions(-) diff --git a/editor/editor_log.h b/editor/editor_log.h index 899b4a9ac49..b6ed372734f 100644 --- a/editor/editor_log.h +++ b/editor/editor_log.h @@ -98,8 +98,6 @@ class EditorLog : public HBoxContainer { toggle_button->set_pressed(true); toggle_button->set_text(itos(message_count)); toggle_button->set_tooltip_text(TTR(p_tooltip)); - // Don't tint the icon even when in "pressed" state. - toggle_button->add_theme_color_override("icon_color_pressed", Color(1, 1, 1, 1)); toggle_button->set_focus_mode(FOCUS_NONE); // When toggled call the callback and pass the MessageType this button is for. toggle_button->connect(SceneStringName(toggled), p_toggled_callback.bind(type)); diff --git a/editor/themes/editor_theme_manager.cpp b/editor/themes/editor_theme_manager.cpp index 32079f3753f..2461f0df2f4 100644 --- a/editor/themes/editor_theme_manager.cpp +++ b/editor/themes/editor_theme_manager.cpp @@ -1901,6 +1901,12 @@ void EditorThemeManager::_populate_editor_styles(const Ref &p_theme p_theme->set_stylebox(SceneStringName(pressed), "BottomPanelButton", menu_transparent_style); p_theme->set_stylebox("hover_pressed", "BottomPanelButton", main_screen_button_hover); p_theme->set_stylebox("hover", "BottomPanelButton", main_screen_button_hover); + // Don't tint the icon even when in "pressed" state. + p_theme->set_color("icon_pressed_color", "BottomPanelButton", Color(1, 1, 1, 1)); + Color icon_hover_color = p_config.icon_normal_color * (p_config.dark_theme ? 1.15 : 1.0); + icon_hover_color.a = 1.0; + p_theme->set_color("icon_hover_color", "BottomPanelButton", icon_hover_color); + p_theme->set_color("icon_hover_pressed_color", "BottomPanelButton", icon_hover_color); } // Editor GUI widgets. @@ -1998,6 +2004,7 @@ void EditorThemeManager::_populate_editor_styles(const Ref &p_theme Color icon_hover_color = p_config.icon_normal_color * (p_config.dark_theme ? 1.15 : 1.0); icon_hover_color.a = 1.0; p_theme->set_color("icon_hover_color", "EditorLogFilterButton", icon_hover_color); + p_theme->set_color("icon_hover_pressed_color", "EditorLogFilterButton", icon_hover_color); // When pressed, add a small bottom border to the buttons to better show their active state, // similar to active tabs. diff --git a/scene/gui/file_dialog.cpp b/scene/gui/file_dialog.cpp index 18864b12892..c905c209a0f 100644 --- a/scene/gui/file_dialog.cpp +++ b/scene/gui/file_dialog.cpp @@ -236,14 +236,14 @@ void FileDialog::_notification(int p_what) { dir_prev->add_theme_color_override("icon_normal_color", theme_cache.icon_normal_color); dir_prev->add_theme_color_override("icon_hover_color", theme_cache.icon_hover_color); dir_prev->add_theme_color_override("icon_focus_color", theme_cache.icon_focus_color); - dir_prev->add_theme_color_override("icon_color_pressed", theme_cache.icon_pressed_color); + dir_prev->add_theme_color_override("icon_pressed_color", theme_cache.icon_pressed_color); dir_prev->end_bulk_theme_override(); dir_next->begin_bulk_theme_override(); dir_next->add_theme_color_override("icon_normal_color", theme_cache.icon_normal_color); dir_next->add_theme_color_override("icon_hover_color", theme_cache.icon_hover_color); dir_next->add_theme_color_override("icon_focus_color", theme_cache.icon_focus_color); - dir_next->add_theme_color_override("icon_color_pressed", theme_cache.icon_pressed_color); + dir_next->add_theme_color_override("icon_pressed_color", theme_cache.icon_pressed_color); dir_next->end_bulk_theme_override(); refresh->begin_bulk_theme_override(); From 109bee08dd093e065abcdd888c37ff8f47735eb5 Mon Sep 17 00:00:00 2001 From: Aaron Franke Date: Wed, 30 Oct 2024 02:57:55 -0700 Subject: [PATCH 018/151] Blend file import: Don't keep original files when not unpacking them --- .../editor/editor_scene_importer_blend.cpp | 19 +++++++------------ 1 file changed, 7 insertions(+), 12 deletions(-) diff --git a/modules/gltf/editor/editor_scene_importer_blend.cpp b/modules/gltf/editor/editor_scene_importer_blend.cpp index 2db46adef41..a36d3c38ac3 100644 --- a/modules/gltf/editor/editor_scene_importer_blend.cpp +++ b/modules/gltf/editor/editor_scene_importer_blend.cpp @@ -143,6 +143,10 @@ Node *EditorSceneFormatImporterBlend::import_scene(const String &p_path, uint32_ const String sink = ProjectSettings::get_singleton()->get_imported_files_path().path_join( vformat("%s-%s.gltf", blend_basename, p_path.md5_text())); const String sink_global = ProjectSettings::get_singleton()->globalize_path(sink); + // If true, unpack the original images to the Godot file system and use them. Allows changing image import settings like VRAM compression. + // If false, allow Blender to convert the original images, such as re-packing roughness and metallic into one roughness+metallic texture. + // In most cases this is desired, but if the .blend file's images are not in the correct format, this must be disabled for correct behavior. + const bool unpack_original_images = p_options.has(SNAME("blender/materials/unpack_enabled")) && p_options[SNAME("blender/materials/unpack_enabled")]; // Handle configuration options. @@ -150,7 +154,7 @@ Node *EditorSceneFormatImporterBlend::import_scene(const String &p_path, uint32_ Dictionary parameters_map; parameters_map["filepath"] = sink_global; - parameters_map["export_keep_originals"] = true; + parameters_map["export_keep_originals"] = unpack_original_images; parameters_map["export_format"] = "GLTF_SEPARATE"; parameters_map["export_yup"] = true; @@ -285,12 +289,7 @@ Node *EditorSceneFormatImporterBlend::import_scene(const String &p_path, uint32_ parameters_map["export_apply"] = false; } - if (p_options.has(SNAME("blender/materials/unpack_enabled")) && p_options[SNAME("blender/materials/unpack_enabled")]) { - request_options["unpack_all"] = true; - } else { - request_options["unpack_all"] = false; - } - + request_options["unpack_all"] = unpack_original_images; request_options["path"] = source_global; request_options["gltf_options"] = parameters_map; @@ -311,17 +310,13 @@ Node *EditorSceneFormatImporterBlend::import_scene(const String &p_path, uint32_ Ref state; state.instantiate(); - String base_dir; - if (p_options.has(SNAME("blender/materials/unpack_enabled")) && p_options[SNAME("blender/materials/unpack_enabled")]) { - base_dir = sink.get_base_dir(); - } if (p_options.has(SNAME("nodes/import_as_skeleton_bones")) ? (bool)p_options[SNAME("nodes/import_as_skeleton_bones")] : false) { state->set_import_as_skeleton_bones(true); } state->set_scene_name(blend_basename); state->set_extract_path(p_path.get_base_dir()); state->set_extract_prefix(blend_basename); - err = gltf->append_from_file(sink.get_basename() + ".gltf", state, p_flags, base_dir); + err = gltf->append_from_file(sink.get_basename() + ".gltf", state, p_flags, sink.get_base_dir()); if (err != OK) { if (r_err) { *r_err = FAILED; From a5f86ee07e401d0191b338c4de440c2665d499e8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pedro=20J=2E=20Est=C3=A9banez?= Date: Thu, 7 Nov 2024 12:13:08 +0100 Subject: [PATCH 019/151] Raise the amount of file handles on Windows --- drivers/windows/file_access_windows.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/windows/file_access_windows.cpp b/drivers/windows/file_access_windows.cpp index a8a2ea6b5e0..fa23e69f427 100644 --- a/drivers/windows/file_access_windows.cpp +++ b/drivers/windows/file_access_windows.cpp @@ -525,6 +525,9 @@ void FileAccessWindows::initialize() { invalid_files.insert(reserved_files[reserved_file_index]); reserved_file_index++; } + + _setmaxstdio(8192); + print_verbose(vformat("Maximum number of file handles: %d", _getmaxstdio())); } void FileAccessWindows::finalize() { From 6445b6cbdda85ecf720fa5caea41073a0aaa3448 Mon Sep 17 00:00:00 2001 From: Anish Mishra Date: Thu, 7 Nov 2024 18:29:25 +0530 Subject: [PATCH 020/151] Android: Fix immersive mode issue --- platform/android/java/lib/src/org/godotengine/godot/Godot.kt | 1 + 1 file changed, 1 insertion(+) diff --git a/platform/android/java/lib/src/org/godotengine/godot/Godot.kt b/platform/android/java/lib/src/org/godotengine/godot/Godot.kt index 3ad8e6bc9ee..6ad74aff918 100644 --- a/platform/android/java/lib/src/org/godotengine/godot/Godot.kt +++ b/platform/android/java/lib/src/org/godotengine/godot/Godot.kt @@ -745,6 +745,7 @@ class Godot(private val context: Context) { runOnUiThread { registerSensorsIfNeeded() + enableImmersiveMode(useImmersive.get(), true) } for (plugin in pluginRegistry.allPlugins) { From dd1372b78e8182c1f15c7e113a7ffc75b75f8ef3 Mon Sep 17 00:00:00 2001 From: rune-scape Date: Mon, 17 Jun 2024 18:43:02 -0700 Subject: [PATCH 021/151] various gui nodes now listen for the changed signal on textures --- scene/gui/line_edit.cpp | 16 ++++++++++++++++ scene/gui/line_edit.h | 1 + scene/gui/texture_button.cpp | 2 +- 3 files changed, 18 insertions(+), 1 deletion(-) diff --git a/scene/gui/line_edit.cpp b/scene/gui/line_edit.cpp index 99678051346..bbba4425ffa 100644 --- a/scene/gui/line_edit.cpp +++ b/scene/gui/line_edit.cpp @@ -2482,11 +2482,27 @@ bool LineEdit::is_drag_and_drop_selection_enabled() const { return drag_and_drop_selection_enabled; } +void LineEdit::_texture_changed() { + _fit_to_width(); + update_minimum_size(); + queue_redraw(); +} + void LineEdit::set_right_icon(const Ref &p_icon) { if (right_icon == p_icon) { return; } + + if (right_icon.is_valid()) { + right_icon->disconnect_changed(callable_mp(this, &LineEdit::_texture_changed)); + } + right_icon = p_icon; + + if (right_icon.is_valid()) { + right_icon->connect_changed(callable_mp(this, &LineEdit::_texture_changed)); + } + _fit_to_width(); update_minimum_size(); queue_redraw(); diff --git a/scene/gui/line_edit.h b/scene/gui/line_edit.h index 9253dd87119..f7d017ab033 100644 --- a/scene/gui/line_edit.h +++ b/scene/gui/line_edit.h @@ -248,6 +248,7 @@ class LineEdit : public Control { void _move_caret_end(bool p_select); void _backspace(bool p_word = false, bool p_all_to_left = false); void _delete(bool p_word = false, bool p_all_to_right = false); + void _texture_changed(); protected: bool _is_over_clear_button(const Point2 &p_pos) const; diff --git a/scene/gui/texture_button.cpp b/scene/gui/texture_button.cpp index 47bb0643b37..392bb50ead9 100644 --- a/scene/gui/texture_button.cpp +++ b/scene/gui/texture_button.cpp @@ -343,7 +343,7 @@ Ref TextureButton::get_texture_focused() const { } void TextureButton::set_texture_focused(const Ref &p_focused) { - focused = p_focused; + _set_texture(&focused, p_focused); } void TextureButton::_set_texture(Ref *p_destination, const Ref &p_texture) { From c363e130519281d9bdaf421d9ba84ad33564d38d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pa=CC=84vels=20Nadtoc=CC=8Cajevs?= <7645683+bruvzg@users.noreply.github.com> Date: Sun, 10 Nov 2024 21:45:32 +0200 Subject: [PATCH 022/151] [Windows] Rename `PKEY_Device_FriendlyName` to avoid duplicate symbols with newer MinGW SDKs. --- drivers/wasapi/audio_driver_wasapi.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/drivers/wasapi/audio_driver_wasapi.cpp b/drivers/wasapi/audio_driver_wasapi.cpp index b800e5f1f4b..b5cb8da249a 100644 --- a/drivers/wasapi/audio_driver_wasapi.cpp +++ b/drivers/wasapi/audio_driver_wasapi.cpp @@ -92,7 +92,7 @@ __CRT_UUID_DECL(IAudioClient3, 0x7ED4EE07, 0x8E67, 0x4CD4, 0x8C, 0x1A, 0x2B, 0x7 #endif // __MINGW32__ || __MINGW64__ -#ifndef PKEY_Device_FriendlyName +#ifndef PKEY_Device_FriendlyNameGodot #undef DEFINE_PROPERTYKEY /* clang-format off */ @@ -100,7 +100,7 @@ __CRT_UUID_DECL(IAudioClient3, 0x7ED4EE07, 0x8E67, 0x4CD4, 0x8C, 0x1A, 0x2B, 0x7 const PROPERTYKEY id = { { a, b, c, { d, e, f, g, h, i, j, k, } }, l }; /* clang-format on */ -DEFINE_PROPERTYKEY(PKEY_Device_FriendlyName, 0xa45c254e, 0xdf1c, 0x4efd, 0x80, 0x20, 0x67, 0xd1, 0x46, 0xa8, 0x50, 0xe0, 14); +DEFINE_PROPERTYKEY(PKEY_Device_FriendlyNameGodot, 0xa45c254e, 0xdf1c, 0x4efd, 0x80, 0x20, 0x67, 0xd1, 0x46, 0xa8, 0x50, 0xe0, 14); #endif const CLSID CLSID_MMDeviceEnumerator = __uuidof(MMDeviceEnumerator); @@ -234,7 +234,7 @@ Error AudioDriverWASAPI::audio_device_init(AudioDeviceWASAPI *p_device, bool p_i PROPVARIANT propvar; PropVariantInit(&propvar); - hr = props->GetValue(PKEY_Device_FriendlyName, &propvar); + hr = props->GetValue(PKEY_Device_FriendlyNameGodot, &propvar); ERR_BREAK(hr != S_OK); if (p_device->device_name == String(propvar.pwszVal)) { @@ -597,7 +597,7 @@ PackedStringArray AudioDriverWASAPI::audio_device_get_list(bool p_input) { PROPVARIANT propvar; PropVariantInit(&propvar); - hr = props->GetValue(PKEY_Device_FriendlyName, &propvar); + hr = props->GetValue(PKEY_Device_FriendlyNameGodot, &propvar); ERR_BREAK(hr != S_OK); list.push_back(String(propvar.pwszVal)); From 3e8a24d0da204eb5100b15efcc1aeff4228bf291 Mon Sep 17 00:00:00 2001 From: Hugo Locurcio Date: Mon, 11 Nov 2024 19:51:54 +0100 Subject: [PATCH 023/151] Display CPU and GPU model name in the editor visual profiler This shows the information from the remote device, which will typically differ from the local device in remote debugging scenarios. --- editor/debugger/editor_visual_profiler.cpp | 10 ++++++++-- editor/debugger/editor_visual_profiler.h | 4 ++++ editor/debugger/script_editor_debugger.cpp | 4 ++++ servers/debugger/servers_debugger.cpp | 6 ++++++ 4 files changed, 22 insertions(+), 2 deletions(-) diff --git a/editor/debugger/editor_visual_profiler.cpp b/editor/debugger/editor_visual_profiler.cpp index 9a83277e99a..7bb444366d9 100644 --- a/editor/debugger/editor_visual_profiler.cpp +++ b/editor/debugger/editor_visual_profiler.cpp @@ -37,6 +37,12 @@ #include "editor/themes/editor_scale.h" #include "scene/resources/image_texture.h" +void EditorVisualProfiler::set_hardware_info(const String &p_cpu_name, const String &p_gpu_name) { + cpu_name = p_cpu_name; + gpu_name = p_gpu_name; + queue_redraw(); +} + void EditorVisualProfiler::add_frame_metric(const Metric &p_metric) { ++last_metric; if (last_metric >= frame_metrics.size()) { @@ -489,8 +495,8 @@ void EditorVisualProfiler::_graph_tex_draw() { graph->draw_string(font, Vector2(half_width * 2 - font->get_string_size(limit_str, HORIZONTAL_ALIGNMENT_LEFT, -1, font_size).x - 2, frame_y - 2), limit_str, HORIZONTAL_ALIGNMENT_LEFT, -1, font_size, color * Color(1, 1, 1, 0.75)); } - graph->draw_string(font, Vector2(font->get_string_size("X", HORIZONTAL_ALIGNMENT_LEFT, -1, font_size).x, font->get_ascent(font_size) + 2), "CPU:", HORIZONTAL_ALIGNMENT_LEFT, -1, font_size, color * Color(1, 1, 1)); - graph->draw_string(font, Vector2(font->get_string_size("X", HORIZONTAL_ALIGNMENT_LEFT, -1, font_size).x + graph->get_size().width / 2, font->get_ascent(font_size) + 2), "GPU:", HORIZONTAL_ALIGNMENT_LEFT, -1, font_size, color * Color(1, 1, 1)); + graph->draw_string(font, Vector2(font->get_string_size("X", HORIZONTAL_ALIGNMENT_LEFT, -1, font_size).x, font->get_ascent(font_size) + 2), "CPU: " + cpu_name, HORIZONTAL_ALIGNMENT_LEFT, -1, font_size, color * Color(1, 1, 1, 0.75)); + graph->draw_string(font, Vector2(font->get_string_size("X", HORIZONTAL_ALIGNMENT_LEFT, -1, font_size).x + graph->get_size().width / 2, font->get_ascent(font_size) + 2), "GPU: " + gpu_name, HORIZONTAL_ALIGNMENT_LEFT, -1, font_size, color * Color(1, 1, 1, 0.75)); } void EditorVisualProfiler::_graph_tex_mouse_exit() { diff --git a/editor/debugger/editor_visual_profiler.h b/editor/debugger/editor_visual_profiler.h index a8ed58132eb..5ef45fcc086 100644 --- a/editor/debugger/editor_visual_profiler.h +++ b/editor/debugger/editor_visual_profiler.h @@ -98,6 +98,9 @@ class EditorVisualProfiler : public VBoxContainer { float graph_limit = 1000.0f / 60; + String cpu_name; + String gpu_name; + bool seeking = false; Timer *frame_delay = nullptr; @@ -136,6 +139,7 @@ class EditorVisualProfiler : public VBoxContainer { static void _bind_methods(); public: + void set_hardware_info(const String &p_cpu_name, const String &p_gpu_name); void add_frame_metric(const Metric &p_metric); void set_enabled(bool p_enable); void set_profiling(bool p_profiling); diff --git a/editor/debugger/script_editor_debugger.cpp b/editor/debugger/script_editor_debugger.cpp index da59450dd0a..8b32abee493 100644 --- a/editor/debugger/script_editor_debugger.cpp +++ b/editor/debugger/script_editor_debugger.cpp @@ -513,6 +513,10 @@ void ScriptEditorDebugger::_parse_message(const String &p_msg, uint64_t p_thread frame_data.write[i] = p_data[i]; } performance_profiler->add_profile_frame(frame_data); + } else if (p_msg == "visual:hardware_info") { + const String cpu_name = p_data[0]; + const String gpu_name = p_data[1]; + visual_profiler->set_hardware_info(cpu_name, gpu_name); } else if (p_msg == "visual:profile_frame") { ServersDebugger::VisualProfilerFrame frame; frame.deserialize(p_data); diff --git a/servers/debugger/servers_debugger.cpp b/servers/debugger/servers_debugger.cpp index eded0ecfe57..0cfaef058a6 100644 --- a/servers/debugger/servers_debugger.cpp +++ b/servers/debugger/servers_debugger.cpp @@ -370,6 +370,12 @@ class ServersDebugger::VisualProfiler : public EngineProfiler { public: void toggle(bool p_enable, const Array &p_opts) { RS::get_singleton()->set_frame_profiling_enabled(p_enable); + + // Send hardware information from the remote device so that it's accurate for remote debugging. + Array hardware_info; + hardware_info.push_back(OS::get_singleton()->get_processor_name()); + hardware_info.push_back(RenderingServer::get_singleton()->get_video_adapter_name()); + EngineDebugger::get_singleton()->send_message("visual:hardware_info", hardware_info); } void add(const Array &p_data) {} From 5769c801960552b3a67faf3d5b0607718df1a3b8 Mon Sep 17 00:00:00 2001 From: MewPurPur Date: Tue, 12 Nov 2024 12:35:41 +0200 Subject: [PATCH 024/151] Add minimum size to some debugger elements --- editor/debugger/editor_performance_profiler.cpp | 5 +++++ editor/debugger/editor_profiler.cpp | 1 + editor/debugger/editor_visual_profiler.cpp | 1 + editor/debugger/script_editor_debugger.cpp | 3 +++ 4 files changed, 10 insertions(+) diff --git a/editor/debugger/editor_performance_profiler.cpp b/editor/debugger/editor_performance_profiler.cpp index 1ea9a665347..b790b93dd7f 100644 --- a/editor/debugger/editor_performance_profiler.cpp +++ b/editor/debugger/editor_performance_profiler.cpp @@ -393,10 +393,14 @@ EditorPerformanceProfiler::EditorPerformanceProfiler() { set_split_offset(340 * EDSCALE); monitor_tree = memnew(Tree); + monitor_tree->set_custom_minimum_size(Size2(300, 0) * EDSCALE); monitor_tree->set_auto_translate_mode(AUTO_TRANSLATE_MODE_DISABLED); monitor_tree->set_columns(2); monitor_tree->set_column_title(0, TTR("Monitor")); + monitor_tree->set_column_expand(0, true); monitor_tree->set_column_title(1, TTR("Value")); + monitor_tree->set_column_custom_minimum_width(1, 100 * EDSCALE); + monitor_tree->set_column_expand(1, false); monitor_tree->set_column_titles_visible(true); monitor_tree->connect("item_edited", callable_mp(this, &EditorPerformanceProfiler::_monitor_select)); monitor_tree->create_item(); @@ -404,6 +408,7 @@ EditorPerformanceProfiler::EditorPerformanceProfiler() { add_child(monitor_tree); monitor_draw = memnew(Control); + monitor_draw->set_custom_minimum_size(Size2(300, 0) * EDSCALE); monitor_draw->set_clip_contents(true); monitor_draw->connect(SceneStringName(draw), callable_mp(this, &EditorPerformanceProfiler::_monitor_draw)); monitor_draw->connect(SceneStringName(gui_input), callable_mp(this, &EditorPerformanceProfiler::_marker_input)); diff --git a/editor/debugger/editor_profiler.cpp b/editor/debugger/editor_profiler.cpp index 33fa208f70a..6f5d2efc840 100644 --- a/editor/debugger/editor_profiler.cpp +++ b/editor/debugger/editor_profiler.cpp @@ -715,6 +715,7 @@ EditorProfiler::EditorProfiler() { variables->connect("item_edited", callable_mp(this, &EditorProfiler::_item_edited)); graph = memnew(TextureRect); + graph->set_custom_minimum_size(Size2(250 * EDSCALE, 0)); graph->set_expand_mode(TextureRect::EXPAND_IGNORE_SIZE); graph->set_mouse_filter(MOUSE_FILTER_STOP); graph->connect(SceneStringName(draw), callable_mp(this, &EditorProfiler::_graph_tex_draw)); diff --git a/editor/debugger/editor_visual_profiler.cpp b/editor/debugger/editor_visual_profiler.cpp index 9a83277e99a..c1f26b0f6b2 100644 --- a/editor/debugger/editor_visual_profiler.cpp +++ b/editor/debugger/editor_visual_profiler.cpp @@ -810,6 +810,7 @@ EditorVisualProfiler::EditorVisualProfiler() { variables->connect("cell_selected", callable_mp(this, &EditorVisualProfiler::_item_selected)); graph = memnew(TextureRect); + graph->set_custom_minimum_size(Size2(250 * EDSCALE, 0)); graph->set_expand_mode(TextureRect::EXPAND_IGNORE_SIZE); graph->set_mouse_filter(MOUSE_FILTER_STOP); graph->connect(SceneStringName(draw), callable_mp(this, &EditorVisualProfiler::_graph_tex_draw)); diff --git a/editor/debugger/script_editor_debugger.cpp b/editor/debugger/script_editor_debugger.cpp index da59450dd0a..c66f8c96a50 100644 --- a/editor/debugger/script_editor_debugger.cpp +++ b/editor/debugger/script_editor_debugger.cpp @@ -1915,6 +1915,7 @@ ScriptEditorDebugger::ScriptEditorDebugger() { threads->connect(SceneStringName(item_selected), callable_mp(this, &ScriptEditorDebugger::_select_thread)); stack_dump = memnew(Tree); + stack_dump->set_custom_minimum_size(Size2(150, 0) * EDSCALE); stack_dump->set_allow_reselect(true); stack_dump->set_columns(1); stack_dump->set_column_titles_visible(true); @@ -1925,6 +1926,7 @@ ScriptEditorDebugger::ScriptEditorDebugger() { stack_vb->add_child(stack_dump); VBoxContainer *inspector_vbox = memnew(VBoxContainer); + inspector_vbox->set_custom_minimum_size(Size2(200, 0) * EDSCALE); inspector_vbox->set_h_size_flags(SIZE_EXPAND_FILL); sc->add_child(inspector_vbox); @@ -1950,6 +1952,7 @@ ScriptEditorDebugger::ScriptEditorDebugger() { inspector_vbox->add_child(inspector); breakpoints_tree = memnew(Tree); + breakpoints_tree->set_custom_minimum_size(Size2(100, 0) * EDSCALE); breakpoints_tree->set_h_size_flags(SIZE_EXPAND_FILL); breakpoints_tree->set_column_titles_visible(true); breakpoints_tree->set_column_title(0, TTR("Breakpoints")); From c6492de27bd17799a6887607b1d0a903ec4a8dde Mon Sep 17 00:00:00 2001 From: Mateus Reis Date: Mon, 10 Apr 2023 22:23:28 +0300 Subject: [PATCH 025/151] Profiler plot zoom and pan --- editor/debugger/editor_profiler.cpp | 64 +++++++++++++++++++++-------- editor/debugger/editor_profiler.h | 6 +++ 2 files changed, 54 insertions(+), 16 deletions(-) diff --git a/editor/debugger/editor_profiler.cpp b/editor/debugger/editor_profiler.cpp index 33fa208f70a..867b794dac2 100644 --- a/editor/debugger/editor_profiler.cpp +++ b/editor/debugger/editor_profiler.cpp @@ -152,6 +152,11 @@ Color EditorProfiler::_get_color_from_signature(const StringName &p_signature) c return c.lerp(get_theme_color(SNAME("base_color"), EditorStringName(Editor)), 0.07); } +int EditorProfiler::_get_zoom_left_border() const { + const int max_profiles_shown = frame_metrics.size() / Math::exp(graph_zoom); + return CLAMP(zoom_center - max_profiles_shown / 2, 0, frame_metrics.size() - max_profiles_shown); +} + void EditorProfiler::_item_edited() { if (updating_frame) { return; @@ -237,12 +242,17 @@ void EditorProfiler::_update_plot() { HashMap prev_plots; - for (int i = 0; i < total_metrics * w / frame_metrics.size() - 1; i++) { + const int max_profiles_shown = frame_metrics.size() / Math::exp(graph_zoom); + const int left_border = _get_zoom_left_border(); + const int profiles_drawn = CLAMP(total_metrics - left_border, 0, max_profiles_shown); + const int pixel_cols = (profiles_drawn * w) / max_profiles_shown - 1; + + for (int i = 0; i < pixel_cols; i++) { for (int j = 0; j < h * 4; j++) { column[j] = 0; } - int current = i * frame_metrics.size() / w; + int current = (i * max_profiles_shown / w) + left_border; for (const StringName &E : plot_sigs) { const Metric &m = _get_frame_metric(current); @@ -449,10 +459,12 @@ void EditorProfiler::_graph_tex_draw() { } if (seeking) { int frame = cursor_metric_edit->get_value() - _get_frame_metric(0).frame_number; - int cur_x = (2 * frame + 1) * graph->get_size().x / (2 * frame_metrics.size()) + 1; + frame = frame - _get_zoom_left_border() + 1; + int cur_x = (frame * graph->get_size().width * Math::exp(graph_zoom)) / frame_metrics.size(); + cur_x = CLAMP(cur_x, 0, graph->get_size().width); graph->draw_line(Vector2(cur_x, 0), Vector2(cur_x, graph->get_size().y), theme_cache.seek_line_color); } - if (hover_metric > -1 && hover_metric < total_metrics) { + if (hover_metric > -1) { int cur_x = (2 * hover_metric + 1) * graph->get_size().x / (2 * frame_metrics.size()) + 1; graph->draw_line(Vector2(cur_x, 0), Vector2(cur_x, graph->get_size().y), theme_cache.seek_line_hover_color); } @@ -480,22 +492,17 @@ void EditorProfiler::_graph_tex_input(const Ref &p_ev) { Ref me = p_ev; Ref mb = p_ev; Ref mm = p_ev; + MouseButton button_idx = mb.is_valid() ? mb->get_button_index() : MouseButton(); if ( - (mb.is_valid() && mb->get_button_index() == MouseButton::LEFT && mb->is_pressed()) || + (mb.is_valid() && button_idx == MouseButton::LEFT && mb->is_pressed()) || (mm.is_valid())) { int x = me->get_position().x - 1; - x = x * frame_metrics.size() / graph->get_size().width; - - hover_metric = x; + hover_metric = x * frame_metrics.size() / graph->get_size().width; - if (x < 0) { - x = 0; - } - - if (x >= frame_metrics.size()) { - x = frame_metrics.size() - 1; - } + x = x * frame_metrics.size() / graph->get_size().width; + x = x / Math::exp(graph_zoom) + _get_zoom_left_border(); + x = CLAMP(x, 0, frame_metrics.size() - 1); if (mb.is_valid() || (mm->get_button_mask().has_flag(MouseButtonMask::LEFT))) { updating_frame = true; @@ -518,9 +525,34 @@ void EditorProfiler::_graph_tex_input(const Ref &p_ev) { frame_delay->start(); } } + } + + if (graph_zoom > 0 && mm.is_valid() && (mm->get_button_mask().has_flag(MouseButtonMask::MIDDLE) || mm->get_button_mask().has_flag(MouseButtonMask::RIGHT))) { + // Panning. + const int max_profiles_shown = frame_metrics.size() / Math::exp(graph_zoom); + pan_accumulator += (float)mm->get_relative().x * max_profiles_shown / graph->get_size().width; + + if (Math::abs(pan_accumulator) > 1) { + zoom_center = CLAMP(zoom_center - (int)pan_accumulator, max_profiles_shown / 2, frame_metrics.size() - max_profiles_shown / 2); + pan_accumulator -= (int)pan_accumulator; + _update_plot(); + } + } - graph->queue_redraw(); + if (button_idx == MouseButton::WHEEL_DOWN) { + // Zooming. + graph_zoom = MAX(-0.05 + graph_zoom, 0); + _update_plot(); + } else if (button_idx == MouseButton::WHEEL_UP) { + if (graph_zoom == 0) { + zoom_center = me->get_position().x; + zoom_center = zoom_center * frame_metrics.size() / graph->get_size().width; + } + graph_zoom = MIN(0.05 + graph_zoom, 2); + _update_plot(); } + + graph->queue_redraw(); } void EditorProfiler::disable_seeking() { diff --git a/editor/debugger/editor_profiler.h b/editor/debugger/editor_profiler.h index 8de276be437..c9a18e3f26a 100644 --- a/editor/debugger/editor_profiler.h +++ b/editor/debugger/editor_profiler.h @@ -104,6 +104,11 @@ class EditorProfiler : public VBoxContainer { TextureRect *graph = nullptr; Ref graph_texture; Vector graph_image; + + float graph_zoom = 0.0f; + float pan_accumulator = 0.0f; + int zoom_center = -1; + Tree *variables = nullptr; HSplitContainer *h_split = nullptr; @@ -155,6 +160,7 @@ class EditorProfiler : public VBoxContainer { void _graph_tex_input(const Ref &p_ev); Color _get_color_from_signature(const StringName &p_signature) const; + int _get_zoom_left_border() const; void _cursor_metric_changed(double); From b329b4ab06873fdbfdeb6030a171bba8dff04939 Mon Sep 17 00:00:00 2001 From: bruvzg <7645683+bruvzg@users.noreply.github.com> Date: Mon, 28 Oct 2024 10:37:07 +0200 Subject: [PATCH 026/151] [Label] Handle text as multiple independent paragraphs. --- doc/classes/Label.xml | 6 + doc/classes/LabelSettings.xml | 3 + scene/gui/label.cpp | 850 ++++++++++++++++++----------- scene/gui/label.h | 23 +- scene/resources/label_settings.cpp | 15 + scene/resources/label_settings.h | 4 + 6 files changed, 571 insertions(+), 330 deletions(-) diff --git a/doc/classes/Label.xml b/doc/classes/Label.xml index ae5a62753f1..7657cf31214 100644 --- a/doc/classes/Label.xml +++ b/doc/classes/Label.xml @@ -74,6 +74,9 @@ Limits the lines of text the node shows on screen. + + String used as a paragraph separator. Each paragraph is processed independently, in its own BiDi context. + Set BiDi algorithm override for the structured text. @@ -129,6 +132,9 @@ [b]Note:[/b] If using a font with [member FontFile.multichannel_signed_distance_field] enabled, its [member FontFile.msdf_pixel_range] must be set to at least [i]twice[/i] the value of [theme_item outline_size] for outline rendering to look correct. Otherwise, the outline may appear to be cut off earlier than intended. [b]Note:[/b] Using a value that is larger than half the font size is not recommended, as the font outline may fail to be fully closed in this case. + + Vertical space between paragraphs. Added on top of [theme_item line_spacing]. + The horizontal offset of the text's shadow. diff --git a/doc/classes/LabelSettings.xml b/doc/classes/LabelSettings.xml index ff7b8e7b0e4..610f10574b5 100644 --- a/doc/classes/LabelSettings.xml +++ b/doc/classes/LabelSettings.xml @@ -27,6 +27,9 @@ Text outline size. + + Vertical space between paragraphs. Added on top of [member line_spacing]. + Color of the shadow effect. If alpha is [code]0[/code], no shadow will be drawn. diff --git a/scene/gui/label.cpp b/scene/gui/label.cpp index 7a0e5b8867b..7dd24cc14d3 100644 --- a/scene/gui/label.cpp +++ b/scene/gui/label.cpp @@ -43,7 +43,9 @@ void Label::set_autowrap_mode(TextServer::AutowrapMode p_mode) { } autowrap_mode = p_mode; - lines_dirty = true; + for (Paragraph ¶ : paragraphs) { + para.lines_dirty = true; + } queue_redraw(); update_configuration_warnings(); @@ -62,7 +64,9 @@ void Label::set_justification_flags(BitField p_fl } jst_flags = p_flags; - lines_dirty = true; + for (Paragraph ¶ : paragraphs) { + para.lines_dirty = true; + } queue_redraw(); } @@ -76,7 +80,7 @@ void Label::set_uppercase(bool p_uppercase) { } uppercase = p_uppercase; - dirty = true; + text_dirty = true; queue_redraw(); } @@ -87,12 +91,14 @@ bool Label::is_uppercase() const { int Label::get_line_height(int p_line) const { Ref font = (settings.is_valid() && settings->get_font().is_valid()) ? settings->get_font() : theme_cache.font; - if (p_line >= 0 && p_line < lines_rid.size()) { - return TS->shaped_text_get_size(lines_rid[p_line]).y; - } else if (lines_rid.size() > 0) { + if (p_line >= 0 && p_line < total_line_count) { + return TS->shaped_text_get_size(get_line_rid(p_line)).y; + } else if (total_line_count > 0) { int h = 0; - for (int i = 0; i < lines_rid.size(); i++) { - h = MAX(h, TS->shaped_text_get_size(lines_rid[i]).y); + for (const Paragraph ¶ : paragraphs) { + for (const RID &line_rid : para.lines_rid) { + h = MAX(h, TS->shaped_text_get_size(line_rid).y); + } } return h; } else { @@ -105,181 +111,216 @@ void Label::_shape() { Ref style = theme_cache.normal_style; int width = (get_size().width - style->get_minimum_size().width); - if (dirty || font_dirty) { - if (dirty) { - TS->shaped_text_clear(text_rid); - } - if (text_direction == Control::TEXT_DIRECTION_INHERITED) { - TS->shaped_text_set_direction(text_rid, is_layout_rtl() ? TextServer::DIRECTION_RTL : TextServer::DIRECTION_LTR); - } else { - TS->shaped_text_set_direction(text_rid, (TextServer::Direction)text_direction); + if (text_dirty) { + for (Paragraph ¶ : paragraphs) { + for (const RID &line_rid : para.lines_rid) { + TS->free_rid(line_rid); + } + para.lines_rid.clear(); + TS->free_rid(para.text_rid); } - const Ref &font = (settings.is_valid() && settings->get_font().is_valid()) ? settings->get_font() : theme_cache.font; - int font_size = settings.is_valid() ? settings->get_font_size() : theme_cache.font_size; - ERR_FAIL_COND(font.is_null()); + paragraphs.clear(); + String txt = (uppercase) ? TS->string_to_upper(xl_text, language) : xl_text; if (visible_chars >= 0 && visible_chars_behavior == TextServer::VC_CHARS_BEFORE_SHAPING) { txt = txt.substr(0, visible_chars); } - if (dirty) { - TS->shaped_text_add_string(text_rid, txt, font->get_rids(), font_size, font->get_opentype_features(), language); - } else { - int spans = TS->shaped_get_span_count(text_rid); - for (int i = 0; i < spans; i++) { - TS->shaped_set_span_update_font(text_rid, i, font->get_rids(), font_size, font->get_opentype_features()); - } - } - TS->shaped_text_set_bidi_override(text_rid, structured_text_parser(st_parser, st_args, txt)); - if (!tab_stops.is_empty()) { - TS->shaped_text_tab_align(text_rid, tab_stops); + String ps = paragraph_separator.c_unescape(); + Vector para_text = txt.split(ps); + int start = 0; + for (const String &str : para_text) { + Paragraph para; + para.text_rid = TS->create_shaped_text(); + para.text = str; + para.start = start; + start += str.length() + ps.length(); + paragraphs.push_back(para); } - dirty = false; - font_dirty = false; - lines_dirty = true; + text_dirty = false; } - if (lines_dirty) { - for (int i = 0; i < lines_rid.size(); i++) { - TS->free_rid(lines_rid[i]); - } - lines_rid.clear(); + total_line_count = 0; + for (Paragraph ¶ : paragraphs) { + if (para.dirty || font_dirty) { + if (para.dirty) { + TS->shaped_text_clear(para.text_rid); + } + if (text_direction == Control::TEXT_DIRECTION_INHERITED) { + TS->shaped_text_set_direction(para.text_rid, is_layout_rtl() ? TextServer::DIRECTION_RTL : TextServer::DIRECTION_LTR); + } else { + TS->shaped_text_set_direction(para.text_rid, (TextServer::Direction)text_direction); + } + const Ref &font = (settings.is_valid() && settings->get_font().is_valid()) ? settings->get_font() : theme_cache.font; + int font_size = settings.is_valid() ? settings->get_font_size() : theme_cache.font_size; + ERR_FAIL_COND(font.is_null()); - BitField autowrap_flags = TextServer::BREAK_MANDATORY; - switch (autowrap_mode) { - case TextServer::AUTOWRAP_WORD_SMART: - autowrap_flags = TextServer::BREAK_WORD_BOUND | TextServer::BREAK_ADAPTIVE | TextServer::BREAK_MANDATORY; - break; - case TextServer::AUTOWRAP_WORD: - autowrap_flags = TextServer::BREAK_WORD_BOUND | TextServer::BREAK_MANDATORY; - break; - case TextServer::AUTOWRAP_ARBITRARY: - autowrap_flags = TextServer::BREAK_GRAPHEME_BOUND | TextServer::BREAK_MANDATORY; - break; - case TextServer::AUTOWRAP_OFF: - break; + if (para.dirty) { + TS->shaped_text_add_string(para.text_rid, para.text, font->get_rids(), font_size, font->get_opentype_features(), language); + } else { + int spans = TS->shaped_get_span_count(para.text_rid); + for (int i = 0; i < spans; i++) { + TS->shaped_set_span_update_font(para.text_rid, i, font->get_rids(), font_size, font->get_opentype_features()); + } + } + TS->shaped_text_set_bidi_override(para.text_rid, structured_text_parser(st_parser, st_args, para.text)); + if (!tab_stops.is_empty()) { + TS->shaped_text_tab_align(para.text_rid, tab_stops); + } + para.dirty = false; + para.lines_dirty = true; } - autowrap_flags = autowrap_flags | TextServer::BREAK_TRIM_EDGE_SPACES; - PackedInt32Array line_breaks = TS->shaped_text_get_line_breaks(text_rid, width, 0, autowrap_flags); - for (int i = 0; i < line_breaks.size(); i = i + 2) { - RID line = TS->shaped_text_substr(text_rid, line_breaks[i], line_breaks[i + 1] - line_breaks[i]); - if (!tab_stops.is_empty()) { - TS->shaped_text_tab_align(line, tab_stops); + if (para.lines_dirty) { + for (const RID &line_rid : para.lines_rid) { + TS->free_rid(line_rid); + } + para.lines_rid.clear(); + + BitField autowrap_flags = TextServer::BREAK_MANDATORY; + switch (autowrap_mode) { + case TextServer::AUTOWRAP_WORD_SMART: + autowrap_flags = TextServer::BREAK_WORD_BOUND | TextServer::BREAK_ADAPTIVE | TextServer::BREAK_MANDATORY; + break; + case TextServer::AUTOWRAP_WORD: + autowrap_flags = TextServer::BREAK_WORD_BOUND | TextServer::BREAK_MANDATORY; + break; + case TextServer::AUTOWRAP_ARBITRARY: + autowrap_flags = TextServer::BREAK_GRAPHEME_BOUND | TextServer::BREAK_MANDATORY; + break; + case TextServer::AUTOWRAP_OFF: + break; + } + autowrap_flags = autowrap_flags | TextServer::BREAK_TRIM_EDGE_SPACES; + + PackedInt32Array line_breaks = TS->shaped_text_get_line_breaks(para.text_rid, width, 0, autowrap_flags); + for (int i = 0; i < line_breaks.size(); i = i + 2) { + RID line = TS->shaped_text_substr(para.text_rid, line_breaks[i], line_breaks[i + 1] - line_breaks[i]); + if (!tab_stops.is_empty()) { + TS->shaped_text_tab_align(line, tab_stops); + } + para.lines_rid.push_back(line); } - lines_rid.push_back(line); } + total_line_count += para.lines_rid.size(); } + dirty = false; + font_dirty = false; if (xl_text.length() == 0) { minsize = Size2(1, get_line_height()); return; } - if (autowrap_mode == TextServer::AUTOWRAP_OFF) { - minsize.width = 0.0f; - for (int i = 0; i < lines_rid.size(); i++) { - if (minsize.width < TS->shaped_text_get_size(lines_rid[i]).x) { - minsize.width = TS->shaped_text_get_size(lines_rid[i]).x; + int visible_lines = get_visible_line_count(); + bool lines_hidden = visible_lines > 0 && visible_lines < total_line_count; + + int line_index = 0; + for (Paragraph ¶ : paragraphs) { + if (autowrap_mode == TextServer::AUTOWRAP_OFF) { + minsize.width = 0.0f; + for (const RID &line_rid : para.lines_rid) { + if (minsize.width < TS->shaped_text_get_size(line_rid).x) { + minsize.width = TS->shaped_text_get_size(line_rid).x; + } } } - } - if (lines_dirty) { - BitField overrun_flags = TextServer::OVERRUN_NO_TRIM; - switch (overrun_behavior) { - case TextServer::OVERRUN_TRIM_WORD_ELLIPSIS: - overrun_flags.set_flag(TextServer::OVERRUN_TRIM); - overrun_flags.set_flag(TextServer::OVERRUN_TRIM_WORD_ONLY); - overrun_flags.set_flag(TextServer::OVERRUN_ADD_ELLIPSIS); - break; - case TextServer::OVERRUN_TRIM_ELLIPSIS: - overrun_flags.set_flag(TextServer::OVERRUN_TRIM); - overrun_flags.set_flag(TextServer::OVERRUN_ADD_ELLIPSIS); - break; - case TextServer::OVERRUN_TRIM_WORD: - overrun_flags.set_flag(TextServer::OVERRUN_TRIM); - overrun_flags.set_flag(TextServer::OVERRUN_TRIM_WORD_ONLY); - break; - case TextServer::OVERRUN_TRIM_CHAR: - overrun_flags.set_flag(TextServer::OVERRUN_TRIM); - break; - case TextServer::OVERRUN_NO_TRIMMING: - break; - } + if (para.lines_dirty) { + BitField overrun_flags = TextServer::OVERRUN_NO_TRIM; + switch (overrun_behavior) { + case TextServer::OVERRUN_TRIM_WORD_ELLIPSIS: + overrun_flags.set_flag(TextServer::OVERRUN_TRIM); + overrun_flags.set_flag(TextServer::OVERRUN_TRIM_WORD_ONLY); + overrun_flags.set_flag(TextServer::OVERRUN_ADD_ELLIPSIS); + break; + case TextServer::OVERRUN_TRIM_ELLIPSIS: + overrun_flags.set_flag(TextServer::OVERRUN_TRIM); + overrun_flags.set_flag(TextServer::OVERRUN_ADD_ELLIPSIS); + break; + case TextServer::OVERRUN_TRIM_WORD: + overrun_flags.set_flag(TextServer::OVERRUN_TRIM); + overrun_flags.set_flag(TextServer::OVERRUN_TRIM_WORD_ONLY); + break; + case TextServer::OVERRUN_TRIM_CHAR: + overrun_flags.set_flag(TextServer::OVERRUN_TRIM); + break; + case TextServer::OVERRUN_NO_TRIMMING: + break; + } - // Fill after min_size calculation. + // Fill after min_size calculation. - BitField line_jst_flags = jst_flags; - if (!tab_stops.is_empty()) { - line_jst_flags.set_flag(TextServer::JUSTIFICATION_AFTER_LAST_TAB); - } - if (autowrap_mode != TextServer::AUTOWRAP_OFF) { - int visible_lines = get_visible_line_count(); - bool lines_hidden = visible_lines > 0 && visible_lines < lines_rid.size(); - if (lines_hidden) { - overrun_flags.set_flag(TextServer::OVERRUN_ENFORCE_ELLIPSIS); + BitField line_jst_flags = jst_flags; + if (!tab_stops.is_empty()) { + line_jst_flags.set_flag(TextServer::JUSTIFICATION_AFTER_LAST_TAB); } - if (horizontal_alignment == HORIZONTAL_ALIGNMENT_FILL) { - int jst_to_line = visible_lines; - if (lines_rid.size() == 1 && line_jst_flags.has_flag(TextServer::JUSTIFICATION_DO_NOT_SKIP_SINGLE_LINE)) { - jst_to_line = lines_rid.size(); + if (autowrap_mode != TextServer::AUTOWRAP_OFF) { + if (lines_hidden) { + overrun_flags.set_flag(TextServer::OVERRUN_ENFORCE_ELLIPSIS); + } + if (horizontal_alignment == HORIZONTAL_ALIGNMENT_FILL) { + int jst_to_line = para.lines_rid.size(); + if (para.lines_rid.size() == 1 && line_jst_flags.has_flag(TextServer::JUSTIFICATION_DO_NOT_SKIP_SINGLE_LINE)) { + jst_to_line = para.lines_rid.size(); + } else { + if (line_jst_flags.has_flag(TextServer::JUSTIFICATION_SKIP_LAST_LINE)) { + jst_to_line = para.lines_rid.size() - 1; + } + if (line_jst_flags.has_flag(TextServer::JUSTIFICATION_SKIP_LAST_LINE_WITH_VISIBLE_CHARS)) { + for (int i = para.lines_rid.size() - 1; i >= 0; i--) { + if (TS->shaped_text_has_visible_chars(para.lines_rid[i])) { + jst_to_line = i; + break; + } + } + } + } + for (int i = 0; i < para.lines_rid.size(); i++) { + if (i < jst_to_line) { + TS->shaped_text_fit_to_width(para.lines_rid[i], width, line_jst_flags); + } else if (i == (visible_lines - line_index - 1)) { + TS->shaped_text_set_custom_ellipsis(para.lines_rid[i], (el_char.length() > 0) ? el_char[0] : 0x2026); + TS->shaped_text_overrun_trim_to_width(para.lines_rid[i], width, overrun_flags); + } + } + } else if (lines_hidden && (visible_lines - line_index - 1 >= 0) && (visible_lines - line_index - 1) < para.lines_rid.size()) { + TS->shaped_text_set_custom_ellipsis(para.lines_rid[visible_lines - line_index - 1], (el_char.length() > 0) ? el_char[0] : 0x2026); + TS->shaped_text_overrun_trim_to_width(para.lines_rid[visible_lines - line_index - 1], width, overrun_flags); + } + } else { + // Autowrap disabled. + int jst_to_line = para.lines_rid.size(); + if (para.lines_rid.size() == 1 && line_jst_flags.has_flag(TextServer::JUSTIFICATION_DO_NOT_SKIP_SINGLE_LINE)) { + jst_to_line = para.lines_rid.size(); } else { if (line_jst_flags.has_flag(TextServer::JUSTIFICATION_SKIP_LAST_LINE)) { - jst_to_line = visible_lines - 1; + jst_to_line = para.lines_rid.size() - 1; } if (line_jst_flags.has_flag(TextServer::JUSTIFICATION_SKIP_LAST_LINE_WITH_VISIBLE_CHARS)) { - for (int i = visible_lines - 1; i >= 0; i--) { - if (TS->shaped_text_has_visible_chars(lines_rid[i])) { + for (int i = para.lines_rid.size() - 1; i >= 0; i--) { + if (TS->shaped_text_has_visible_chars(para.lines_rid[i])) { jst_to_line = i; break; } } } } - for (int i = 0; i < lines_rid.size(); i++) { - if (i < jst_to_line) { - TS->shaped_text_fit_to_width(lines_rid[i], width, line_jst_flags); - } else if (i == (visible_lines - 1)) { - TS->shaped_text_set_custom_ellipsis(lines_rid[i], (el_char.length() > 0) ? el_char[0] : 0x2026); - TS->shaped_text_overrun_trim_to_width(lines_rid[i], width, overrun_flags); + for (int i = 0; i < para.lines_rid.size(); i++) { + if (i < jst_to_line && horizontal_alignment == HORIZONTAL_ALIGNMENT_FILL) { + TS->shaped_text_fit_to_width(para.lines_rid[i], width, line_jst_flags); + overrun_flags.set_flag(TextServer::OVERRUN_JUSTIFICATION_AWARE); + TS->shaped_text_set_custom_ellipsis(para.lines_rid[i], (el_char.length() > 0) ? el_char[0] : 0x2026); + TS->shaped_text_overrun_trim_to_width(para.lines_rid[i], width, overrun_flags); + TS->shaped_text_fit_to_width(para.lines_rid[i], width, line_jst_flags | TextServer::JUSTIFICATION_CONSTRAIN_ELLIPSIS); + } else { + TS->shaped_text_set_custom_ellipsis(para.lines_rid[i], (el_char.length() > 0) ? el_char[0] : 0x2026); + TS->shaped_text_overrun_trim_to_width(para.lines_rid[i], width, overrun_flags); } } - } else if (lines_hidden) { - TS->shaped_text_set_custom_ellipsis(lines_rid[visible_lines - 1], (el_char.length() > 0) ? el_char[0] : 0x2026); - TS->shaped_text_overrun_trim_to_width(lines_rid[visible_lines - 1], width, overrun_flags); - } - } else { - // Autowrap disabled. - int jst_to_line = lines_rid.size(); - if (lines_rid.size() == 1 && line_jst_flags.has_flag(TextServer::JUSTIFICATION_DO_NOT_SKIP_SINGLE_LINE)) { - jst_to_line = lines_rid.size(); - } else { - if (line_jst_flags.has_flag(TextServer::JUSTIFICATION_SKIP_LAST_LINE)) { - jst_to_line = lines_rid.size() - 1; - } - if (line_jst_flags.has_flag(TextServer::JUSTIFICATION_SKIP_LAST_LINE_WITH_VISIBLE_CHARS)) { - for (int i = lines_rid.size() - 1; i >= 0; i--) { - if (TS->shaped_text_has_visible_chars(lines_rid[i])) { - jst_to_line = i; - break; - } - } - } - } - for (int i = 0; i < lines_rid.size(); i++) { - if (i < jst_to_line && horizontal_alignment == HORIZONTAL_ALIGNMENT_FILL) { - TS->shaped_text_fit_to_width(lines_rid[i], width, line_jst_flags); - overrun_flags.set_flag(TextServer::OVERRUN_JUSTIFICATION_AWARE); - TS->shaped_text_set_custom_ellipsis(lines_rid[i], (el_char.length() > 0) ? el_char[0] : 0x2026); - TS->shaped_text_overrun_trim_to_width(lines_rid[i], width, overrun_flags); - TS->shaped_text_fit_to_width(lines_rid[i], width, line_jst_flags | TextServer::JUSTIFICATION_CONSTRAIN_ELLIPSIS); - } else { - TS->shaped_text_set_custom_ellipsis(lines_rid[i], (el_char.length() > 0) ? el_char[0] : 0x2026); - TS->shaped_text_overrun_trim_to_width(lines_rid[i], width, overrun_flags); - } } + para.lines_dirty = false; } - lines_dirty = false; + line_index += para.lines_rid.size(); } _update_visible(); @@ -291,20 +332,37 @@ void Label::_shape() { void Label::_update_visible() { int line_spacing = settings.is_valid() ? settings->get_line_spacing() : theme_cache.line_spacing; + int paragraph_spacing = settings.is_valid() ? settings->get_paragraph_spacing() : theme_cache.paragraph_spacing; Ref style = theme_cache.normal_style; - int lines_visible = lines_rid.size(); + int lines_visible = total_line_count; if (max_lines_visible >= 0 && lines_visible > max_lines_visible) { lines_visible = max_lines_visible; } minsize.height = 0; - int last_line = MIN(lines_rid.size(), lines_visible + lines_skipped); - for (int64_t i = lines_skipped; i < last_line; i++) { - minsize.height += TS->shaped_text_get_size(lines_rid[i]).y + line_spacing; + int last_line = MIN(total_line_count, lines_visible + lines_skipped); + + int line_index = 0; + for (const Paragraph ¶ : paragraphs) { + if (line_index + para.lines_rid.size() <= lines_skipped) { + line_index += para.lines_rid.size(); + } else { + int start = (line_index < lines_skipped) ? lines_skipped - line_index : 0; + int end = (line_index + para.lines_rid.size() < last_line) ? para.lines_rid.size() : last_line - line_index; + if (end <= 0) { + break; + } + for (int i = start; i < end; i++) { + minsize.height += TS->shaped_text_get_size(para.lines_rid[i]).y + line_spacing; + } + minsize.height += paragraph_spacing; + line_index += para.lines_rid.size(); + } } + if (minsize.height > 0) { - minsize.height -= line_spacing; + minsize.height -= (line_spacing + paragraph_spacing); } } @@ -336,25 +394,61 @@ inline void draw_glyph_outline(const Glyph &p_gl, const RID &p_canvas, const Col } void Label::_ensure_shaped() const { - if (dirty || font_dirty || lines_dirty) { + if (dirty || font_dirty || text_dirty) { const_cast