diff options
Diffstat (limited to 'scene/gui')
-rw-r--r-- | scene/gui/base_button.cpp | 2 | ||||
-rw-r--r-- | scene/gui/code_edit.cpp | 2 | ||||
-rw-r--r-- | scene/gui/control.cpp | 9 | ||||
-rw-r--r-- | scene/gui/control.h | 2 | ||||
-rw-r--r-- | scene/gui/label.cpp | 40 | ||||
-rw-r--r-- | scene/gui/label.h | 1 | ||||
-rw-r--r-- | scene/gui/menu_button.cpp | 8 | ||||
-rw-r--r-- | scene/gui/option_button.cpp | 9 | ||||
-rw-r--r-- | scene/gui/popup_menu.cpp | 44 | ||||
-rw-r--r-- | scene/gui/rich_text_label.cpp | 117 | ||||
-rw-r--r-- | scene/gui/rich_text_label.h | 15 | ||||
-rw-r--r-- | scene/gui/text_edit.cpp | 73 | ||||
-rw-r--r-- | scene/gui/text_edit.h | 3 |
13 files changed, 253 insertions, 72 deletions
diff --git a/scene/gui/base_button.cpp b/scene/gui/base_button.cpp index ec451b07cf..da2ef6c5ec 100644 --- a/scene/gui/base_button.cpp +++ b/scene/gui/base_button.cpp @@ -403,7 +403,7 @@ bool BaseButton::_is_focus_owner_in_shorcut_context() const { } Node *ctx_node = get_shortcut_context(); - Control *vp_focus = get_focus_owner(); + Control *vp_focus = get_viewport() ? get_viewport()->gui_get_focus_owner() : nullptr; // If the context is valid and the viewport focus is valid, check if the context is the focus or is a parent of it. return ctx_node && vp_focus && (ctx_node == vp_focus || ctx_node->is_ancestor_of(vp_focus)); diff --git a/scene/gui/code_edit.cpp b/scene/gui/code_edit.cpp index 22def607ed..7e21a43ab6 100644 --- a/scene/gui/code_edit.cpp +++ b/scene/gui/code_edit.cpp @@ -232,7 +232,7 @@ void CodeEdit::_notification(int p_what) { int begin = 0; int end = 0; - if (line.find(String::chr(0xFFFF)) != -1) { + if (line.contains(String::chr(0xFFFF))) { begin = font->get_string_size(line.substr(0, line.find(String::chr(0xFFFF))), font_size).x; end = font->get_string_size(line.substr(0, line.rfind(String::chr(0xFFFF))), font_size).x; } diff --git a/scene/gui/control.cpp b/scene/gui/control.cpp index 7ebc5c27f8..fdae8e2f1f 100644 --- a/scene/gui/control.cpp +++ b/scene/gui/control.cpp @@ -2196,8 +2196,7 @@ void Control::release_focus() { return; } - get_viewport()->_gui_remove_focus(); - update(); + get_viewport()->gui_release_focus(); } bool Control::is_top_level_control() const { @@ -2600,11 +2599,6 @@ Control::MouseFilter Control::get_mouse_filter() const { return data.mouse_filter; } -Control *Control::get_focus_owner() const { - ERR_FAIL_COND_V(!is_inside_tree(), nullptr); - return get_viewport()->_gui_get_focus_owner(); -} - void Control::warp_mouse(const Point2 &p_to_pos) { ERR_FAIL_COND(!is_inside_tree()); get_viewport()->warp_mouse(get_global_transform().xform(p_to_pos)); @@ -2894,7 +2888,6 @@ void Control::_bind_methods() { ClassDB::bind_method(D_METHOD("release_focus"), &Control::release_focus); ClassDB::bind_method(D_METHOD("find_prev_valid_focus"), &Control::find_prev_valid_focus); ClassDB::bind_method(D_METHOD("find_next_valid_focus"), &Control::find_next_valid_focus); - ClassDB::bind_method(D_METHOD("get_focus_owner"), &Control::get_focus_owner); ClassDB::bind_method(D_METHOD("set_h_size_flags", "flags"), &Control::set_h_size_flags); ClassDB::bind_method(D_METHOD("get_h_size_flags"), &Control::get_h_size_flags); diff --git a/scene/gui/control.h b/scene/gui/control.h index bf79f790e7..962135280f 100644 --- a/scene/gui/control.h +++ b/scene/gui/control.h @@ -462,8 +462,6 @@ public: void set_focus_previous(const NodePath &p_prev); NodePath get_focus_previous() const; - Control *get_focus_owner() const; - void set_mouse_filter(MouseFilter p_filter); MouseFilter get_mouse_filter() const; diff --git a/scene/gui/label.cpp b/scene/gui/label.cpp index fab420d593..852aaaab24 100644 --- a/scene/gui/label.cpp +++ b/scene/gui/label.cpp @@ -82,9 +82,11 @@ void Label::_shape() { Ref<StyleBox> style = get_theme_stylebox(SNAME("normal"), SNAME("Label")); int width = (get_size().width - style->get_minimum_size().width); - if (dirty) { + if (dirty || font_dirty) { String lang = (!language.is_empty()) ? language : TranslationServer::get_singleton()->get_tool_locale(); - TS->shaped_text_clear(text_rid); + 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 { @@ -97,9 +99,17 @@ void Label::_shape() { if (visible_chars >= 0 && visible_chars_behavior == VC_CHARS_BEFORE_SHAPING) { text = text.substr(0, visible_chars); } - TS->shaped_text_add_string(text_rid, text, font->get_rids(), font_size, opentype_features, lang); + if (dirty) { + TS->shaped_text_add_string(text_rid, text, font->get_rids(), font_size, opentype_features, lang); + } 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, opentype_features); + } + } TS->shaped_text_set_bidi_override(text_rid, structured_text_parser(st_parser, st_args, text)); dirty = false; + font_dirty = false; lines_dirty = true; } @@ -276,7 +286,7 @@ void Label::_notification(int p_what) { RenderingServer::get_singleton()->canvas_item_set_clip(get_canvas_item(), true); } - if (dirty || lines_dirty) { + if (dirty || font_dirty || lines_dirty) { _shape(); } @@ -521,7 +531,7 @@ void Label::_notification(int p_what) { } if (p_what == NOTIFICATION_THEME_CHANGED) { - dirty = true; + font_dirty = true; update(); } if (p_what == NOTIFICATION_RESIZED) { @@ -531,7 +541,7 @@ void Label::_notification(int p_what) { Size2 Label::get_minimum_size() const { // don't want to mutable everything - if (dirty || lines_dirty) { + if (dirty || font_dirty || lines_dirty) { const_cast<Label *>(this)->_shape(); } @@ -555,7 +565,7 @@ int Label::get_line_count() const { if (!is_inside_tree()) { return 1; } - if (dirty || lines_dirty) { + if (dirty || font_dirty || lines_dirty) { const_cast<Label *>(this)->_shape(); } @@ -630,7 +640,7 @@ void Label::set_text_direction(Control::TextDirection p_text_direction) { ERR_FAIL_COND((int)p_text_direction < -1 || (int)p_text_direction > 3); if (text_direction != p_text_direction) { text_direction = p_text_direction; - dirty = true; + font_dirty = true; update(); } } @@ -638,7 +648,7 @@ void Label::set_text_direction(Control::TextDirection p_text_direction) { void Label::set_structured_text_bidi_override(Control::StructuredTextParser p_parser) { if (st_parser != p_parser) { st_parser = p_parser; - dirty = true; + font_dirty = true; update(); } } @@ -649,7 +659,7 @@ Control::StructuredTextParser Label::get_structured_text_bidi_override() const { void Label::set_structured_text_bidi_override_options(Array p_args) { st_args = p_args; - dirty = true; + font_dirty = true; update(); } @@ -663,7 +673,7 @@ Control::TextDirection Label::get_text_direction() const { void Label::clear_opentype_features() { opentype_features.clear(); - dirty = true; + font_dirty = true; update(); } @@ -671,7 +681,7 @@ void Label::set_opentype_feature(const String &p_name, int p_value) { int32_t tag = TS->name_to_tag(p_name); if (!opentype_features.has(tag) || (int)opentype_features[tag] != p_value) { opentype_features[tag] = p_value; - dirty = true; + font_dirty = true; update(); } } @@ -798,7 +808,7 @@ int Label::get_max_lines_visible() const { } int Label::get_total_character_count() const { - if (dirty || lines_dirty) { + if (dirty || font_dirty || lines_dirty) { const_cast<Label *>(this)->_shape(); } @@ -814,13 +824,13 @@ bool Label::_set(const StringName &p_name, const Variant &p_value) { if (value == -1) { if (opentype_features.has(tag)) { opentype_features.erase(tag); - dirty = true; + font_dirty = true; update(); } } else { if (!opentype_features.has(tag) || (int)opentype_features[tag] != value) { opentype_features[tag] = value; - dirty = true; + font_dirty = true; update(); } } diff --git a/scene/gui/label.h b/scene/gui/label.h index 354e9c664d..0b931b3084 100644 --- a/scene/gui/label.h +++ b/scene/gui/label.h @@ -73,6 +73,7 @@ private: bool lines_dirty = true; bool dirty = true; + bool font_dirty = true; RID text_rid; Vector<RID> lines_rid; diff --git a/scene/gui/menu_button.cpp b/scene/gui/menu_button.cpp index f7805136f9..a985a9d031 100644 --- a/scene/gui/menu_button.cpp +++ b/scene/gui/menu_button.cpp @@ -98,7 +98,13 @@ void MenuButton::pressed() { popup->set_position(gp); popup->set_parent_rect(Rect2(Point2(gp - popup->get_position()), size)); - popup->take_mouse_focus(); + // If not triggered by the mouse, start the popup with its first item selected. + if (popup->get_item_count() > 0 && + ((get_action_mode() == ActionMode::ACTION_MODE_BUTTON_PRESS && Input::get_singleton()->is_action_just_pressed("ui_accept")) || + (get_action_mode() == ActionMode::ACTION_MODE_BUTTON_RELEASE && Input::get_singleton()->is_action_just_released("ui_accept")))) { + popup->set_current_index(0); + } + popup->popup(); } diff --git a/scene/gui/option_button.cpp b/scene/gui/option_button.cpp index c80de04c01..9984ab240a 100644 --- a/scene/gui/option_button.cpp +++ b/scene/gui/option_button.cpp @@ -181,7 +181,14 @@ void OptionButton::pressed() { Size2 size = get_size() * get_viewport()->get_canvas_transform().get_scale(); popup->set_position(get_screen_position() + Size2(0, size.height * get_global_transform().get_scale().y)); popup->set_size(Size2(size.width, 0)); - popup->set_current_index(current); + + // If not triggered by the mouse, start the popup with the checked item selected. + if (popup->get_item_count() > 0 && + ((get_action_mode() == ActionMode::ACTION_MODE_BUTTON_PRESS && Input::get_singleton()->is_action_just_pressed("ui_accept")) || + (get_action_mode() == ActionMode::ACTION_MODE_BUTTON_RELEASE && Input::get_singleton()->is_action_just_released("ui_accept")))) { + popup->set_current_index(current > -1 ? current : 0); + } + popup->popup(); } diff --git a/scene/gui/popup_menu.cpp b/scene/gui/popup_menu.cpp index fc853a3df4..812339dc19 100644 --- a/scene/gui/popup_menu.cpp +++ b/scene/gui/popup_menu.cpp @@ -192,7 +192,7 @@ void PopupMenu::_activate_submenu(int p_over) { Popup *submenu_popup = Object::cast_to<Popup>(n); ERR_FAIL_COND_MSG(!submenu_popup, "Item subnode is not a Popup: " + items[p_over].submenu + "."); if (submenu_popup->is_visible()) { - return; //already visible! + return; // Already visible. } Ref<StyleBox> style = get_theme_stylebox(SNAME("panel")); @@ -223,24 +223,33 @@ void PopupMenu::_activate_submenu(int p_over) { submenu_popup->set_close_on_parent_focus(false); submenu_popup->set_position(submenu_pos); submenu_popup->set_as_minsize(); // Shrink the popup size to its contents. - submenu_popup->popup(); - // Set autohide areas PopupMenu *submenu_pum = Object::cast_to<PopupMenu>(submenu_popup); - if (submenu_pum) { - submenu_pum->take_mouse_focus(); - // Make the position of the parent popup relative to submenu popup - this_rect.position = this_rect.position - submenu_pum->get_position(); - - // Autohide area above the submenu item - submenu_pum->clear_autohide_areas(); - submenu_pum->add_autohide_area(Rect2(this_rect.position.x, this_rect.position.y, this_rect.size.x, items[p_over]._ofs_cache + scroll_offset + style->get_offset().height - vsep / 2)); - - // If there is an area below the submenu item, add an autohide area there. - if (items[p_over]._ofs_cache + items[p_over]._height_cache + scroll_offset <= control->get_size().height) { - int from = items[p_over]._ofs_cache + items[p_over]._height_cache + scroll_offset + vsep / 2 + style->get_offset().height; - submenu_pum->add_autohide_area(Rect2(this_rect.position.x, this_rect.position.y + from, this_rect.size.x, this_rect.size.y - from)); - } + if (!submenu_pum) { + submenu_popup->popup(); + return; + } + + // If not triggered by the mouse, start the popup with its first item selected. + if (submenu_pum->get_item_count() > 0 && Input::get_singleton()->is_action_just_pressed("ui_accept")) { + submenu_pum->set_current_index(0); + } + + submenu_pum->popup(); + + // Set autohide areas. + + // Make the position of the parent popup relative to submenu popup. + this_rect.position = this_rect.position - submenu_pum->get_position(); + + // Autohide area above the submenu item. + submenu_pum->clear_autohide_areas(); + submenu_pum->add_autohide_area(Rect2(this_rect.position.x, this_rect.position.y, this_rect.size.x, items[p_over]._ofs_cache + scroll_offset + style->get_offset().height - vsep / 2)); + + // If there is an area below the submenu item, add an autohide area there. + if (items[p_over]._ofs_cache + items[p_over]._height_cache + scroll_offset <= control->get_size().height) { + int from = items[p_over]._ofs_cache + items[p_over]._height_cache + scroll_offset + vsep / 2 + style->get_offset().height; + submenu_pum->add_autohide_area(Rect2(this_rect.position.x, this_rect.position.y + from, this_rect.size.x, this_rect.size.y - from)); } } @@ -1747,6 +1756,7 @@ void PopupMenu::_bind_methods() { ClassDB::bind_method(D_METHOD("get_item_tooltip", "index"), &PopupMenu::get_item_tooltip); ClassDB::bind_method(D_METHOD("get_item_shortcut", "index"), &PopupMenu::get_item_shortcut); + ClassDB::bind_method(D_METHOD("set_current_index", "index"), &PopupMenu::set_current_index); ClassDB::bind_method(D_METHOD("get_current_index"), &PopupMenu::get_current_index); ClassDB::bind_method(D_METHOD("set_item_count", "count"), &PopupMenu::set_item_count); ClassDB::bind_method(D_METHOD("get_item_count"), &PopupMenu::get_item_count); diff --git a/scene/gui/rich_text_label.cpp b/scene/gui/rich_text_label.cpp index 151ae2f092..4865b9770e 100644 --- a/scene/gui/rich_text_label.cpp +++ b/scene/gui/rich_text_label.cpp @@ -205,6 +205,49 @@ String RichTextLabel::_letters(int p_num, bool p_capitalize) const { return s; } +void RichTextLabel::_update_line_font(ItemFrame *p_frame, int p_line, const Ref<Font> &p_base_font, int p_base_font_size) { + ERR_FAIL_COND(p_frame == nullptr); + ERR_FAIL_COND(p_line < 0 || p_line >= p_frame->lines.size()); + + Line &l = p_frame->lines.write[p_line]; + + RID t = l.text_buf->get_rid(); + int spans = TS->shaped_get_span_count(t); + for (int i = 0; i < spans; i++) { + ItemText *it = (ItemText *)(uint64_t)TS->shaped_get_span_meta(t, i); + if (it) { + Ref<Font> font = _find_font(it); + if (font.is_null()) { + font = p_base_font; + } + int font_size = _find_font_size(it); + if (font_size == -1) { + font_size = p_base_font_size; + } + Dictionary font_ftr = _find_font_features(it); + TS->shaped_set_span_update_font(t, i, font->get_rids(), font_size, font_ftr); + } + } + + Item *it_to = (p_line + 1 < p_frame->lines.size()) ? p_frame->lines[p_line + 1].from : nullptr; + for (Item *it = l.from; it && it != it_to; it = _get_next_item(it)) { + switch (it->type) { + case ITEM_TABLE: { + ItemTable *table = static_cast<ItemTable *>(it); + for (Item *E : table->subitems) { + ERR_CONTINUE(E->type != ITEM_FRAME); // Children should all be frames. + ItemFrame *frame = static_cast<ItemFrame *>(E); + for (int i = 0; i < frame->lines.size(); i++) { + _update_line_font(frame, i, p_base_font, p_base_font_size); + } + } + } break; + default: + break; + } + } +} + void RichTextLabel::_resize_line(ItemFrame *p_frame, int p_line, const Ref<Font> &p_base_font, int p_base_font_size, int p_width) { ERR_FAIL_COND(p_frame == nullptr); ERR_FAIL_COND(p_line < 0 || p_line >= p_frame->lines.size()); @@ -378,9 +421,24 @@ void RichTextLabel::_shape_line(ItemFrame *p_frame, int p_line, const Ref<Font> Line &l = p_frame->lines.write[p_line]; + uint16_t autowrap_flags = TextServer::BREAK_MANDATORY; + switch (autowrap_mode) { + case AUTOWRAP_WORD_SMART: + autowrap_flags = TextServer::BREAK_WORD_BOUND_ADAPTIVE | TextServer::BREAK_MANDATORY; + break; + case AUTOWRAP_WORD: + autowrap_flags = TextServer::BREAK_WORD_BOUND | TextServer::BREAK_MANDATORY; + break; + case AUTOWRAP_ARBITRARY: + autowrap_flags = TextServer::BREAK_GRAPHEME_BOUND | TextServer::BREAK_MANDATORY; + break; + case AUTOWRAP_OFF: + break; + } + // Clear cache. l.text_buf->clear(); - l.text_buf->set_flags(TextServer::BREAK_MANDATORY | TextServer::BREAK_WORD_BOUND | TextServer::JUSTIFICATION_KASHIDA | TextServer::JUSTIFICATION_WORD_BOUND | TextServer::JUSTIFICATION_TRIM_EDGE_SPACES); + l.text_buf->set_flags(autowrap_flags | TextServer::JUSTIFICATION_KASHIDA | TextServer::JUSTIFICATION_WORD_BOUND | TextServer::JUSTIFICATION_TRIM_EDGE_SPACES); l.char_offset = *r_char_offset; l.char_count = 0; @@ -449,7 +507,7 @@ void RichTextLabel::_shape_line(ItemFrame *p_frame, int p_line, const Ref<Font> } remaining_characters -= tx.length(); - l.text_buf->add_string(tx, font, font_size, font_ftr, lang); + l.text_buf->add_string(tx, font, font_size, font_ftr, lang, (uint64_t)it); text += tx; l.char_count += tx.length(); } break; @@ -1448,7 +1506,10 @@ void RichTextLabel::_notification(int p_what) { update(); } break; - case NOTIFICATION_THEME_CHANGED: + case NOTIFICATION_THEME_CHANGED: { + main->first_invalid_font_line = 0; //invalidate ALL + update(); + } break; case NOTIFICATION_ENTER_TREE: { if (!text.is_empty()) { set_text(text); @@ -1545,6 +1606,10 @@ Control::CursorShape RichTextLabel::get_cursor_shape(const Point2 &p_pos) const return get_default_cursor_shape(); //invalid } + if (main->first_invalid_font_line < main->lines.size()) { + return get_default_cursor_shape(); //invalid + } + if (main->first_resized_line < main->lines.size()) { return get_default_cursor_shape(); //invalid } @@ -1569,6 +1634,9 @@ void RichTextLabel::gui_input(const Ref<InputEvent> &p_event) { if (main->first_invalid_line < main->lines.size()) { return; } + if (main->first_invalid_font_line < main->lines.size()) { + return; + } if (main->first_resized_line < main->lines.size()) { return; } @@ -1732,6 +1800,9 @@ void RichTextLabel::gui_input(const Ref<InputEvent> &p_event) { if (main->first_invalid_line < main->lines.size()) { return; } + if (main->first_invalid_font_line < main->lines.size()) { + return; + } if (main->first_resized_line < main->lines.size()) { return; } @@ -2184,6 +2255,18 @@ bool RichTextLabel::_find_layout_subitem(Item *from, Item *to) { void RichTextLabel::_validate_line_caches(ItemFrame *p_frame) { if (p_frame->first_invalid_line == p_frame->lines.size()) { + Ref<Font> base_font = get_theme_font(SNAME("normal_font")); + int base_font_size = get_theme_font_size(SNAME("normal_font_size")); + + // Update fonts. + if (p_frame->first_invalid_font_line != p_frame->lines.size()) { + for (int i = p_frame->first_invalid_font_line; i < p_frame->lines.size(); i++) { + _update_line_font(p_frame, i, base_font, base_font_size); + } + p_frame->first_resized_line = p_frame->first_invalid_font_line; + p_frame->first_invalid_font_line = p_frame->lines.size(); + } + if (p_frame->first_resized_line == p_frame->lines.size()) { return; } @@ -2191,9 +2274,6 @@ void RichTextLabel::_validate_line_caches(ItemFrame *p_frame) { // Resize lines without reshaping. Rect2 text_rect = _get_text_rect(); - Ref<Font> base_font = get_theme_font(SNAME("normal_font")); - int base_font_size = get_theme_font_size(SNAME("normal_font_size")); - for (int i = p_frame->first_resized_line; i < p_frame->lines.size(); i++) { _resize_line(p_frame, i, base_font, base_font_size, text_rect.get_size().width - scroll_w); } @@ -2237,6 +2317,7 @@ void RichTextLabel::_validate_line_caches(ItemFrame *p_frame) { p_frame->first_invalid_line = p_frame->lines.size(); p_frame->first_resized_line = p_frame->lines.size(); + p_frame->first_invalid_font_line = p_frame->lines.size(); updating_scroll = true; vscroll->set_max(total_height); @@ -4011,6 +4092,19 @@ String RichTextLabel::get_language() const { return language; } +void RichTextLabel::set_autowrap_mode(RichTextLabel::AutowrapMode p_mode) { + if (autowrap_mode != p_mode) { + autowrap_mode = p_mode; + main->first_invalid_line = 0; //invalidate ALL + _validate_line_caches(main); + update(); + } +} + +RichTextLabel::AutowrapMode RichTextLabel::get_autowrap_mode() const { + return autowrap_mode; +} + void RichTextLabel::set_percent_visible(float p_percent) { if (percent_visible != p_percent) { if (p_percent < 0 || p_percent >= 1) { @@ -4122,6 +4216,9 @@ void RichTextLabel::_bind_methods() { ClassDB::bind_method(D_METHOD("set_language", "language"), &RichTextLabel::set_language); ClassDB::bind_method(D_METHOD("get_language"), &RichTextLabel::get_language); + ClassDB::bind_method(D_METHOD("set_autowrap_mode", "autowrap_mode"), &RichTextLabel::set_autowrap_mode); + ClassDB::bind_method(D_METHOD("get_autowrap_mode"), &RichTextLabel::get_autowrap_mode); + ClassDB::bind_method(D_METHOD("set_meta_underline", "enable"), &RichTextLabel::set_meta_underline); ClassDB::bind_method(D_METHOD("is_meta_underlined"), &RichTextLabel::is_meta_underlined); @@ -4214,6 +4311,8 @@ void RichTextLabel::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::INT, "text_direction", PROPERTY_HINT_ENUM, "Auto,Left-to-Right,Right-to-Left,Inherited"), "set_text_direction", "get_text_direction"); ADD_PROPERTY(PropertyInfo(Variant::STRING, "language", PROPERTY_HINT_LOCALE_ID, ""), "set_language", "get_language"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "autowrap_mode", PROPERTY_HINT_ENUM, "Off,Arbitrary,Word,Word (Smart)"), "set_autowrap_mode", "get_autowrap_mode"); + ADD_GROUP("Structured Text", "structured_text_"); ADD_PROPERTY(PropertyInfo(Variant::INT, "structured_text_bidi_override", PROPERTY_HINT_ENUM, "Default,URI,File,Email,List,None,Custom"), "set_structured_text_bidi_override", "get_structured_text_bidi_override"); ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "structured_text_bidi_override_options"), "set_structured_text_bidi_override_options", "get_structured_text_bidi_override_options"); @@ -4222,6 +4321,11 @@ void RichTextLabel::_bind_methods() { ADD_SIGNAL(MethodInfo("meta_hover_started", PropertyInfo(Variant::NIL, "meta", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NIL_IS_VARIANT))); ADD_SIGNAL(MethodInfo("meta_hover_ended", PropertyInfo(Variant::NIL, "meta", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NIL_IS_VARIANT))); + BIND_ENUM_CONSTANT(AUTOWRAP_OFF); + BIND_ENUM_CONSTANT(AUTOWRAP_ARBITRARY); + BIND_ENUM_CONSTANT(AUTOWRAP_WORD); + BIND_ENUM_CONSTANT(AUTOWRAP_WORD_SMART); + BIND_ENUM_CONSTANT(LIST_NUMBERS); BIND_ENUM_CONSTANT(LIST_LETTERS); BIND_ENUM_CONSTANT(LIST_ROMAN); @@ -4494,6 +4598,7 @@ RichTextLabel::RichTextLabel() { main->lines.write[0].from = main; main->first_invalid_line = 0; main->first_resized_line = 0; + main->first_invalid_font_line = 0; current_frame = main; vscroll = memnew(VScrollBar); diff --git a/scene/gui/rich_text_label.h b/scene/gui/rich_text_label.h index 70467e7e7c..e79244f2e4 100644 --- a/scene/gui/rich_text_label.h +++ b/scene/gui/rich_text_label.h @@ -39,6 +39,13 @@ class RichTextLabel : public Control { GDCLASS(RichTextLabel, Control); public: + enum AutowrapMode { + AUTOWRAP_OFF, + AUTOWRAP_ARBITRARY, + AUTOWRAP_WORD, + AUTOWRAP_WORD_SMART + }; + enum ListType { LIST_NUMBERS, LIST_LETTERS, @@ -129,6 +136,7 @@ private: Vector<Line> lines; int first_invalid_line = 0; + int first_invalid_font_line = 0; int first_resized_line = 0; ItemFrame *parent_frame = nullptr; @@ -345,6 +353,8 @@ private: VScrollBar *vscroll = nullptr; + AutowrapMode autowrap_mode = AUTOWRAP_WORD_SMART; + bool scroll_visible = false; bool scroll_follow = false; bool scroll_following = false; @@ -414,6 +424,7 @@ private: void _shape_line(ItemFrame *p_frame, int p_line, const Ref<Font> &p_base_font, int p_base_font_size, int p_width, int *r_char_offset); void _resize_line(ItemFrame *p_frame, int p_line, const Ref<Font> &p_base_font, int p_base_font_size, int p_width); + void _update_line_font(ItemFrame *p_frame, int p_line, const Ref<Font> &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); @@ -572,6 +583,9 @@ public: void set_language(const String &p_language); String get_language() const; + void set_autowrap_mode(AutowrapMode p_mode); + AutowrapMode get_autowrap_mode() const; + void set_structured_text_bidi_override(Control::StructuredTextParser p_parser); Control::StructuredTextParser get_structured_text_bidi_override() const; @@ -601,6 +615,7 @@ public: ~RichTextLabel(); }; +VARIANT_ENUM_CAST(RichTextLabel::AutowrapMode); VARIANT_ENUM_CAST(RichTextLabel::ListType); VARIANT_ENUM_CAST(RichTextLabel::ItemType); VARIANT_ENUM_CAST(RichTextLabel::VisibleCharactersBehavior); diff --git a/scene/gui/text_edit.cpp b/scene/gui/text_edit.cpp index e060d3b901..0ee4a6af4e 100644 --- a/scene/gui/text_edit.cpp +++ b/scene/gui/text_edit.cpp @@ -187,29 +187,44 @@ void TextEdit::Text::_calculate_max_line_width() { max_width = width; } -void TextEdit::Text::invalidate_cache(int p_line, int p_column, const String &p_ime_text, const Array &p_bidi_override) { +void TextEdit::Text::invalidate_cache(int p_line, int p_column, bool p_text_changed, const String &p_ime_text, const Array &p_bidi_override) { ERR_FAIL_INDEX(p_line, text.size()); if (font.is_null() || font_size <= 0) { return; // Not in tree? } - text.write[p_line].data_buf->clear(); + if (p_text_changed) { + text.write[p_line].data_buf->clear(); + } + text.write[p_line].data_buf->set_width(width); text.write[p_line].data_buf->set_direction((TextServer::Direction)direction); text.write[p_line].data_buf->set_preserve_control(draw_control_chars); if (p_ime_text.length() > 0) { - text.write[p_line].data_buf->add_string(p_ime_text, font, font_size, opentype_features, language); + if (p_text_changed) { + text.write[p_line].data_buf->add_string(p_ime_text, font, font_size, opentype_features, language); + } if (!p_bidi_override.is_empty()) { TS->shaped_text_set_bidi_override(text.write[p_line].data_buf->get_rid(), p_bidi_override); } } else { - text.write[p_line].data_buf->add_string(text[p_line].data, font, font_size, opentype_features, language); + if (p_text_changed) { + text.write[p_line].data_buf->add_string(text[p_line].data, font, font_size, opentype_features, language); + } if (!text[p_line].bidi_override.is_empty()) { TS->shaped_text_set_bidi_override(text.write[p_line].data_buf->get_rid(), text[p_line].bidi_override); } } + if (!p_text_changed) { + RID r = text.write[p_line].data_buf->get_rid(); + int spans = TS->shaped_get_span_count(r); + for (int i = 0; i < spans; i++) { + TS->shaped_set_span_update_font(r, i, font->get_rids(), font_size, opentype_features); + } + } + // Apply tab align. if (tab_size > 0) { Vector<float> tabs; @@ -266,6 +281,24 @@ void TextEdit::Text::invalidate_all_lines() { } } +void TextEdit::Text::invalidate_font() { + if (!is_dirty) { + return; + } + + max_width = -1; + line_height = -1; + + if (!font.is_null() && font_size > 0) { + font_height = font->get_height(font_size); + } + + for (int i = 0; i < text.size(); i++) { + invalidate_cache(i, -1, false); + } + is_dirty = false; +} + void TextEdit::Text::invalidate_all() { if (!is_dirty) { return; @@ -279,7 +312,7 @@ void TextEdit::Text::invalidate_all() { } for (int i = 0; i < text.size(); i++) { - invalidate_cache(i); + invalidate_cache(i, -1, true); } is_dirty = false; } @@ -294,7 +327,7 @@ void TextEdit::Text::clear() { line.gutters.resize(gutter_count); line.data = ""; text.insert(0, line); - invalidate_cache(0); + invalidate_cache(0, -1, true); } int TextEdit::Text::get_max_width() const { @@ -306,7 +339,7 @@ void TextEdit::Text::set(int p_line, const String &p_text, const Array &p_bidi_o text.write[p_line].data = p_text; text.write[p_line].bidi_override = p_bidi_override; - invalidate_cache(p_line); + invalidate_cache(p_line, -1, true); } void TextEdit::Text::insert(int p_at, const Vector<String> &p_text, const Vector<Array> &p_bidi_override) { @@ -331,7 +364,7 @@ void TextEdit::Text::insert(int p_at, const Vector<String> &p_text, const Vector line.data = p_text[i]; line.bidi_override = p_bidi_override[i]; text.write[p_at + i] = line; - invalidate_cache(p_at + i); + invalidate_cache(p_at + i, -1, true); } } @@ -1446,9 +1479,11 @@ void TextEdit::_notification(int p_what) { DisplayServer::get_singleton()->window_set_ime_position(Point2(), get_viewport()->get_window_id()); DisplayServer::get_singleton()->window_set_ime_active(false, get_viewport()->get_window_id()); } - ime_text = ""; - ime_selection = Point2(); - text.invalidate_cache(caret.line, caret.column, ime_text); + if (!ime_text.is_empty()) { + ime_text = ""; + ime_selection = Point2(); + text.invalidate_cache(caret.line, caret.column, true, ime_text); + } if (DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_VIRTUAL_KEYBOARD) && virtual_keyboard_enabled) { DisplayServer::get_singleton()->virtual_keyboard_hide(); @@ -1470,7 +1505,7 @@ void TextEdit::_notification(int p_what) { t = ime_text; } - text.invalidate_cache(caret.line, caret.column, t, structured_text_parser(st_parser, st_args, t)); + text.invalidate_cache(caret.line, caret.column, true, t, structured_text_parser(st_parser, st_args, t)); update(); } } break; @@ -2538,7 +2573,7 @@ void TextEdit::_update_caches() { text.set_draw_control_chars(draw_control_chars); text.set_font(font); text.set_font_size(font_size); - text.invalidate_all(); + text.invalidate_font(); _update_placeholder(); /* Syntax highlighting. */ @@ -2718,7 +2753,7 @@ void TextEdit::set_text_direction(Control::TextDirection p_text_direction) { dir = (TextServer::Direction)text_direction; } text.set_direction_and_language(dir, (!language.is_empty()) ? language : TranslationServer::get_singleton()->get_tool_locale()); - text.invalidate_all(); + text.invalidate_font(); _update_placeholder(); if (menu_dir) { @@ -2740,7 +2775,7 @@ void TextEdit::set_opentype_feature(const String &p_name, int p_value) { if (!opentype_features.has(tag) || (int)opentype_features[tag] != p_value) { opentype_features[tag] = p_value; text.set_font_features(opentype_features); - text.invalidate_all(); + text.invalidate_font(); _update_placeholder(); update(); } @@ -2757,7 +2792,7 @@ int TextEdit::get_opentype_feature(const String &p_name) const { void TextEdit::clear_opentype_features() { opentype_features.clear(); text.set_font_features(opentype_features); - text.invalidate_all(); + text.invalidate_font(); _update_placeholder(); update(); } @@ -4852,7 +4887,7 @@ void TextEdit::set_draw_control_chars(bool p_enabled) { menu->set_item_checked(menu->get_item_index(MENU_DISPLAY_UCC), draw_control_chars); } text.set_draw_control_chars(draw_control_chars); - text.invalidate_all(); + text.invalidate_font(); _update_placeholder(); update(); } @@ -5319,7 +5354,7 @@ bool TextEdit::_set(const StringName &p_name, const Variant &p_value) { if (opentype_features.has(tag)) { opentype_features.erase(tag); text.set_font_features(opentype_features); - text.invalidate_all(); + text.invalidate_font(); _update_placeholder(); update(); } @@ -5327,7 +5362,7 @@ bool TextEdit::_set(const StringName &p_name, const Variant &p_value) { if (!opentype_features.has(tag) || (int)opentype_features[tag] != value) { opentype_features[tag] = value; text.set_font_features(opentype_features); - text.invalidate_all(); + text.invalidate_font(); _update_placeholder(); update(); } diff --git a/scene/gui/text_edit.h b/scene/gui/text_edit.h index 079890249e..83a63ae40a 100644 --- a/scene/gui/text_edit.h +++ b/scene/gui/text_edit.h @@ -210,7 +210,8 @@ private: int size() const { return text.size(); } void clear(); - void invalidate_cache(int p_line, int p_column = -1, const String &p_ime_text = String(), const Array &p_bidi_override = Array()); + void invalidate_cache(int p_line, int p_column = -1, bool p_text_changed = false, const String &p_ime_text = String(), const Array &p_bidi_override = Array()); + void invalidate_font(); void invalidate_all(); void invalidate_all_lines(); |