diff options
Diffstat (limited to 'scene/gui')
31 files changed, 2373 insertions, 1317 deletions
diff --git a/scene/gui/base_button.cpp b/scene/gui/base_button.cpp index 01e3cce78b..72299f788d 100644 --- a/scene/gui/base_button.cpp +++ b/scene/gui/base_button.cpp @@ -140,11 +140,13 @@ void BaseButton::_pressed() { void BaseButton::_toggled(bool p_pressed) { GDVIRTUAL_CALL(_toggled, p_pressed); toggled(p_pressed); - emit_signal(SNAME("toggled"), p_pressed); + emit_signal(SceneStringName(toggled), p_pressed); } void BaseButton::on_action_event(Ref<InputEvent> p_event) { - if (p_event->is_pressed()) { + Ref<InputEventMouseButton> mouse_button = p_event; + + if (p_event->is_pressed() && (mouse_button.is_null() || status.hovering)) { status.press_attempt = true; status.pressing_inside = true; emit_signal(SNAME("button_down")); @@ -174,12 +176,6 @@ void BaseButton::on_action_event(Ref<InputEvent> p_event) { } if (!p_event->is_pressed()) { - Ref<InputEventMouseButton> mouse_button = p_event; - if (mouse_button.is_valid()) { - if (!has_point(mouse_button->get_position())) { - status.hovering = false; - } - } status.press_attempt = false; status.pressing_inside = false; emit_signal(SNAME("button_up")); diff --git a/scene/gui/code_edit.cpp b/scene/gui/code_edit.cpp index 00b9a3478a..c3287035ff 100644 --- a/scene/gui/code_edit.cpp +++ b/scene/gui/code_edit.cpp @@ -40,8 +40,18 @@ void CodeEdit::_notification(int p_what) { switch (p_what) { case NOTIFICATION_THEME_CHANGED: { set_gutter_width(main_gutter, get_line_height()); - set_gutter_width(line_number_gutter, (line_number_digits + 1) * theme_cache.font->get_char_size('0', theme_cache.font_size).width); + _update_line_number_gutter_width(); set_gutter_width(fold_gutter, get_line_height() / 1.2); + _clear_line_number_text_cache(); + } break; + + case NOTIFICATION_TRANSLATION_CHANGED: + [[fallthrough]]; + case NOTIFICATION_LAYOUT_DIRECTION_CHANGED: + [[fallthrough]]; + case NOTIFICATION_VISIBILITY_CHANGED: { + // Avoid having many hidden text editors with unused cache filling up memory. + _clear_line_number_text_cache(); } break; case NOTIFICATION_DRAW: { @@ -211,7 +221,13 @@ void CodeEdit::_notification(int p_what) { tl->set_horizontal_alignment(HORIZONTAL_ALIGNMENT_RIGHT); } else { if (code_completion_options[l].default_value.get_type() == Variant::COLOR) { - draw_rect(Rect2(Point2(code_completion_rect.position.x + code_completion_rect.size.width - icon_area_size.x, icon_area.position.y), icon_area_size), (Color)code_completion_options[l].default_value); + const Color color = code_completion_options[l].default_value; + const Rect2 rect = Rect2(Point2(code_completion_rect.position.x + code_completion_rect.size.width - icon_area_size.x, icon_area.position.y), icon_area_size); + if (color.a < 1.0) { + draw_texture_rect(theme_cache.completion_color_bg, rect, true); + } + + draw_rect(rect, color); } tl->set_horizontal_alignment(HORIZONTAL_ALIGNMENT_LEFT); } @@ -765,7 +781,7 @@ void CodeEdit::_backspace_internal(int p_caret) { continue; } - if (to_line > 0 && _is_line_hidden(to_line - 1)) { + if (to_line > 0 && to_column == 0 && _is_line_hidden(to_line - 1)) { unfold_line(to_line - 1); } @@ -1281,9 +1297,9 @@ bool CodeEdit::is_drawing_executing_lines_gutter() const { } void CodeEdit::_main_gutter_draw_callback(int p_line, int p_gutter, const Rect2 &p_region) { + bool hovering = get_hovered_gutter() == Vector2i(main_gutter, p_line); if (draw_breakpoints && theme_cache.breakpoint_icon.is_valid()) { bool breakpointed = is_line_breakpointed(p_line); - bool hovering = p_region.has_point(get_local_mouse_pos()); bool shift_pressed = Input::get_singleton()->is_key_pressed(Key::SHIFT); if (breakpointed || (hovering && !is_dragging_cursor() && !shift_pressed)) { @@ -1302,7 +1318,6 @@ void CodeEdit::_main_gutter_draw_callback(int p_line, int p_gutter, const Rect2 if (draw_bookmarks && theme_cache.bookmark_icon.is_valid()) { bool bookmarked = is_line_bookmarked(p_line); - bool hovering = p_region.has_point(get_local_mouse_pos()); bool shift_pressed = Input::get_singleton()->is_key_pressed(Key::SHIFT); if (bookmarked || (hovering && !is_dragging_cursor() && shift_pressed)) { @@ -1436,7 +1451,13 @@ bool CodeEdit::is_draw_line_numbers_enabled() const { } void CodeEdit::set_line_numbers_zero_padded(bool p_zero_padded) { - p_zero_padded ? line_number_padding = "0" : line_number_padding = " "; + String new_line_number_padding = p_zero_padded ? "0" : " "; + if (line_number_padding == new_line_number_padding) { + return; + } + + line_number_padding = new_line_number_padding; + _clear_line_number_text_cache(); queue_redraw(); } @@ -1445,19 +1466,55 @@ bool CodeEdit::is_line_numbers_zero_padded() const { } void CodeEdit::_line_number_draw_callback(int p_line, int p_gutter, const Rect2 &p_region) { - String fc = String::num(p_line + 1).lpad(line_number_digits, line_number_padding); - if (is_localizing_numeral_system()) { - fc = TS->format_number(fc); - } - Ref<TextLine> tl; - tl.instantiate(); - tl->add_string(fc, theme_cache.font, theme_cache.font_size); - int yofs = p_region.position.y + (get_line_height() - tl->get_size().y) / 2; + if (!Rect2(Vector2(0, 0), get_size()).intersects(p_region)) { + return; + } + + bool rtl = is_layout_rtl(); + HashMap<int, RID>::Iterator E = line_number_text_cache.find(p_line); + RID text_rid; + if (E) { + text_rid = E->value; + } else { + String fc = String::num(p_line + 1).lpad(line_number_digits, line_number_padding); + if (is_localizing_numeral_system()) { + fc = TS->format_number(fc); + } + + text_rid = TS->create_shaped_text(); + if (theme_cache.font.is_valid()) { + TS->shaped_text_add_string(text_rid, fc, theme_cache.font->get_rids(), theme_cache.font_size, theme_cache.font->get_opentype_features()); + } + line_number_text_cache.insert(p_line, text_rid); + } + + Size2 text_size = TS->shaped_text_get_size(text_rid); + Point2 ofs = p_region.get_center() - text_size / 2; + ofs.y += TS->shaped_text_get_ascent(text_rid); + + if (rtl) { + ofs.x = p_region.position.x; + } else { + ofs.x = p_region.get_end().x - text_size.width; + } + Color number_color = get_line_gutter_item_color(p_line, line_number_gutter); if (number_color == Color(1, 1, 1)) { number_color = theme_cache.line_number_color; } - tl->draw(get_canvas_item(), Point2(p_region.position.x, yofs), number_color); + + TS->shaped_text_draw(text_rid, get_canvas_item(), ofs, -1, -1, number_color); +} + +void CodeEdit::_clear_line_number_text_cache() { + for (const KeyValue<int, RID> &KV : line_number_text_cache) { + TS->free_rid(KV.value); + } + line_number_text_cache.clear(); +} + +void CodeEdit::_update_line_number_gutter_width() { + set_gutter_width(line_number_gutter, (line_number_digits + 1) * theme_cache.font->get_char_size('0', theme_cache.font_size).width); } /* Fold Gutter */ @@ -1977,12 +2034,18 @@ Point2 CodeEdit::get_delimiter_end_position(int p_line, int p_column) const { /* Code hint */ void CodeEdit::set_code_hint(const String &p_hint) { + if (code_hint == p_hint) { + return; + } code_hint = p_hint; code_hint_xpos = -0xFFFF; queue_redraw(); } void CodeEdit::set_code_hint_draw_below(bool p_below) { + if (code_hint_draw_below == p_below) { + return; + } code_hint_draw_below = p_below; queue_redraw(); } @@ -2771,6 +2834,7 @@ void CodeEdit::_bind_methods() { BIND_THEME_ITEM_CUSTOM(Theme::DATA_TYPE_ICON, CodeEdit, can_fold_code_region_icon, "can_fold_code_region"); BIND_THEME_ITEM_CUSTOM(Theme::DATA_TYPE_ICON, CodeEdit, folded_code_region_icon, "folded_code_region"); BIND_THEME_ITEM(Theme::DATA_TYPE_ICON, CodeEdit, folded_eol_icon); + BIND_THEME_ITEM(Theme::DATA_TYPE_ICON, CodeEdit, completion_color_bg); BIND_THEME_ITEM(Theme::DATA_TYPE_COLOR, CodeEdit, breakpoint_color); BIND_THEME_ITEM_CUSTOM(Theme::DATA_TYPE_ICON, CodeEdit, breakpoint_icon, "breakpoint"); @@ -3602,16 +3666,13 @@ void CodeEdit::_text_changed() { } int lc = get_line_count(); - line_number_digits = 1; - while (lc /= 10) { - line_number_digits++; - } - - if (theme_cache.font.is_valid()) { - set_gutter_width(line_number_gutter, (line_number_digits + 1) * theme_cache.font->get_char_size('0', theme_cache.font_size).width); + int new_line_number_digits = log10l(lc) + 1; + if (line_number_digits != new_line_number_digits) { + _clear_line_number_text_cache(); } + line_number_digits = new_line_number_digits; + _update_line_number_gutter_width(); - lc = get_line_count(); List<int> breakpoints; for (const KeyValue<int, bool> &E : breakpointed_lines) { breakpoints.push_back(E.key); @@ -3698,6 +3759,7 @@ CodeEdit::CodeEdit() { } CodeEdit::~CodeEdit() { + _clear_line_number_text_cache(); } // Return true if l should come before r diff --git a/scene/gui/code_edit.h b/scene/gui/code_edit.h index 580435f65e..ab443e95e1 100644 --- a/scene/gui/code_edit.h +++ b/scene/gui/code_edit.h @@ -113,6 +113,9 @@ private: int line_number_gutter = -1; int line_number_digits = 1; String line_number_padding = " "; + HashMap<int, RID> line_number_text_cache; + void _clear_line_number_text_cache(); + void _update_line_number_gutter_width(); void _line_number_draw_callback(int p_line, int p_gutter, const Rect2 &p_region); /* Fold Gutter */ @@ -245,6 +248,7 @@ private: Ref<Texture2D> can_fold_code_region_icon; Ref<Texture2D> folded_code_region_icon; Ref<Texture2D> folded_eol_icon; + Ref<Texture2D> completion_color_bg; Color breakpoint_color = Color(1, 1, 1); Ref<Texture2D> breakpoint_icon = Ref<Texture2D>(); diff --git a/scene/gui/color_picker.cpp b/scene/gui/color_picker.cpp index a2aee353f9..c92dcbc153 100644 --- a/scene/gui/color_picker.cpp +++ b/scene/gui/color_picker.cpp @@ -54,6 +54,18 @@ void ColorPicker::_notification(int p_what) { _update_color(); } break; + case NOTIFICATION_READY: { + // FIXME: The embedding check is needed to fix a bug in single-window mode (GH-93718). + if (DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_SCREEN_CAPTURE) && !get_tree()->get_root()->is_embedding_subwindows()) { + btn_pick->set_tooltip_text(ETR("Pick a color from the screen.")); + btn_pick->connect(SceneStringName(pressed), callable_mp(this, &ColorPicker::_pick_button_pressed)); + } else { + // On unsupported platforms, use a legacy method for color picking. + btn_pick->set_tooltip_text(ETR("Pick a color from the application window.")); + btn_pick->connect(SceneStringName(pressed), callable_mp(this, &ColorPicker::_pick_button_pressed_legacy)); + } + } break; + case NOTIFICATION_TRANSLATION_CHANGED: { List<BaseButton *> buttons; preset_group->get_buttons(&buttons); @@ -771,7 +783,7 @@ void ColorPicker::_add_recent_preset_button(int p_size, const Color &p_color) { recent_preset_hbc->add_child(btn_preset_new); recent_preset_hbc->move_child(btn_preset_new, 0); btn_preset_new->set_pressed(true); - btn_preset_new->connect("toggled", callable_mp(this, &ColorPicker::_recent_preset_pressed).bind(btn_preset_new)); + btn_preset_new->connect(SceneStringName(toggled), callable_mp(this, &ColorPicker::_recent_preset_pressed).bind(btn_preset_new)); } void ColorPicker::_show_hide_preset(const bool &p_is_btn_pressed, Button *p_btn_preset, Container *p_preset_container) { @@ -1834,14 +1846,6 @@ ColorPicker::ColorPicker() { btn_pick = memnew(Button); sample_hbc->add_child(btn_pick); - if (DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_SCREEN_CAPTURE)) { - btn_pick->set_tooltip_text(ETR("Pick a color from the screen.")); - btn_pick->connect(SceneStringName(pressed), callable_mp(this, &ColorPicker::_pick_button_pressed)); - } else { - // On unsupported platforms, use a legacy method for color picking. - btn_pick->set_tooltip_text(ETR("Pick a color from the application window.")); - btn_pick->connect(SceneStringName(pressed), callable_mp(this, &ColorPicker::_pick_button_pressed_legacy)); - } sample = memnew(TextureRect); sample_hbc->add_child(sample); @@ -1999,7 +2003,7 @@ ColorPicker::ColorPicker() { btn_preset->set_toggle_mode(true); btn_preset->set_focus_mode(FOCUS_NONE); btn_preset->set_text_alignment(HORIZONTAL_ALIGNMENT_LEFT); - btn_preset->connect("toggled", callable_mp(this, &ColorPicker::_show_hide_preset).bind(btn_preset, preset_container)); + btn_preset->connect(SceneStringName(toggled), callable_mp(this, &ColorPicker::_show_hide_preset).bind(btn_preset, preset_container)); real_vbox->add_child(btn_preset); real_vbox->add_child(preset_container); @@ -2016,7 +2020,7 @@ ColorPicker::ColorPicker() { btn_recent_preset->set_toggle_mode(true); btn_recent_preset->set_focus_mode(FOCUS_NONE); btn_recent_preset->set_text_alignment(HORIZONTAL_ALIGNMENT_LEFT); - btn_recent_preset->connect("toggled", callable_mp(this, &ColorPicker::_show_hide_preset).bind(btn_recent_preset, recent_preset_hbc)); + btn_recent_preset->connect(SceneStringName(toggled), callable_mp(this, &ColorPicker::_show_hide_preset).bind(btn_recent_preset, recent_preset_hbc)); real_vbox->add_child(btn_recent_preset); real_vbox->add_child(recent_preset_hbc); diff --git a/scene/gui/control.compat.inc b/scene/gui/control.compat.inc deleted file mode 100644 index 96ee720d90..0000000000 --- a/scene/gui/control.compat.inc +++ /dev/null @@ -1,48 +0,0 @@ -/**************************************************************************/ -/* control.compat.inc */ -/**************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/**************************************************************************/ -/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ -/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ -/* */ -/* Permission is hereby granted, free of charge, to any person obtaining */ -/* a copy of this software and associated documentation files (the */ -/* "Software"), to deal in the Software without restriction, including */ -/* without limitation the rights to use, copy, modify, merge, publish, */ -/* distribute, sublicense, and/or sell copies of the Software, and to */ -/* permit persons to whom the Software is furnished to do so, subject to */ -/* the following conditions: */ -/* */ -/* The above copyright notice and this permission notice shall be */ -/* included in all copies or substantial portions of the Software. */ -/* */ -/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ -/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ -/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ -/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ -/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ -/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ -/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -/**************************************************************************/ - -#ifndef DISABLE_DEPRECATED - -void Control::_bind_compatibility_methods() { - ClassDB::bind_compatibility_method(D_METHOD("get_theme_icon", "name", "theme_type"), &Control::get_theme_icon, DEFVAL("")); - ClassDB::bind_compatibility_method(D_METHOD("get_theme_stylebox", "name", "theme_type"), &Control::get_theme_stylebox, DEFVAL("")); - ClassDB::bind_compatibility_method(D_METHOD("get_theme_font", "name", "theme_type"), &Control::get_theme_font, DEFVAL("")); - ClassDB::bind_compatibility_method(D_METHOD("get_theme_font_size", "name", "theme_type"), &Control::get_theme_font_size, DEFVAL("")); - ClassDB::bind_compatibility_method(D_METHOD("get_theme_color", "name", "theme_type"), &Control::get_theme_color, DEFVAL("")); - ClassDB::bind_compatibility_method(D_METHOD("get_theme_constant", "name", "theme_type"), &Control::get_theme_constant, DEFVAL("")); - ClassDB::bind_compatibility_method(D_METHOD("has_theme_icon", "name", "theme_type"), &Control::has_theme_icon, DEFVAL("")); - ClassDB::bind_compatibility_method(D_METHOD("has_theme_stylebox", "name", "theme_type"), &Control::has_theme_stylebox, DEFVAL("")); - ClassDB::bind_compatibility_method(D_METHOD("has_theme_font", "name", "theme_type"), &Control::has_theme_font, DEFVAL("")); - ClassDB::bind_compatibility_method(D_METHOD("has_theme_font_size", "name", "theme_type"), &Control::has_theme_font_size, DEFVAL("")); - ClassDB::bind_compatibility_method(D_METHOD("has_theme_color", "name", "theme_type"), &Control::has_theme_color, DEFVAL("")); - ClassDB::bind_compatibility_method(D_METHOD("has_theme_constant", "name", "theme_type"), &Control::has_theme_constant, DEFVAL("")); -} - -#endif diff --git a/scene/gui/control.cpp b/scene/gui/control.cpp index d169e82e5d..cecddebe88 100644 --- a/scene/gui/control.cpp +++ b/scene/gui/control.cpp @@ -29,7 +29,6 @@ /**************************************************************************/ #include "control.h" -#include "control.compat.inc" #include "container.h" #include "core/config/project_settings.h" @@ -37,7 +36,7 @@ #include "core/os/keyboard.h" #include "core/os/os.h" #include "core/string/print_string.h" -#include "core/string/translation.h" +#include "core/string/translation_server.h" #include "scene/gui/label.h" #include "scene/gui/panel.h" #include "scene/main/canvas_layer.h" @@ -248,7 +247,7 @@ void Control::get_argument_options(const StringName &p_function, int p_idx, List PackedStringArray Control::get_configuration_warnings() const { ERR_READ_THREAD_GUARD_V(PackedStringArray()); - PackedStringArray warnings = Node::get_configuration_warnings(); + PackedStringArray warnings = CanvasItem::get_configuration_warnings(); if (data.mouse_filter == MOUSE_FILTER_IGNORE && !data.tooltip.is_empty()) { warnings.push_back(RTR("The Hint Tooltip won't be displayed as the control's Mouse Filter is set to \"Ignore\". To solve this, set the Mouse Filter to \"Stop\" or \"Pass\".")); @@ -682,12 +681,10 @@ Size2 Control::get_parent_area_size() const { // Positioning and sizing. Transform2D Control::_get_internal_transform() const { - Transform2D rot_scale; - rot_scale.set_rotation_and_scale(data.rotation, data.scale); - Transform2D offset; - offset.set_origin(-data.pivot_offset); - - return offset.affine_inverse() * (rot_scale * offset); + // T(pivot_offset) * R(rotation) * S(scale) * T(-pivot_offset) + Transform2D xform(data.rotation, data.scale, 0.0f, data.pivot_offset); + xform.translate_local(-data.pivot_offset); + return xform; } void Control::_update_canvas_item_transform() { @@ -1748,10 +1745,10 @@ void Control::_size_changed() { // so an up to date global transform could be obtained when handling these. _notify_transform(); + item_rect_changed(size_changed); if (size_changed) { notification(NOTIFICATION_RESIZED); } - item_rect_changed(size_changed); } if (pos_changed && !size_changed) { @@ -2358,6 +2355,24 @@ void Control::_window_find_focus_neighbor(const Vector2 &p_dir, Node *p_at, cons points[2] = xform.xform(c->get_size()); points[3] = xform.xform(Point2(0, c->get_size().y)); + // Tie-breaking aims to address situations where a potential focus neighbor's bounding rect + // is right next to the currently focused control (e.g. in BoxContainer with + // separation overridden to 0). This needs specific handling so that the correct + // focus neighbor is selected. + + // Calculate centers of the potential neighbor, currently focused, and closest controls. + Point2 center = xform.xform(0.5 * c->get_size()); + // We only have the points, not an actual reference. + Point2 p_center = 0.25 * (p_points[0] + p_points[1] + p_points[2] + p_points[3]); + Point2 closest_center; + bool should_tiebreak = false; + if (*r_closest != nullptr) { + should_tiebreak = true; + Control *closest = *r_closest; + Transform2D closest_xform = closest->get_global_transform(); + closest_center = closest_xform.xform(0.5 * closest->get_size()); + } + real_t min = 1e7; for (int i = 0; i < 4; i++) { @@ -2378,10 +2393,15 @@ void Control::_window_find_focus_neighbor(const Vector2 &p_dir, Node *p_at, cons Vector2 pa, pb; real_t d = Geometry2D::get_closest_points_between_segments(la, lb, fa, fb, pa, pb); - //real_t d = Geometry2D::get_closest_distance_between_segments(Vector3(la.x,la.y,0),Vector3(lb.x,lb.y,0),Vector3(fa.x,fa.y,0),Vector3(fb.x,fb.y,0)); if (d < r_closest_dist) { r_closest_dist = d; *r_closest = c; + } else if (should_tiebreak && d == r_closest_dist) { + // Tie-break in favor of the control most aligned with p_dir. + if (p_dir.dot((center - p_center).normalized()) > p_dir.dot((closest_center - p_center).normalized())) { + r_closest_dist = d; + *r_closest = c; + } } } } @@ -2577,8 +2597,8 @@ Ref<Texture2D> Control::get_theme_icon(const StringName &p_name, const StringNam return data.theme_icon_cache[p_theme_type][p_name]; } - List<StringName> theme_types; - data.theme_owner->get_theme_type_dependencies(this, p_theme_type, &theme_types); + Vector<StringName> theme_types; + data.theme_owner->get_theme_type_dependencies(this, p_theme_type, theme_types); Ref<Texture2D> icon = data.theme_owner->get_theme_item_in_types(Theme::DATA_TYPE_ICON, p_name, theme_types); data.theme_icon_cache[p_theme_type][p_name] = icon; return icon; @@ -2601,8 +2621,8 @@ Ref<StyleBox> Control::get_theme_stylebox(const StringName &p_name, const String return data.theme_style_cache[p_theme_type][p_name]; } - List<StringName> theme_types; - data.theme_owner->get_theme_type_dependencies(this, p_theme_type, &theme_types); + Vector<StringName> theme_types; + data.theme_owner->get_theme_type_dependencies(this, p_theme_type, theme_types); Ref<StyleBox> style = data.theme_owner->get_theme_item_in_types(Theme::DATA_TYPE_STYLEBOX, p_name, theme_types); data.theme_style_cache[p_theme_type][p_name] = style; return style; @@ -2625,8 +2645,8 @@ Ref<Font> Control::get_theme_font(const StringName &p_name, const StringName &p_ return data.theme_font_cache[p_theme_type][p_name]; } - List<StringName> theme_types; - data.theme_owner->get_theme_type_dependencies(this, p_theme_type, &theme_types); + Vector<StringName> theme_types; + data.theme_owner->get_theme_type_dependencies(this, p_theme_type, theme_types); Ref<Font> font = data.theme_owner->get_theme_item_in_types(Theme::DATA_TYPE_FONT, p_name, theme_types); data.theme_font_cache[p_theme_type][p_name] = font; return font; @@ -2649,8 +2669,8 @@ int Control::get_theme_font_size(const StringName &p_name, const StringName &p_t return data.theme_font_size_cache[p_theme_type][p_name]; } - List<StringName> theme_types; - data.theme_owner->get_theme_type_dependencies(this, p_theme_type, &theme_types); + Vector<StringName> theme_types; + data.theme_owner->get_theme_type_dependencies(this, p_theme_type, theme_types); int font_size = data.theme_owner->get_theme_item_in_types(Theme::DATA_TYPE_FONT_SIZE, p_name, theme_types); data.theme_font_size_cache[p_theme_type][p_name] = font_size; return font_size; @@ -2673,8 +2693,8 @@ Color Control::get_theme_color(const StringName &p_name, const StringName &p_the return data.theme_color_cache[p_theme_type][p_name]; } - List<StringName> theme_types; - data.theme_owner->get_theme_type_dependencies(this, p_theme_type, &theme_types); + Vector<StringName> theme_types; + data.theme_owner->get_theme_type_dependencies(this, p_theme_type, theme_types); Color color = data.theme_owner->get_theme_item_in_types(Theme::DATA_TYPE_COLOR, p_name, theme_types); data.theme_color_cache[p_theme_type][p_name] = color; return color; @@ -2697,8 +2717,8 @@ int Control::get_theme_constant(const StringName &p_name, const StringName &p_th return data.theme_constant_cache[p_theme_type][p_name]; } - List<StringName> theme_types; - data.theme_owner->get_theme_type_dependencies(this, p_theme_type, &theme_types); + Vector<StringName> theme_types; + data.theme_owner->get_theme_type_dependencies(this, p_theme_type, theme_types); int constant = data.theme_owner->get_theme_item_in_types(Theme::DATA_TYPE_CONSTANT, p_name, theme_types); data.theme_constant_cache[p_theme_type][p_name] = constant; return constant; @@ -2743,8 +2763,8 @@ bool Control::has_theme_icon(const StringName &p_name, const StringName &p_theme } } - List<StringName> theme_types; - data.theme_owner->get_theme_type_dependencies(this, p_theme_type, &theme_types); + Vector<StringName> theme_types; + data.theme_owner->get_theme_type_dependencies(this, p_theme_type, theme_types); return data.theme_owner->has_theme_item_in_types(Theme::DATA_TYPE_ICON, p_name, theme_types); } @@ -2760,8 +2780,8 @@ bool Control::has_theme_stylebox(const StringName &p_name, const StringName &p_t } } - List<StringName> theme_types; - data.theme_owner->get_theme_type_dependencies(this, p_theme_type, &theme_types); + Vector<StringName> theme_types; + data.theme_owner->get_theme_type_dependencies(this, p_theme_type, theme_types); return data.theme_owner->has_theme_item_in_types(Theme::DATA_TYPE_STYLEBOX, p_name, theme_types); } @@ -2777,8 +2797,8 @@ bool Control::has_theme_font(const StringName &p_name, const StringName &p_theme } } - List<StringName> theme_types; - data.theme_owner->get_theme_type_dependencies(this, p_theme_type, &theme_types); + Vector<StringName> theme_types; + data.theme_owner->get_theme_type_dependencies(this, p_theme_type, theme_types); return data.theme_owner->has_theme_item_in_types(Theme::DATA_TYPE_FONT, p_name, theme_types); } @@ -2794,8 +2814,8 @@ bool Control::has_theme_font_size(const StringName &p_name, const StringName &p_ } } - List<StringName> theme_types; - data.theme_owner->get_theme_type_dependencies(this, p_theme_type, &theme_types); + Vector<StringName> theme_types; + data.theme_owner->get_theme_type_dependencies(this, p_theme_type, theme_types); return data.theme_owner->has_theme_item_in_types(Theme::DATA_TYPE_FONT_SIZE, p_name, theme_types); } @@ -2811,8 +2831,8 @@ bool Control::has_theme_color(const StringName &p_name, const StringName &p_them } } - List<StringName> theme_types; - data.theme_owner->get_theme_type_dependencies(this, p_theme_type, &theme_types); + Vector<StringName> theme_types; + data.theme_owner->get_theme_type_dependencies(this, p_theme_type, theme_types); return data.theme_owner->has_theme_item_in_types(Theme::DATA_TYPE_COLOR, p_name, theme_types); } @@ -2828,8 +2848,8 @@ bool Control::has_theme_constant(const StringName &p_name, const StringName &p_t } } - List<StringName> theme_types; - data.theme_owner->get_theme_type_dependencies(this, p_theme_type, &theme_types); + Vector<StringName> theme_types; + data.theme_owner->get_theme_type_dependencies(this, p_theme_type, theme_types); return data.theme_owner->has_theme_item_in_types(Theme::DATA_TYPE_CONSTANT, p_name, theme_types); } @@ -3608,7 +3628,7 @@ void Control::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::INT, "focus_mode", PROPERTY_HINT_ENUM, "None,Click,All"), "set_focus_mode", "get_focus_mode"); ADD_GROUP("Mouse", "mouse_"); - ADD_PROPERTY(PropertyInfo(Variant::INT, "mouse_filter", PROPERTY_HINT_ENUM, "Stop,Pass,Ignore"), "set_mouse_filter", "get_mouse_filter"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "mouse_filter", PROPERTY_HINT_ENUM, "Stop,Pass (Propagate Up),Ignore"), "set_mouse_filter", "get_mouse_filter"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "mouse_force_pass_scroll_events"), "set_force_pass_scroll_events", "is_force_pass_scroll_events"); ADD_PROPERTY(PropertyInfo(Variant::INT, "mouse_default_cursor_shape", PROPERTY_HINT_ENUM, "Arrow,I-Beam,Pointing Hand,Cross,Wait,Busy,Drag,Can Drop,Forbidden,Vertical Resize,Horizontal Resize,Secondary Diagonal Resize,Main Diagonal Resize,Move,Vertical Split,Horizontal Split,Help"), "set_default_cursor_shape", "get_default_cursor_shape"); diff --git a/scene/gui/control.h b/scene/gui/control.h index c784d4330d..2655b14562 100644 --- a/scene/gui/control.h +++ b/scene/gui/control.h @@ -348,10 +348,6 @@ protected: void _notification(int p_notification); static void _bind_methods(); -#ifndef DISABLE_DEPRECATED - static void _bind_compatibility_methods(); -#endif - // Exposed virtual methods. GDVIRTUAL1RC(bool, _has_point, Vector2) diff --git a/scene/gui/file_dialog.cpp b/scene/gui/file_dialog.cpp index 8047369ab1..1fc8586448 100644 --- a/scene/gui/file_dialog.cpp +++ b/scene/gui/file_dialog.cpp @@ -76,6 +76,7 @@ void FileDialog::popup(const Rect2i &p_rect) { #ifdef TOOLS_ENABLED if (is_part_of_edited_scene()) { ConfirmationDialog::popup(p_rect); + return; } #endif @@ -124,6 +125,8 @@ void FileDialog::_native_dialog_cb(bool p_ok, const Vector<String> &p_files, int file_name = ProjectSettings::get_singleton()->localize_path(file_name); } } + selected_options = p_selected_options; + String f = files[0]; if (mode == FILE_MODE_OPEN_FILES) { emit_signal(SNAME("files_selected"), files); @@ -155,7 +158,6 @@ void FileDialog::_native_dialog_cb(bool p_ok, const Vector<String> &p_files, int } file->set_text(f); dir->set_text(f.get_base_dir()); - selected_options = p_selected_options; filter->select(p_filter); } @@ -1143,7 +1145,7 @@ void FileDialog::_update_option_controls() { CheckBox *cb = memnew(CheckBox); cb->set_pressed(opt.default_idx); grid_options->add_child(cb); - cb->connect("toggled", callable_mp(this, &FileDialog::_option_changed_checkbox_toggled).bind(opt.name)); + cb->connect(SceneStringName(toggled), callable_mp(this, &FileDialog::_option_changed_checkbox_toggled).bind(opt.name)); selected_options[opt.name] = (bool)opt.default_idx; } else { OptionButton *ob = memnew(OptionButton); @@ -1379,7 +1381,7 @@ void FileDialog::set_use_native_dialog(bool p_native) { #endif // Replace the built-in dialog with the native one if it's currently visible. - if (is_visible() && DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_NATIVE_DIALOG_FILE) && (use_native_dialog || OS::get_singleton()->is_sandboxed())) { + if (is_inside_tree() && is_visible() && DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_NATIVE_DIALOG_FILE) && (use_native_dialog || OS::get_singleton()->is_sandboxed())) { ConfirmationDialog::set_visible(false); _native_popup(); } @@ -1441,7 +1443,7 @@ FileDialog::FileDialog() { show_hidden->set_toggle_mode(true); show_hidden->set_pressed(is_showing_hidden_files()); show_hidden->set_tooltip_text(ETR("Toggle the visibility of hidden files.")); - show_hidden->connect("toggled", callable_mp(this, &FileDialog::set_show_hidden_files)); + show_hidden->connect(SceneStringName(toggled), callable_mp(this, &FileDialog::set_show_hidden_files)); hbc->add_child(show_hidden); shortcuts_container = memnew(HBoxContainer); diff --git a/scene/gui/graph_edit.cpp b/scene/gui/graph_edit.cpp index 55a2c607e3..11a6411e65 100644 --- a/scene/gui/graph_edit.cpp +++ b/scene/gui/graph_edit.cpp @@ -695,6 +695,10 @@ void GraphEdit::remove_child_notify(Node *p_child) { graph_element->disconnect("raise_request", callable_mp(this, &GraphEdit::_ensure_node_order_from)); graph_element->disconnect("resize_request", callable_mp(this, &GraphEdit::_graph_element_resize_request)); + if (connections_layer != nullptr && connections_layer->is_inside_tree()) { + graph_element->disconnect(SceneStringName(item_rect_changed), callable_mp((CanvasItem *)connections_layer, &CanvasItem::queue_redraw)); + } + // In case of the whole GraphEdit being destroyed these references can already be freed. if (minimap != nullptr && minimap->is_inside_tree()) { graph_element->disconnect(SceneStringName(item_rect_changed), callable_mp((CanvasItem *)minimap, &GraphEditMinimap::queue_redraw)); @@ -908,7 +912,7 @@ bool GraphEdit::_filter_input(const Point2 &p_point) { // This prevents interactions with a port hotzone that is behind another node. Rect2 graph_node_rect = Rect2(graph_node->get_position(), graph_node->get_size() * zoom); - if (graph_node_rect.has_point(click_pos * zoom)) { + if (graph_node_rect.has_point(p_point)) { break; } } @@ -1040,12 +1044,6 @@ void GraphEdit::_top_connection_layer_input(const Ref<InputEvent> &p_ev) { return; } } - - // This prevents interactions with a port hotzone that is behind another node. - Rect2 graph_node_rect = Rect2(graph_node->get_position(), graph_node->get_size() * zoom); - if (graph_node_rect.has_point(click_pos * zoom)) { - break; - } } } @@ -1117,6 +1115,12 @@ void GraphEdit::_top_connection_layer_input(const Ref<InputEvent> &p_ev) { } connecting_target_valid = false; } + + // This prevents interactions with a port hotzone that is behind another node. + Rect2 graph_node_rect = Rect2(graph_node->get_position(), graph_node->get_size() * zoom); + if (graph_node_rect.has_point(mm->get_position())) { + break; + } } } } @@ -1642,24 +1646,34 @@ void GraphEdit::_draw_grid() { Color transparent_grid_minor = theme_cache.grid_minor; transparent_grid_minor.a *= CLAMP(1.0 * (zoom - 0.4), 0, 1); - for (int i = from_pos.x; i < from_pos.x + len.x; i++) { - for (int j = from_pos.y; j < from_pos.y + len.y; j++) { - Color color = transparent_grid_minor; + // Minor dots. + if (transparent_grid_minor.a != 0) { + for (int i = from_pos.x; i < from_pos.x + len.x; i++) { + for (int j = from_pos.y; j < from_pos.y + len.y; j++) { + if (ABS(i) % GRID_MINOR_STEPS_PER_MAJOR_DOT == 0 && ABS(j) % GRID_MINOR_STEPS_PER_MAJOR_DOT == 0) { + continue; + } - if (ABS(i) % GRID_MINOR_STEPS_PER_MAJOR_DOT == 0 && ABS(j) % GRID_MINOR_STEPS_PER_MAJOR_DOT == 0) { - color = theme_cache.grid_major; - } + float base_offset_x = i * snapping_distance * zoom - offset.x * zoom; + float base_offset_y = j * snapping_distance * zoom - offset.y * zoom; - if (color.a == 0) { - continue; + draw_rect(Rect2(base_offset_x - 1, base_offset_y - 1, 3, 3), transparent_grid_minor); } + } + } - float base_offset_x = i * snapping_distance * zoom - offset.x * zoom; - float base_offset_y = j * snapping_distance * zoom - offset.y * zoom; + // Major dots. + if (theme_cache.grid_major.a != 0) { + for (int i = from_pos.x - from_pos.x % GRID_MINOR_STEPS_PER_MAJOR_DOT; i < from_pos.x + len.x; i += GRID_MINOR_STEPS_PER_MAJOR_DOT) { + for (int j = from_pos.y - from_pos.y % GRID_MINOR_STEPS_PER_MAJOR_DOT; j < from_pos.y + len.y; j += GRID_MINOR_STEPS_PER_MAJOR_DOT) { + float base_offset_x = i * snapping_distance * zoom - offset.x * zoom; + float base_offset_y = j * snapping_distance * zoom - offset.y * zoom; - draw_rect(Rect2(base_offset_x - 1, base_offset_y - 1, 3, 3), color); + draw_rect(Rect2(base_offset_x - 1, base_offset_y - 1, 3, 3), theme_cache.grid_major); + } } } + } break; } } diff --git a/scene/gui/line_edit.cpp b/scene/gui/line_edit.cpp index 0006204ae3..1a066b0728 100644 --- a/scene/gui/line_edit.cpp +++ b/scene/gui/line_edit.cpp @@ -45,6 +45,111 @@ #include "editor/editor_settings.h" #endif +void LineEdit::_edit() { + if (!is_inside_tree()) { + return; + } + + if (!has_focus()) { + grab_focus(); + } + + if (!editable || editing) { + return; + } + + editing = true; + _validate_caret_can_draw(); + + show_virtual_keyboard(); + queue_redraw(); + emit_signal(SNAME("editing_toggled"), true); +} + +void LineEdit::_unedit() { + if (!editing) { + return; + } + + editing = false; + _validate_caret_can_draw(); + + apply_ime(); + set_caret_column(caret_column); // Update scroll_offset. + + if (DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_VIRTUAL_KEYBOARD) && virtual_keyboard_enabled) { + DisplayServer::get_singleton()->virtual_keyboard_hide(); + } + + if (deselect_on_focus_loss_enabled && !selection.drag_attempt) { + deselect(); + } + + emit_signal(SNAME("editing_toggled"), false); +} + +bool LineEdit::is_editing() const { + return editing; +} + +void LineEdit::_close_ime_window() { + DisplayServer::WindowID wid = get_window() ? get_window()->get_window_id() : DisplayServer::INVALID_WINDOW_ID; + if (wid == DisplayServer::INVALID_WINDOW_ID || !DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_IME)) { + return; + } + DisplayServer::get_singleton()->window_set_ime_position(Point2(), wid); + DisplayServer::get_singleton()->window_set_ime_active(false, wid); +} + +void LineEdit::_update_ime_window_position() { + DisplayServer::WindowID wid = get_window() ? get_window()->get_window_id() : DisplayServer::INVALID_WINDOW_ID; + if (wid == DisplayServer::INVALID_WINDOW_ID || !DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_IME)) { + return; + } + DisplayServer::get_singleton()->window_set_ime_active(true, wid); + Point2 pos = Point2(get_caret_pixel_pos().x, (get_size().y + theme_cache.font->get_height(theme_cache.font_size)) / 2) + get_global_position(); + if (get_window()->get_embedder()) { + pos += get_viewport()->get_popup_base_transform().get_origin(); + } + // The window will move to the updated position the next time the IME is updated, not immediately. + DisplayServer::get_singleton()->window_set_ime_position(pos, wid); +} + +bool LineEdit::has_ime_text() const { + return !ime_text.is_empty(); +} + +void LineEdit::cancel_ime() { + if (!has_ime_text()) { + return; + } + ime_text = String(); + ime_selection = Vector2i(); + alt_start = false; + alt_start_no_hold = false; + _close_ime_window(); + _shape(); +} + +void LineEdit::apply_ime() { + if (!has_ime_text()) { + return; + } + + // Force apply the current IME text. + if (alt_start || alt_start_no_hold) { + cancel_ime(); + if ((alt_code > 0x31 && alt_code < 0xd800) || (alt_code > 0xdfff && alt_code <= 0x10ffff)) { + char32_t ucodestr[2] = { (char32_t)alt_code, 0 }; + insert_text_at_caret(ucodestr); + } + } else { + String insert_ime_text = ime_text; + cancel_ime(); + insert_text_at_caret(insert_ime_text); + } +} + void LineEdit::_swap_current_input_direction() { if (input_direction == TEXT_DIRECTION_LTR) { input_direction = TEXT_DIRECTION_RTL; @@ -52,7 +157,6 @@ void LineEdit::_swap_current_input_direction() { input_direction = TEXT_DIRECTION_LTR; } set_caret_column(get_caret_column()); - queue_redraw(); } void LineEdit::_move_caret_left(bool p_select, bool p_move_by_word) { @@ -68,10 +172,15 @@ void LineEdit::_move_caret_left(bool p_select, bool p_move_by_word) { int cc = caret_column; PackedInt32Array words = TS->shaped_text_get_word_breaks(text_rid); - for (int i = words.size() - 2; i >= 0; i = i - 2) { - if (words[i] < cc) { - cc = words[i]; - break; + if (words.is_empty() || cc <= words[0]) { + // Move to the start when there are no more words. + cc = 0; + } else { + for (int i = words.size() - 2; i >= 0; i = i - 2) { + if (words[i] < cc) { + cc = words[i]; + break; + } } } @@ -101,10 +210,15 @@ void LineEdit::_move_caret_right(bool p_select, bool p_move_by_word) { int cc = caret_column; PackedInt32Array words = TS->shaped_text_get_word_breaks(text_rid); - for (int i = 1; i < words.size(); i = i + 2) { - if (words[i] > cc) { - cc = words[i]; - break; + if (words.is_empty() || cc >= words[words.size() - 1]) { + // Move to the end when there are no more words. + cc = text.length(); + } else { + for (int i = 1; i < words.size(); i = i + 2) { + if (words[i] > cc) { + cc = words[i]; + break; + } } } @@ -159,10 +273,15 @@ void LineEdit::_backspace(bool p_word, bool p_all_to_left) { int cc = caret_column; PackedInt32Array words = TS->shaped_text_get_word_breaks(text_rid); - for (int i = words.size() - 2; i >= 0; i = i - 2) { - if (words[i] < cc) { - cc = words[i]; - break; + if (words.is_empty() || cc <= words[0]) { + // Delete to the start when there are no more words. + cc = 0; + } else { + for (int i = words.size() - 2; i >= 0; i = i - 2) { + if (words[i] < cc) { + cc = words[i]; + break; + } } } @@ -198,10 +317,15 @@ void LineEdit::_delete(bool p_word, bool p_all_to_right) { if (p_word) { int cc = caret_column; PackedInt32Array words = TS->shaped_text_get_word_breaks(text_rid); - for (int i = 1; i < words.size(); i = i + 2) { - if (words[i] > cc) { - cc = words[i]; - break; + if (words.is_empty() || cc >= words[words.size() - 1]) { + // Delete to the end when there are no more words. + cc = text.length(); + } else { + for (int i = 1; i < words.size(); i = i + 2) { + if (words[i] > cc) { + cc = words[i]; + break; + } } } @@ -220,6 +344,11 @@ void LineEdit::_delete(bool p_word, bool p_all_to_right) { } void LineEdit::unhandled_key_input(const Ref<InputEvent> &p_event) { + // Return to prevent editing if just focused. + if (!editing) { + return; + } + Ref<InputEventKey> k = p_event; if (k.is_valid()) { @@ -246,25 +375,40 @@ void LineEdit::gui_input(const Ref<InputEvent> &p_event) { Ref<InputEventMouseButton> b = p_event; if (b.is_valid()) { - if (ime_text.length() != 0) { - // Ignore mouse clicks in IME input mode. - return; - } - if (b->is_pressed() && b->get_button_index() == MouseButton::RIGHT && context_menu_enabled) { - _update_context_menu(); - menu->set_position(get_screen_position() + get_local_mouse_position()); - menu->reset_size(); - menu->popup(); - grab_focus(); + if (b->is_pressed() && b->get_button_index() == MouseButton::RIGHT) { + apply_ime(); + + if (editable && !selection.enabled) { + set_caret_at_pixel_pos(b->get_position().x); + } + + if (context_menu_enabled) { + _update_context_menu(); + menu->set_position(get_screen_position() + get_local_mouse_position()); + menu->reset_size(); + menu->popup(); + } + + if (editable && !editing) { + _edit(); + } + accept_event(); return; } - if (is_middle_mouse_paste_enabled() && b->is_pressed() && b->get_button_index() == MouseButton::MIDDLE && is_editable() && DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_CLIPBOARD_PRIMARY)) { + if (editable && is_middle_mouse_paste_enabled() && b->is_pressed() && b->get_button_index() == MouseButton::MIDDLE && DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_CLIPBOARD_PRIMARY)) { + apply_ime(); + String paste_buffer = DisplayServer::get_singleton()->clipboard_get_primary().strip_escapes(); deselect(); set_caret_at_pixel_pos(b->get_position().x); + + if (!editing) { + _edit(); + } + if (!paste_buffer.is_empty()) { insert_text_at_caret(paste_buffer); @@ -275,7 +419,6 @@ void LineEdit::gui_input(const Ref<InputEvent> &p_event) { text_changed_dirty = true; } } - grab_focus(); accept_event(); return; } @@ -284,10 +427,15 @@ void LineEdit::gui_input(const Ref<InputEvent> &p_event) { return; } - _reset_caret_blink_timer(); + if (editing) { + _reset_caret_blink_timer(); + } + if (b->is_pressed()) { + apply_ime(); + accept_event(); // Don't pass event further when clicked on text field. - if (!text.is_empty() && is_editable() && _is_over_clear_button(b->get_position())) { + if (editable && !text.is_empty() && _is_over_clear_button(b->get_position())) { clear_button_status.press_attempt = true; clear_button_status.pressing_inside = true; queue_redraw(); @@ -310,7 +458,7 @@ void LineEdit::gui_input(const Ref<InputEvent> &p_event) { const int triple_click_tolerance = 5; const bool is_triple_click = !b->is_double_click() && (OS::get_singleton()->get_ticks_msec() - last_dblclk) < triple_click_timeout && b->get_position().distance_to(last_dblclk_pos) < triple_click_tolerance; - if (is_triple_click && text.length()) { + if (is_triple_click && !text.is_empty()) { // Triple-click select all. selection.enabled = true; selection.begin = 0; @@ -357,13 +505,17 @@ void LineEdit::gui_input(const Ref<InputEvent> &p_event) { } } + if (editable && !editing) { + _edit(); + return; + } queue_redraw(); } else { if (selection.enabled && !pass && b->get_button_index() == MouseButton::LEFT && DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_CLIPBOARD_PRIMARY)) { DisplayServer::get_singleton()->clipboard_set_primary(get_selected_text()); } - if (!text.is_empty() && is_editable() && clear_button_enabled) { + if (editable && !text.is_empty() && clear_button_enabled) { bool press_attempt = clear_button_status.press_attempt; clear_button_status.press_attempt = false; if (press_attempt && clear_button_status.pressing_inside && _is_over_clear_button(b->get_position())) { @@ -396,7 +548,7 @@ void LineEdit::gui_input(const Ref<InputEvent> &p_event) { Ref<InputEventMouseMotion> m = p_event; if (m.is_valid()) { - if (!text.is_empty() && is_editable() && clear_button_enabled) { + if (editable && !text.is_empty() && clear_button_enabled) { bool last_press_inside = clear_button_status.pressing_inside; clear_button_status.pressing_inside = clear_button_status.press_attempt && _is_over_clear_button(m->get_position()); if (last_press_inside != clear_button_status.pressing_inside) { @@ -442,221 +594,305 @@ void LineEdit::gui_input(const Ref<InputEvent> &p_event) { Ref<InputEventKey> k = p_event; - if (k.is_valid()) { - if (!k->is_pressed()) { - if (alt_start && k->get_keycode() == Key::ALT) { - alt_start = false; - if ((alt_code > 0x31 && alt_code < 0xd800) || (alt_code > 0xdfff && alt_code <= 0x10ffff)) { - char32_t ucodestr[2] = { (char32_t)alt_code, 0 }; - insert_text_at_caret(ucodestr); - } - accept_event(); - return; - } - return; - } + if (k.is_null()) { + return; + } - // Alt + Unicode input: - if (k->is_alt_pressed()) { - if (!alt_start) { - if (k->get_keycode() == Key::KP_ADD) { - alt_start = true; - alt_code = 0; - accept_event(); - return; - } - } else { - if (k->get_keycode() >= Key::KEY_0 && k->get_keycode() <= Key::KEY_9) { - alt_code = alt_code << 4; - alt_code += (uint32_t)(k->get_keycode() - Key::KEY_0); - } - if (k->get_keycode() >= Key::KP_0 && k->get_keycode() <= Key::KP_9) { - alt_code = alt_code << 4; - alt_code += (uint32_t)(k->get_keycode() - Key::KP_0); - } - if (k->get_keycode() >= Key::A && k->get_keycode() <= Key::F) { - alt_code = alt_code << 4; - alt_code += (uint32_t)(k->get_keycode() - Key::A) + 10; - } - accept_event(); - return; - } - } + if (editable && !editing && k->is_action_pressed("ui_text_submit", false)) { + _edit(); + return; + } - if (context_menu_enabled) { - if (k->is_action("ui_menu", true)) { - _update_context_menu(); - Point2 pos = Point2(get_caret_pixel_pos().x, (get_size().y + theme_cache.font->get_height(theme_cache.font_size)) / 2); - menu->set_position(get_screen_position() + pos); - menu->reset_size(); - menu->popup(); - menu->grab_focus(); + if (!editing) { + return; + } - accept_event(); - return; - } + // Start Unicode input (hold). + if (k->is_alt_pressed() && k->get_keycode() == Key::KP_ADD && !alt_start && !alt_start_no_hold) { + if (selection.enabled) { + selection_delete(); } + alt_start = true; + alt_code = 0; + ime_text = "u"; + ime_selection = Vector2i(0, -1); + _shape(); + queue_redraw(); + accept_event(); + return; + } - // Default is ENTER and KP_ENTER. Cannot use ui_accept as default includes SPACE. - if (k->is_action("ui_text_submit", false)) { - emit_signal(SNAME("text_submitted"), text); - if (DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_VIRTUAL_KEYBOARD) && virtual_keyboard_enabled) { - DisplayServer::get_singleton()->virtual_keyboard_hide(); - } - accept_event(); - return; + // Start Unicode input (press). + if (k->is_action("ui_unicode_start", true) && !alt_start && !alt_start_no_hold) { + if (selection.enabled) { + selection_delete(); } + alt_start_no_hold = true; + alt_code = 0; + ime_text = "u"; + ime_selection = Vector2i(0, -1); + _shape(); + queue_redraw(); + accept_event(); + return; + } - if (k->is_action("ui_cancel")) { - callable_mp((Control *)this, &Control::release_focus).call_deferred(); - accept_event(); - return; + // Update Unicode input. + if (k->is_pressed() && ((k->is_alt_pressed() && alt_start) || alt_start_no_hold)) { + if (k->get_keycode() >= Key::KEY_0 && k->get_keycode() <= Key::KEY_9) { + alt_code = alt_code << 4; + alt_code += (uint32_t)(k->get_keycode() - Key::KEY_0); + } else if (k->get_keycode() >= Key::KP_0 && k->get_keycode() <= Key::KP_9) { + alt_code = alt_code << 4; + alt_code += (uint32_t)(k->get_keycode() - Key::KP_0); + } else if (k->get_keycode() >= Key::A && k->get_keycode() <= Key::F) { + alt_code = alt_code << 4; + alt_code += (uint32_t)(k->get_keycode() - Key::A) + 10; + } else if ((Key)k->get_unicode() >= Key::KEY_0 && (Key)k->get_unicode() <= Key::KEY_9) { + alt_code = alt_code << 4; + alt_code += (uint32_t)((Key)k->get_unicode() - Key::KEY_0); + } else if ((Key)k->get_unicode() >= Key::A && (Key)k->get_unicode() <= Key::F) { + alt_code = alt_code << 4; + alt_code += (uint32_t)((Key)k->get_unicode() - Key::A) + 10; + } else if (k->get_physical_keycode() >= Key::KEY_0 && k->get_physical_keycode() <= Key::KEY_9) { + alt_code = alt_code << 4; + alt_code += (uint32_t)(k->get_physical_keycode() - Key::KEY_0); } + if (k->get_keycode() == Key::BACKSPACE) { + alt_code = alt_code >> 4; + } + if (alt_code > 0x10ffff) { + alt_code = 0x10ffff; + } + if (alt_code > 0) { + ime_text = vformat("u%s", String::num_int64(alt_code, 16, true)); + } else { + ime_text = "u"; + } + ime_selection = Vector2i(0, -1); + _shape(); + queue_redraw(); + accept_event(); + return; + } - if (is_shortcut_keys_enabled()) { - if (k->is_action("ui_copy", true)) { - copy_text(); - accept_event(); - return; - } - - if (k->is_action("ui_text_select_all", true)) { - select(); - accept_event(); - return; - } - - // Cut / Paste - if (k->is_action("ui_cut", true)) { - cut_text(); - accept_event(); - return; - } + // Submit Unicode input. + if ((!k->is_pressed() && alt_start && k->get_keycode() == Key::ALT) || (alt_start_no_hold && (k->is_action("ui_text_submit", true) || k->is_action("ui_accept", true)))) { + alt_start = false; + alt_start_no_hold = false; + if ((alt_code > 0x31 && alt_code < 0xd800) || (alt_code > 0xdfff && alt_code <= 0x10ffff)) { + ime_text = String(); + ime_selection = Vector2i(); + char32_t ucodestr[2] = { (char32_t)alt_code, 0 }; + insert_text_at_caret(ucodestr); + } else { + ime_text = String(); + ime_selection = Vector2i(); + _shape(); + } + queue_redraw(); + accept_event(); + return; + } - if (k->is_action("ui_paste", true)) { - paste_text(); - accept_event(); - return; - } + // Cancel Unicode input. + if (alt_start_no_hold && k->is_action("ui_cancel", true)) { + alt_start = false; + alt_start_no_hold = false; + ime_text = String(); + ime_selection = Vector2i(); + _shape(); + queue_redraw(); + accept_event(); + return; + } - // Undo / Redo - if (k->is_action("ui_undo", true)) { - undo(); - accept_event(); - return; - } + if (!k->is_pressed()) { + return; + } - if (k->is_action("ui_redo", true)) { - redo(); - accept_event(); - return; - } - } + // Open context menu. + if (context_menu_enabled) { + if (k->is_action("ui_menu", true)) { + _update_context_menu(); + Point2 pos = Point2(get_caret_pixel_pos().x, (get_size().y + theme_cache.font->get_height(theme_cache.font_size)) / 2); + menu->set_position(get_screen_position() + pos); + menu->reset_size(); + menu->popup(); + menu->grab_focus(); - // BACKSPACE - if (k->is_action("ui_text_backspace_all_to_left", true)) { - _backspace(false, true); - accept_event(); - return; - } - if (k->is_action("ui_text_backspace_word", true)) { - _backspace(true); - accept_event(); - return; - } - if (k->is_action("ui_text_backspace", true)) { - _backspace(); accept_event(); return; } + } - // DELETE - if (k->is_action("ui_text_delete_all_to_right", true)) { - _delete(false, true); - accept_event(); - return; - } - if (k->is_action("ui_text_delete_word", true)) { - _delete(true); - accept_event(); - return; - } - if (k->is_action("ui_text_delete", true)) { - _delete(); - accept_event(); - return; + // Default is ENTER and KP_ENTER. Cannot use ui_accept as default includes SPACE. + if (k->is_action_pressed("ui_text_submit")) { + emit_signal(SNAME("text_submitted"), text); + if (DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_VIRTUAL_KEYBOARD) && virtual_keyboard_enabled) { + DisplayServer::get_singleton()->virtual_keyboard_hide(); } - // Cursor Movement + if (editing) { + _unedit(); + } - k = k->duplicate(); - bool shift_pressed = k->is_shift_pressed(); - // Remove shift or else actions will not match. Use above variable for selection. - k->set_shift_pressed(false); + accept_event(); + return; + } - if (k->is_action("ui_text_caret_word_left", true)) { - _move_caret_left(shift_pressed, true); - accept_event(); - return; + if (k->is_action("ui_cancel")) { + if (editing) { + _unedit(); } - if (k->is_action("ui_text_caret_left", true)) { - _move_caret_left(shift_pressed); + + accept_event(); + return; + } + + if (is_shortcut_keys_enabled()) { + if (k->is_action("ui_copy", true)) { + copy_text(); accept_event(); return; } - if (k->is_action("ui_text_caret_word_right", true)) { - _move_caret_right(shift_pressed, true); + + if (k->is_action("ui_text_select_all", true)) { + select(); accept_event(); return; } - if (k->is_action("ui_text_caret_right", true)) { - _move_caret_right(shift_pressed, false); + + // Cut / Paste + if (k->is_action("ui_cut", true)) { + cut_text(); accept_event(); return; } - // Up = Home, Down = End - if (k->is_action("ui_text_caret_up", true) || k->is_action("ui_text_caret_line_start", true) || k->is_action("ui_text_caret_page_up", true)) { - _move_caret_start(shift_pressed); + if (k->is_action("ui_paste", true)) { + paste_text(); accept_event(); return; } - if (k->is_action("ui_text_caret_down", true) || k->is_action("ui_text_caret_line_end", true) || k->is_action("ui_text_caret_page_down", true)) { - _move_caret_end(shift_pressed); + + // Undo / Redo + if (k->is_action("ui_undo", true)) { + undo(); accept_event(); return; } - // Misc - if (k->is_action("ui_swap_input_direction", true)) { - _swap_current_input_direction(); + if (k->is_action("ui_redo", true)) { + redo(); accept_event(); return; } + } + + // BACKSPACE + if (k->is_action("ui_text_backspace_all_to_left", true)) { + _backspace(false, true); + accept_event(); + return; + } + if (k->is_action("ui_text_backspace_word", true)) { + _backspace(true); + accept_event(); + return; + } + if (k->is_action("ui_text_backspace", true)) { + _backspace(); + accept_event(); + return; + } - _reset_caret_blink_timer(); + // DELETE + if (k->is_action("ui_text_delete_all_to_right", true)) { + _delete(false, true); + accept_event(); + return; + } + if (k->is_action("ui_text_delete_word", true)) { + _delete(true); + accept_event(); + return; + } + if (k->is_action("ui_text_delete", true)) { + _delete(); + accept_event(); + return; + } - // Allow unicode handling if: - // * No Modifiers are pressed (except shift) - bool allow_unicode_handling = !(k->is_ctrl_pressed() || k->is_alt_pressed() || k->is_meta_pressed()); + // Cursor Movement - if (allow_unicode_handling && editable && k->get_unicode() >= 32) { - // Handle Unicode if no modifiers are active. - selection_delete(); - char32_t ucodestr[2] = { (char32_t)k->get_unicode(), 0 }; - int prev_len = text.length(); - insert_text_at_caret(ucodestr); - if (text.length() != prev_len) { - if (!text_changed_dirty) { - if (is_inside_tree()) { - callable_mp(this, &LineEdit::_text_changed).call_deferred(); - } - text_changed_dirty = true; + k = k->duplicate(); + bool shift_pressed = k->is_shift_pressed(); + // Remove shift or else actions will not match. Use above variable for selection. + k->set_shift_pressed(false); + + if (k->is_action("ui_text_caret_word_left", true)) { + _move_caret_left(shift_pressed, true); + accept_event(); + return; + } + if (k->is_action("ui_text_caret_left", true)) { + _move_caret_left(shift_pressed); + accept_event(); + return; + } + if (k->is_action("ui_text_caret_word_right", true)) { + _move_caret_right(shift_pressed, true); + accept_event(); + return; + } + if (k->is_action("ui_text_caret_right", true)) { + _move_caret_right(shift_pressed, false); + accept_event(); + return; + } + + // Up = Home, Down = End + if (k->is_action("ui_text_caret_up", true) || k->is_action("ui_text_caret_line_start", true) || k->is_action("ui_text_caret_page_up", true)) { + _move_caret_start(shift_pressed); + accept_event(); + return; + } + if (k->is_action("ui_text_caret_down", true) || k->is_action("ui_text_caret_line_end", true) || k->is_action("ui_text_caret_page_down", true)) { + _move_caret_end(shift_pressed); + accept_event(); + return; + } + + // Misc + if (k->is_action("ui_swap_input_direction", true)) { + _swap_current_input_direction(); + accept_event(); + return; + } + + _reset_caret_blink_timer(); + + // Allow unicode handling if: + // * No Modifiers are pressed (except shift) + bool allow_unicode_handling = !(k->is_ctrl_pressed() || k->is_alt_pressed() || k->is_meta_pressed()); + + if (allow_unicode_handling && editable && k->get_unicode() >= 32) { + // Handle Unicode if no modifiers are active. + selection_delete(); + char32_t ucodestr[2] = { (char32_t)k->get_unicode(), 0 }; + int prev_len = text.length(); + insert_text_at_caret(ucodestr); + if (text.length() != prev_len) { + if (!text_changed_dirty) { + if (is_inside_tree()) { + callable_mp(this, &LineEdit::_text_changed).call_deferred(); } + text_changed_dirty = true; } - accept_event(); - return; } + accept_event(); + return; } } @@ -698,13 +934,15 @@ bool LineEdit::can_drop_data(const Point2 &p_point, const Variant &p_data) const return drop_override; } - return is_editable() && p_data.get_type() == Variant::STRING; + return is_editable() && p_data.is_string(); } void LineEdit::drop_data(const Point2 &p_point, const Variant &p_data) { Control::drop_data(p_point, p_data); - if (p_data.get_type() == Variant::STRING && is_editable()) { + if (p_data.is_string() && is_editable()) { + apply_ime(); + set_caret_at_pixel_pos(p_point.x); int caret_column_tmp = caret_column; bool is_inside_sel = selection.enabled && caret_column >= selection.begin && caret_column <= selection.end; @@ -1087,22 +1325,12 @@ void LineEdit::_notification(int p_what) { } } - if (has_focus()) { - DisplayServer::WindowID wid = get_window() ? get_window()->get_window_id() : DisplayServer::INVALID_WINDOW_ID; - if (wid != DisplayServer::INVALID_WINDOW_ID && DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_IME)) { - DisplayServer::get_singleton()->window_set_ime_active(true, wid); - Point2 pos = Point2(get_caret_pixel_pos().x, (get_size().y + theme_cache.font->get_height(theme_cache.font_size)) / 2) + get_global_position(); - if (get_window()->get_embedder()) { - pos += get_viewport()->get_popup_base_transform().get_origin(); - } - DisplayServer::get_singleton()->window_set_ime_position(pos, wid); - } + if (editing) { + _update_ime_window_position(); } } break; case NOTIFICATION_FOCUS_ENTER: { - _validate_caret_can_draw(); - if (select_all_on_focus) { if (Input::get_singleton()->is_mouse_button_pressed(MouseButton::LEFT)) { // Select all when the mouse button is up. @@ -1112,43 +1340,20 @@ void LineEdit::_notification(int p_what) { } } - DisplayServer::WindowID wid = get_window() ? get_window()->get_window_id() : DisplayServer::INVALID_WINDOW_ID; - if (wid != DisplayServer::INVALID_WINDOW_ID && DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_IME)) { - DisplayServer::get_singleton()->window_set_ime_active(true, wid); - Point2 pos = Point2(get_caret_pixel_pos().x, (get_size().y + theme_cache.font->get_height(theme_cache.font_size)) / 2) + get_global_position(); - if (get_window()->get_embedder()) { - pos += get_viewport()->get_popup_base_transform().get_origin(); - } - DisplayServer::get_singleton()->window_set_ime_position(pos, wid); + // Only allow editing if the LineEdit is not focused with arrow keys. + if (!(Input::get_singleton()->is_action_pressed("ui_up") || Input::get_singleton()->is_action_pressed("ui_down") || Input::get_singleton()->is_action_pressed("ui_left") || Input::get_singleton()->is_action_pressed("ui_right"))) { + _edit(); } - - show_virtual_keyboard(); } break; case NOTIFICATION_FOCUS_EXIT: { - _validate_caret_can_draw(); - - DisplayServer::WindowID wid = get_window() ? get_window()->get_window_id() : DisplayServer::INVALID_WINDOW_ID; - if (wid != DisplayServer::INVALID_WINDOW_ID && DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_IME)) { - DisplayServer::get_singleton()->window_set_ime_position(Point2(), wid); - DisplayServer::get_singleton()->window_set_ime_active(false, wid); - } - ime_text = ""; - ime_selection = Point2(); - _shape(); - set_caret_column(caret_column); // Update scroll_offset. - - if (DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_VIRTUAL_KEYBOARD) && virtual_keyboard_enabled) { - DisplayServer::get_singleton()->virtual_keyboard_hide(); - } - - if (deselect_on_focus_loss_enabled && !selection.drag_attempt) { - deselect(); + if (editing) { + _unedit(); } } break; case MainLoop::NOTIFICATION_OS_IME_UPDATE: { - if (has_focus()) { + if (editing) { ime_text = DisplayServer::get_singleton()->ime_get_text(); ime_selection = DisplayServer::get_singleton()->ime_get_selection(); @@ -1158,8 +1363,6 @@ void LineEdit::_notification(int p_what) { _shape(); set_caret_column(caret_column); // Update scroll_offset. - - queue_redraw(); } } break; @@ -1399,7 +1602,7 @@ Vector2 LineEdit::get_caret_pixel_pos() { Vector2 ret; CaretInfo caret; // Get position of the start of caret. - if (ime_text.length() != 0 && ime_selection.x != 0) { + if (!ime_text.is_empty() && ime_selection.x != 0) { caret = TS->shaped_text_get_carets(text_rid, caret_column + ime_selection.x); } else { caret = TS->shaped_text_get_carets(text_rid, caret_column); @@ -1412,7 +1615,7 @@ Vector2 LineEdit::get_caret_pixel_pos() { } // Get position of the end of caret. - if (ime_text.length() != 0) { + if (!ime_text.is_empty()) { if (ime_selection.y != 0) { caret = TS->shaped_text_get_carets(text_rid, caret_column + ime_selection.x + ime_selection.y); } else { @@ -1505,11 +1708,11 @@ void LineEdit::_validate_caret_can_draw() { draw_caret = true; caret_blink_timer = 0.0; } - caret_can_draw = editable && (window_has_focus || (menu && menu->has_focus())) && (has_focus() || caret_force_displayed); + caret_can_draw = editing && (window_has_focus || (menu && menu->has_focus())) && (has_focus() || caret_force_displayed); } void LineEdit::delete_char() { - if ((text.length() <= 0) || (caret_column == 0)) { + if (text.is_empty() || caret_column == 0) { return; } @@ -1641,12 +1844,14 @@ void LineEdit::clear() { _text_changed(); // This should reset virtual keyboard state if needed. - if (has_focus()) { + if (editing) { show_virtual_keyboard(); } } void LineEdit::show_virtual_keyboard() { + _update_ime_window_position(); + if (DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_VIRTUAL_KEYBOARD) && virtual_keyboard_enabled) { if (selection.enabled) { DisplayServer::get_singleton()->virtual_keyboard_show(text, get_global_rect(), DisplayServer::VirtualKeyboardType(virtual_keyboard_type), max_length, selection.begin, selection.end); @@ -1814,7 +2019,8 @@ Size2 LineEdit::get_minimum_size() const { Size2 min_size; // Minimum size of text. - float em_space_size = font->get_char_size('M', font_size).x; + // W is wider than M in most fonts, Using M may result in hiding the last digit when using float values in SpinBox, ie. ColorPicker RAW values. + float em_space_size = font->get_char_size('W', font_size).x; min_size.width = theme_cache.minimum_character_width * em_space_size; if (expand_to_text_length) { @@ -1912,7 +2118,8 @@ void LineEdit::select_all() { return; } - if (!text.length()) { + if (text.is_empty()) { + set_caret_column(0); return; } @@ -1928,6 +2135,10 @@ void LineEdit::set_editable(bool p_editable) { } editable = p_editable; + + if (!editable && editing) { + _unedit(); + } _validate_caret_can_draw(); update_minimum_size(); @@ -2308,6 +2519,7 @@ void LineEdit::_emit_text_change() { emit_signal(SceneStringName(text_changed), text); text_changed_dirty = false; } + PackedStringArray LineEdit::get_configuration_warnings() const { PackedStringArray warnings = Control::get_configuration_warnings(); if (secret_character.length() > 1) { @@ -2327,13 +2539,13 @@ void LineEdit::_shape() { TS->shaped_text_clear(text_rid); String t; - if (text.length() == 0 && ime_text.length() == 0) { + if (text.is_empty() && ime_text.is_empty()) { t = placeholder_translated; } else if (pass) { - String s = (secret_character.length() > 0) ? secret_character.left(1) : U"•"; + String s = secret_character.is_empty() ? U"•" : secret_character.left(1); t = s.repeat(text.length() + ime_text.length()); } else { - if (ime_text.length() > 0) { + if (!ime_text.is_empty()) { t = text.substr(0, caret_column) + ime_text + text.substr(caret_column, text.length()); } else { t = text; @@ -2539,9 +2751,14 @@ void LineEdit::_validate_property(PropertyInfo &p_property) const { } void LineEdit::_bind_methods() { + ClassDB::bind_method(D_METHOD("has_ime_text"), &LineEdit::has_ime_text); + ClassDB::bind_method(D_METHOD("cancel_ime"), &LineEdit::cancel_ime); + ClassDB::bind_method(D_METHOD("apply_ime"), &LineEdit::apply_ime); + ClassDB::bind_method(D_METHOD("set_horizontal_alignment", "alignment"), &LineEdit::set_horizontal_alignment); ClassDB::bind_method(D_METHOD("get_horizontal_alignment"), &LineEdit::get_horizontal_alignment); + ClassDB::bind_method(D_METHOD("is_editing"), &LineEdit::is_editing); ClassDB::bind_method(D_METHOD("clear"), &LineEdit::clear); ClassDB::bind_method(D_METHOD("select", "from", "to"), &LineEdit::select, DEFVAL(0), DEFVAL(-1)); ClassDB::bind_method(D_METHOD("select_all"), &LineEdit::select_all); @@ -2622,6 +2839,7 @@ void LineEdit::_bind_methods() { ADD_SIGNAL(MethodInfo("text_changed", PropertyInfo(Variant::STRING, "new_text"))); ADD_SIGNAL(MethodInfo("text_change_rejected", PropertyInfo(Variant::STRING, "rejected_substring"))); ADD_SIGNAL(MethodInfo("text_submitted", PropertyInfo(Variant::STRING, "new_text"))); + ADD_SIGNAL(MethodInfo("editing_toggled", PropertyInfo(Variant::BOOL, "toggled_on"))); BIND_ENUM_CONSTANT(MENU_CUT); BIND_ENUM_CONSTANT(MENU_COPY); diff --git a/scene/gui/line_edit.h b/scene/gui/line_edit.h index 993bc727e4..ac7436646b 100644 --- a/scene/gui/line_edit.h +++ b/scene/gui/line_edit.h @@ -86,11 +86,13 @@ public: private: HorizontalAlignment alignment = HORIZONTAL_ALIGNMENT_LEFT; + bool editing = false; bool editable = false; bool pass = false; bool text_changed_dirty = false; bool alt_start = false; + bool alt_start_no_hold = false; uint32_t alt_code = 0; String undo_text; @@ -205,6 +207,12 @@ private: float base_scale = 1.0; } theme_cache; + void _edit(); + void _unedit(); + + void _close_ime_window(); + void _update_ime_window_position(); + void _clear_undo_stack(); void _clear_redo(); void _create_undo_state(); @@ -257,6 +265,12 @@ protected: virtual void gui_input(const Ref<InputEvent> &p_event) override; public: + bool is_editing() const; + + bool has_ime_text() const; + void cancel_ime(); + void apply_ime(); + void set_horizontal_alignment(HorizontalAlignment p_alignment); HorizontalAlignment get_horizontal_alignment() const; diff --git a/scene/gui/menu_bar.cpp b/scene/gui/menu_bar.cpp index 4f8d818a6c..46c9c7cccc 100644 --- a/scene/gui/menu_bar.cpp +++ b/scene/gui/menu_bar.cpp @@ -121,8 +121,9 @@ void MenuBar::_open_popup(int p_index, bool p_focus_item) { } Rect2 item_rect = _get_menu_item_rect(p_index); - Point2 screen_pos = get_screen_position() + item_rect.position * get_viewport()->get_canvas_transform().get_scale(); - Size2 screen_size = item_rect.size * get_viewport()->get_canvas_transform().get_scale(); + Size2 canvas_scale = get_canvas_transform().get_scale(); + Point2 screen_pos = get_screen_position() + item_rect.position * canvas_scale; + Size2 screen_size = item_rect.size * canvas_scale; active_menu = p_index; @@ -217,15 +218,18 @@ void MenuBar::bind_global_menu() { int global_start_idx = -1; int count = nmenu->get_item_count(main_menu); String prev_tag; - for (int i = 0; i < count; i++) { - String tag = nmenu->get_item_tag(main_menu, i).operator String().get_slice("#", 1); - if (!tag.is_empty() && tag != prev_tag) { - if (i >= start_index) { - global_start_idx = i; - break; + if (start_index >= 0) { + for (int i = 0; i < count; i++) { + String tag = nmenu->get_item_tag(main_menu, i).operator String().get_slice("#", 1); + if (!tag.is_empty() && tag != prev_tag) { + MenuBar *mb = Object::cast_to<MenuBar>(ObjectDB::get_instance(ObjectID(static_cast<uint64_t>(tag.to_int())))); + if (mb && mb->get_start_index() >= start_index) { + global_start_idx = i; + break; + } } + prev_tag = tag; } - prev_tag = tag; } if (global_start_idx == -1) { global_start_idx = count; @@ -679,10 +683,7 @@ void MenuBar::_bind_methods() { ClassDB::bind_method(D_METHOD("set_menu_hidden", "menu", "hidden"), &MenuBar::set_menu_hidden); ClassDB::bind_method(D_METHOD("is_menu_hidden", "menu"), &MenuBar::is_menu_hidden); - // TODO: Properly handle popups when advanced GUI is disabled. -#ifndef ADVANCED_GUI_DISABLED ClassDB::bind_method(D_METHOD("get_menu_popup", "menu"), &MenuBar::get_menu_popup); -#endif // ADVANCED_GUI_DISABLED ADD_PROPERTY(PropertyInfo(Variant::BOOL, "flat"), "set_flat", "is_flat"); ADD_PROPERTY(PropertyInfo(Variant::INT, "start_index"), "set_start_index", "get_start_index"); diff --git a/scene/gui/menu_button.cpp b/scene/gui/menu_button.cpp index c60f728f34..1069a752c4 100644 --- a/scene/gui/menu_button.cpp +++ b/scene/gui/menu_button.cpp @@ -173,10 +173,7 @@ bool MenuButton::_get(const StringName &p_name, Variant &r_ret) const { } void MenuButton::_bind_methods() { - // TODO: Properly handle popups when advanced GUI is disabled. -#ifndef ADVANCED_GUI_DISABLED ClassDB::bind_method(D_METHOD("get_popup"), &MenuButton::get_popup); -#endif // ADVANCED_GUI_DISABLED ClassDB::bind_method(D_METHOD("show_popup"), &MenuButton::show_popup); ClassDB::bind_method(D_METHOD("set_switch_on_hover", "enable"), &MenuButton::set_switch_on_hover); ClassDB::bind_method(D_METHOD("is_switch_on_hover"), &MenuButton::is_switch_on_hover); @@ -198,7 +195,7 @@ void MenuButton::_bind_methods() { base_property_helper.register_property(PropertyInfo(Variant::OBJECT, "icon", PROPERTY_HINT_RESOURCE_TYPE, "Texture2D"), defaults.icon); base_property_helper.register_property(PropertyInfo(Variant::INT, "checkable", PROPERTY_HINT_ENUM, "No,As Checkbox,As Radio Button"), defaults.checkable_type); base_property_helper.register_property(PropertyInfo(Variant::BOOL, "checked"), defaults.checked); - base_property_helper.register_property(PropertyInfo(Variant::INT, "id", PROPERTY_HINT_RANGE, "0,10,1,or_greater"), defaults.id); + base_property_helper.register_property(PropertyInfo(Variant::INT, "id", PROPERTY_HINT_RANGE, "0,10,1,or_greater", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_STORE_IF_NULL), defaults.id); base_property_helper.register_property(PropertyInfo(Variant::BOOL, "disabled"), defaults.disabled); base_property_helper.register_property(PropertyInfo(Variant::BOOL, "separator"), defaults.separator); PropertyListHelper::register_base_helper(&base_property_helper); diff --git a/scene/gui/option_button.cpp b/scene/gui/option_button.cpp index a1425fb847..5432058f7b 100644 --- a/scene/gui/option_button.cpp +++ b/scene/gui/option_button.cpp @@ -574,7 +574,7 @@ void OptionButton::_bind_methods() { base_property_helper.set_array_length_getter(&OptionButton::get_item_count); base_property_helper.register_property(PropertyInfo(Variant::STRING, "text"), defaults.text, &OptionButton::_dummy_setter, &OptionButton::get_item_text); base_property_helper.register_property(PropertyInfo(Variant::OBJECT, "icon", PROPERTY_HINT_RESOURCE_TYPE, "Texture2D"), defaults.icon, &OptionButton::_dummy_setter, &OptionButton::get_item_icon); - base_property_helper.register_property(PropertyInfo(Variant::INT, "id", PROPERTY_HINT_RANGE, "0,10,1,or_greater"), defaults.id, &OptionButton::_dummy_setter, &OptionButton::get_item_id); + base_property_helper.register_property(PropertyInfo(Variant::INT, "id", PROPERTY_HINT_RANGE, "0,10,1,or_greater", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_STORE_IF_NULL), defaults.id, &OptionButton::_dummy_setter, &OptionButton::get_item_id); base_property_helper.register_property(PropertyInfo(Variant::BOOL, "disabled"), defaults.disabled, &OptionButton::_dummy_setter, &OptionButton::is_item_disabled); base_property_helper.register_property(PropertyInfo(Variant::BOOL, "separator"), defaults.separator, &OptionButton::_dummy_setter, &OptionButton::is_item_separator); PropertyListHelper::register_base_helper(&base_property_helper); diff --git a/scene/gui/popup_menu.cpp b/scene/gui/popup_menu.cpp index f62421061b..3c04094526 100644 --- a/scene/gui/popup_menu.cpp +++ b/scene/gui/popup_menu.cpp @@ -589,6 +589,7 @@ void PopupMenu::_input_from_window_internal(const Ref<InputEvent> &p_event) { // This allows for opening the popup and triggering an action in a single mouse click. if (button_idx == MouseButton::LEFT || initial_button_mask.has_flag(mouse_button_to_mask(button_idx))) { if (b->is_pressed()) { + during_grabbed_click = false; is_scrolling = is_layout_rtl() ? b->get_position().x < item_clickable_area.position.x : b->get_position().x > item_clickable_area.size.width; if (!item_clickable_area.has_point(b->get_position())) { @@ -608,7 +609,7 @@ void PopupMenu::_input_from_window_internal(const Ref<InputEvent> &p_event) { return; } // Disable clicks under a time threshold to avoid selection right when opening the popup. - if (was_during_grabbed_click && OS::get_singleton()->get_ticks_msec() - popup_time_msec < 150) { + if (was_during_grabbed_click && OS::get_singleton()->get_ticks_msec() - popup_time_msec < 400) { return; } @@ -1064,6 +1065,7 @@ void PopupMenu::_notification(int p_what) { } break; case NOTIFICATION_POST_POPUP: { + popup_time_msec = OS::get_singleton()->get_ticks_msec(); initial_button_mask = Input::get_singleton()->get_mouse_button_mask(); during_grabbed_click = (bool)initial_button_mask; } break; @@ -2590,14 +2592,6 @@ void PopupMenu::clear_autohide_areas() { autohide_areas.clear(); } -void PopupMenu::take_mouse_focus() { - ERR_FAIL_COND(!is_inside_tree()); - - if (get_parent()) { - get_parent()->get_viewport()->pass_mouse_focus_to(this, control); - } -} - bool PopupMenu::_set(const StringName &p_name, const Variant &p_value) { if (property_helper.property_set_value(p_name, p_value)) { return true; @@ -2821,7 +2815,7 @@ void PopupMenu::_bind_methods() { base_property_helper.register_property(PropertyInfo(Variant::OBJECT, "icon", PROPERTY_HINT_RESOURCE_TYPE, "Texture2D"), defaults.icon, &PopupMenu::set_item_icon, &PopupMenu::get_item_icon); base_property_helper.register_property(PropertyInfo(Variant::INT, "checkable", PROPERTY_HINT_ENUM, "No,As checkbox,As radio button"), defaults.checkable_type, &PopupMenu::_set_item_checkable_type, &PopupMenu::_get_item_checkable_type); base_property_helper.register_property(PropertyInfo(Variant::BOOL, "checked"), defaults.checked, &PopupMenu::set_item_checked, &PopupMenu::is_item_checked); - base_property_helper.register_property(PropertyInfo(Variant::INT, "id", PROPERTY_HINT_RANGE, "0,10,1,or_greater"), defaults.id, &PopupMenu::set_item_id, &PopupMenu::get_item_id); + base_property_helper.register_property(PropertyInfo(Variant::INT, "id", PROPERTY_HINT_RANGE, "0,10,1,or_greater", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_STORE_IF_NULL), defaults.id, &PopupMenu::set_item_id, &PopupMenu::get_item_id); base_property_helper.register_property(PropertyInfo(Variant::BOOL, "disabled"), defaults.disabled, &PopupMenu::set_item_disabled, &PopupMenu::is_item_disabled); base_property_helper.register_property(PropertyInfo(Variant::BOOL, "separator"), defaults.separator, &PopupMenu::set_item_as_separator, &PopupMenu::is_item_separator); PropertyListHelper::register_base_helper(&base_property_helper); diff --git a/scene/gui/popup_menu.h b/scene/gui/popup_menu.h index 5313dae404..b8aa51c1ad 100644 --- a/scene/gui/popup_menu.h +++ b/scene/gui/popup_menu.h @@ -369,8 +369,6 @@ public: virtual void popup(const Rect2i &p_bounds = Rect2i()) override; virtual void set_visible(bool p_visible) override; - void take_mouse_focus(); - PopupMenu(); ~PopupMenu(); }; diff --git a/scene/gui/range.cpp b/scene/gui/range.cpp index 83359653f1..d7b1a4933d 100644 --- a/scene/gui/range.cpp +++ b/scene/gui/range.cpp @@ -31,7 +31,7 @@ #include "range.h" PackedStringArray Range::get_configuration_warnings() const { - PackedStringArray warnings = Node::get_configuration_warnings(); + PackedStringArray warnings = Control::get_configuration_warnings(); if (shared->exp_ratio && shared->min <= 0) { warnings.push_back(RTR("If \"Exp Edit\" is enabled, \"Min Value\" must be greater than 0.")); diff --git a/scene/gui/rich_text_label.cpp b/scene/gui/rich_text_label.cpp index 1da3668ebe..0c3c90d070 100644 --- a/scene/gui/rich_text_label.cpp +++ b/scene/gui/rich_text_label.cpp @@ -333,6 +333,8 @@ void RichTextLabel::_update_line_font(ItemFrame *p_frame, int p_line, const Ref< font_size = font_size_it->font_size; } TS->shaped_set_span_update_font(t, i, font->get_rids(), font_size, font->get_opentype_features()); + } else { + TS->shaped_set_span_update_font(t, i, p_base_font->get_rids(), p_base_font_size, p_base_font->get_opentype_features()); } } @@ -547,7 +549,7 @@ float RichTextLabel::_shape_line(ItemFrame *p_frame, int p_line, const Ref<Font> if (font_size_it && font_size_it->font_size > 0) { font_size = font_size_it->font_size; } - l.text_buf->add_string("\n", font, font_size); + l.text_buf->add_string(String::chr(0x200B), font, font_size); txt += "\n"; l.char_count++; remaining_characters--; @@ -817,6 +819,8 @@ int RichTextLabel::_draw_line(ItemFrame *p_frame, int p_line, const Vector2 &p_o } int line_count = 0; + // Bottom margin for text clipping. + float v_limit = theme_cache.normal_style->get_margin(SIDE_BOTTOM); Size2 ctrl_size = get_size(); // Draw text. for (int line = 0; line < l.text_buf->get_line_count(); line++) { @@ -824,7 +828,7 @@ int RichTextLabel::_draw_line(ItemFrame *p_frame, int p_line, const Vector2 &p_o off.y += theme_cache.line_separation; } - if (p_ofs.y + off.y >= ctrl_size.height) { + if (p_ofs.y + off.y >= ctrl_size.height - v_limit) { break; } @@ -910,84 +914,6 @@ int RichTextLabel::_draw_line(ItemFrame *p_frame, int p_line, const Vector2 &p_o double uth = TS->shaped_text_get_underline_thickness(rid); off.y += l_ascent; - // Draw inlined objects. - Array objects = TS->shaped_text_get_objects(rid); - for (int i = 0; i < objects.size(); i++) { - Item *it = items.get_or_null(objects[i]); - if (it != nullptr) { - Vector2i obj_range = TS->shaped_text_get_object_range(rid, objects[i]); - if (trim_chars && l.char_offset + obj_range.y > visible_characters) { - continue; - } - if (trim_glyphs_ltr || trim_glyphs_rtl) { - int obj_glyph = r_processed_glyphs + TS->shaped_text_get_object_glyph(rid, objects[i]); - if ((trim_glyphs_ltr && (obj_glyph >= visible_glyphs)) || (trim_glyphs_rtl && (obj_glyph < total_glyphs - visible_glyphs))) { - continue; - } - } - Rect2 rect = TS->shaped_text_get_object_rect(rid, objects[i]); - switch (it->type) { - case ITEM_IMAGE: { - ItemImage *img = static_cast<ItemImage *>(it); - if (img->pad) { - Size2 pad_size = rect.size.min(img->image->get_size()); - Vector2 pad_off = (rect.size - pad_size) / 2; - img->image->draw_rect(ci, Rect2(p_ofs + rect.position + off + pad_off, pad_size), false, img->color); - } else { - img->image->draw_rect(ci, Rect2(p_ofs + rect.position + off, rect.size), false, img->color); - } - } break; - case ITEM_TABLE: { - ItemTable *table = static_cast<ItemTable *>(it); - Color odd_row_bg = theme_cache.table_odd_row_bg; - Color even_row_bg = theme_cache.table_even_row_bg; - Color border = theme_cache.table_border; - float h_separation = theme_cache.table_h_separation; - float v_separation = theme_cache.table_v_separation; - - int col_count = table->columns.size(); - int row_count = table->rows.size(); - - int idx = 0; - for (Item *E : table->subitems) { - ItemFrame *frame = static_cast<ItemFrame *>(E); - - int col = idx % col_count; - int row = idx / col_count; - - if (frame->lines.size() != 0 && row < row_count) { - Vector2 coff = frame->lines[0].offset; - if (rtl) { - coff.x = rect.size.width - table->columns[col].width - coff.x; - } - if (row % 2 == 0) { - Color c = frame->odd_row_bg != Color(0, 0, 0, 0) ? frame->odd_row_bg : odd_row_bg; - if (c.a > 0.0) { - draw_rect(Rect2(p_ofs + rect.position + off + coff - frame->padding.position - Vector2(h_separation * 0.5, v_separation * 0.5).floor(), Size2(table->columns[col].width + h_separation + frame->padding.position.x + frame->padding.size.x, table->rows[row])), c, true); - } - } else { - Color c = frame->even_row_bg != Color(0, 0, 0, 0) ? frame->even_row_bg : even_row_bg; - if (c.a > 0.0) { - draw_rect(Rect2(p_ofs + rect.position + off + coff - frame->padding.position - Vector2(h_separation * 0.5, v_separation * 0.5).floor(), Size2(table->columns[col].width + h_separation + frame->padding.position.x + frame->padding.size.x, table->rows[row])), c, true); - } - } - Color bc = frame->border != Color(0, 0, 0, 0) ? frame->border : border; - if (bc.a > 0.0) { - draw_rect(Rect2(p_ofs + rect.position + off + coff - frame->padding.position - Vector2(h_separation * 0.5, v_separation * 0.5).floor(), Size2(table->columns[col].width + h_separation + frame->padding.position.x + frame->padding.size.x, table->rows[row])), bc, false); - } - } - - 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); - } - idx++; - } - } break; - default: - break; - } - } - } const Glyph *glyphs = TS->shaped_text_get_glyphs(rid); int gl_size = TS->shaped_text_get_glyph_count(rid); @@ -1003,6 +929,86 @@ int RichTextLabel::_draw_line(ItemFrame *p_frame, int p_line, const Vector2 &p_o int processed_glyphs_step = 0; for (int step = DRAW_STEP_BACKGROUND; step < DRAW_STEP_MAX; step++) { + if (step == DRAW_STEP_TEXT) { + // Draw inlined objects. + Array objects = TS->shaped_text_get_objects(rid); + for (int i = 0; i < objects.size(); i++) { + Item *it = items.get_or_null(objects[i]); + if (it != nullptr) { + Vector2i obj_range = TS->shaped_text_get_object_range(rid, objects[i]); + if (trim_chars && l.char_offset + obj_range.y > visible_characters) { + continue; + } + if (trim_glyphs_ltr || trim_glyphs_rtl) { + int obj_glyph = r_processed_glyphs + TS->shaped_text_get_object_glyph(rid, objects[i]); + if ((trim_glyphs_ltr && (obj_glyph >= visible_glyphs)) || (trim_glyphs_rtl && (obj_glyph < total_glyphs - visible_glyphs))) { + continue; + } + } + Rect2 rect = TS->shaped_text_get_object_rect(rid, objects[i]); + switch (it->type) { + case ITEM_IMAGE: { + ItemImage *img = static_cast<ItemImage *>(it); + if (img->pad) { + Size2 pad_size = rect.size.min(img->image->get_size()); + Vector2 pad_off = (rect.size - pad_size) / 2; + img->image->draw_rect(ci, Rect2(p_ofs + rect.position + off + pad_off, pad_size), false, img->color); + } else { + img->image->draw_rect(ci, Rect2(p_ofs + rect.position + off, rect.size), false, img->color); + } + } break; + case ITEM_TABLE: { + ItemTable *table = static_cast<ItemTable *>(it); + Color odd_row_bg = theme_cache.table_odd_row_bg; + Color even_row_bg = theme_cache.table_even_row_bg; + Color border = theme_cache.table_border; + float h_separation = theme_cache.table_h_separation; + float v_separation = theme_cache.table_v_separation; + + int col_count = table->columns.size(); + int row_count = table->rows.size(); + + int idx = 0; + for (Item *E : table->subitems) { + ItemFrame *frame = static_cast<ItemFrame *>(E); + + int col = idx % col_count; + int row = idx / col_count; + + if (frame->lines.size() != 0 && row < row_count) { + Vector2 coff = frame->lines[0].offset; + if (rtl) { + coff.x = rect.size.width - table->columns[col].width - coff.x; + } + if (row % 2 == 0) { + Color c = frame->odd_row_bg != Color(0, 0, 0, 0) ? frame->odd_row_bg : odd_row_bg; + if (c.a > 0.0) { + draw_rect(Rect2(p_ofs + rect.position + off + coff - frame->padding.position - Vector2(h_separation * 0.5, v_separation * 0.5).floor(), Size2(table->columns[col].width + h_separation + frame->padding.position.x + frame->padding.size.x, table->rows[row])), c, true); + } + } else { + Color c = frame->even_row_bg != Color(0, 0, 0, 0) ? frame->even_row_bg : even_row_bg; + if (c.a > 0.0) { + draw_rect(Rect2(p_ofs + rect.position + off + coff - frame->padding.position - Vector2(h_separation * 0.5, v_separation * 0.5).floor(), Size2(table->columns[col].width + h_separation + frame->padding.position.x + frame->padding.size.x, table->rows[row])), c, true); + } + } + Color bc = frame->border != Color(0, 0, 0, 0) ? frame->border : border; + if (bc.a > 0.0) { + draw_rect(Rect2(p_ofs + rect.position + off + coff - frame->padding.position - Vector2(h_separation * 0.5, v_separation * 0.5).floor(), Size2(table->columns[col].width + h_separation + frame->padding.position.x + frame->padding.size.x, table->rows[row])), bc, false); + } + } + + 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); + } + idx++; + } + } break; + default: + break; + } + } + } + } Vector2 off_step = off; processed_glyphs_step = r_processed_glyphs; @@ -1448,6 +1454,7 @@ float RichTextLabel::_find_click_in_line(ItemFrame *p_frame, int p_line, const V bool line_clicked = false; float text_rect_begin = 0.0; int char_pos = -1; + bool char_clicked = false; Line &l = p_frame->lines[p_line]; MutexLock lock(l.text_buf->get_mutex()); @@ -1578,6 +1585,9 @@ float RichTextLabel::_find_click_in_line(ItemFrame *p_frame, int p_line, const V } if (p_click.y >= rect.position.y && p_click.y <= rect.position.y + rect.size.y) { + if (!p_meta) { + char_pos = rtl ? TS->shaped_text_get_range(rid).y : TS->shaped_text_get_range(rid).x; + } if ((!rtl && p_click.x >= rect.position.x) || (rtl && p_click.x <= rect.position.x + rect.size.x)) { if (p_meta) { int64_t glyph_idx = TS->shaped_text_hit_test_grapheme(rid, p_click.x - rect.position.x); @@ -1592,6 +1602,7 @@ float RichTextLabel::_find_click_in_line(ItemFrame *p_frame, int p_line, const V obj_rect.position.y += baseline_y; if (p_click.y >= obj_rect.position.y && p_click.y <= obj_rect.position.y + obj_rect.size.y) { char_pos = glyphs[glyph_idx].start; + char_clicked = true; } break; } @@ -1602,18 +1613,21 @@ float RichTextLabel::_find_click_in_line(ItemFrame *p_frame, int p_line, const V float fd = TS->font_get_descent(glyphs[glyph_idx].font_rid, glyphs[glyph_idx].font_size); if (p_click.y >= baseline_y - fa && p_click.y <= baseline_y + fd) { char_pos = glyphs[glyph_idx].start; + char_clicked = true; } } else if (!(glyphs[glyph_idx].flags & TextServer::GRAPHEME_IS_VIRTUAL)) { // Hex code box. Vector2 gl_size = TS->get_hex_code_box_size(glyphs[glyph_idx].font_size, glyphs[glyph_idx].index); if (p_click.y >= baseline_y - gl_size.y * 0.9 && p_click.y <= baseline_y + gl_size.y * 0.2) { char_pos = glyphs[glyph_idx].start; + char_clicked = true; } } } } else { char_pos = TS->shaped_text_hit_test_position(rid, p_click.x - rect.position.x); char_pos = TS->shaped_text_closest_character_pos(rid, char_pos); + char_clicked = true; } } line_clicked = true; @@ -1621,7 +1635,7 @@ float RichTextLabel::_find_click_in_line(ItemFrame *p_frame, int p_line, const V } // If table hit was detected, and line hit is in the table bounds use table hit. - if (table_hit && (((char_pos + p_frame->lines[p_line].char_offset) >= table_range.x && (char_pos + p_frame->lines[p_line].char_offset) <= table_range.y) || char_pos == -1)) { + if (table_hit && (((char_pos + p_frame->lines[p_line].char_offset) >= table_range.x && (char_pos + p_frame->lines[p_line].char_offset) <= table_range.y) || !char_clicked)) { if (r_click_frame != nullptr) { *r_click_frame = table_click_frame; } @@ -1792,13 +1806,13 @@ void RichTextLabel::_notification(int p_what) { case NOTIFICATION_RESIZED: { _stop_thread(); - main->first_resized_line.store(0); //invalidate ALL + main->first_resized_line.store(0); // Invalidate all lines. queue_redraw(); } break; case NOTIFICATION_THEME_CHANGED: { _stop_thread(); - main->first_invalid_font_line.store(0); //invalidate ALL + main->first_invalid_font_line.store(0); // Invalidate all lines. queue_redraw(); } break; @@ -1808,7 +1822,7 @@ void RichTextLabel::_notification(int p_what) { set_text(text); } - main->first_invalid_line.store(0); //invalidate ALL + main->first_invalid_line.store(0); // Invalidate all lines. queue_redraw(); } break; @@ -1882,10 +1896,12 @@ void RichTextLabel::_notification(int p_what) { 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); int processed_glyphs = 0; - while (ofs.y < size.height && from_line < to_line) { + while (ofs.y < size.height - v_limit && from_line < to_line) { MutexLock lock(main->lines[from_line].text_buf->get_mutex()); visible_paragraph_count++; @@ -1897,7 +1913,7 @@ void RichTextLabel::_notification(int p_what) { case NOTIFICATION_INTERNAL_PROCESS: { if (is_visible_in_tree()) { - if (!is_ready()) { + if (!is_finished()) { return; } double dt = get_process_delta_time(); @@ -2520,7 +2536,7 @@ PackedFloat32Array RichTextLabel::_find_tab_stops(Item *p_item) { item = item->parent; } - return PackedFloat32Array(); + return default_tab_stops; } HorizontalAlignment RichTextLabel::_find_alignment(Item *p_item) { @@ -2788,7 +2804,7 @@ int RichTextLabel::get_pending_paragraphs() const { return lines - to_line; } -bool RichTextLabel::is_ready() const { +bool RichTextLabel::is_finished() const { const_cast<RichTextLabel *>(this)->_validate_line_caches(); if (updating.load()) { @@ -3031,7 +3047,7 @@ void RichTextLabel::add_text(const String &p_text) { int pos = 0; while (pos < p_text.length()) { - int end = p_text.find("\n", pos); + int end = p_text.find_char('\n', pos); String line; bool eol = false; if (end == -1) { @@ -3398,6 +3414,21 @@ bool RichTextLabel::remove_paragraph(int p_paragraph, bool p_no_invalidate) { selection.click_item = nullptr; selection.active = false; + if (is_processing_internal()) { + bool process_enabled = false; + Item *it = main; + while (it) { + Vector<ItemFX *> fx_stack; + _fetch_item_fx_stack(it, fx_stack); + if (fx_stack.size()) { + process_enabled = true; + break; + } + it = _get_next_item(it, true); + } + set_process_internal(process_enabled); + } + if (p_no_invalidate) { // Do not invalidate cache, only update vertical offsets of the paragraphs after deleted one and scrollbar. int to_line = main->first_invalid_line.load() - 1; @@ -3971,6 +4002,7 @@ void RichTextLabel::pop_all() { void RichTextLabel::clear() { _stop_thread(); + set_process_internal(false); MutexLock data_lock(data_mutex); main->_clear_children(); @@ -4080,6 +4112,74 @@ void RichTextLabel::parse_bbcode(const String &p_bbcode) { append_text(p_bbcode); } +String RichTextLabel::_get_tag_value(const String &p_tag) { + return p_tag.substr(p_tag.find_char('=') + 1); +} + +int RichTextLabel::_find_unquoted(const String &p_src, char32_t p_chr, int p_from) { + if (p_from < 0) { + return -1; + } + + const int len = p_src.length(); + if (len == 0) { + return -1; + } + + const char32_t *src = p_src.get_data(); + bool in_single_quote = false; + bool in_double_quote = false; + for (int i = p_from; i < len; i++) { + if (in_double_quote) { + if (src[i] == '"') { + in_double_quote = false; + } + } else if (in_single_quote) { + if (src[i] == '\'') { + in_single_quote = false; + } + } else { + if (src[i] == '"') { + in_double_quote = true; + } else if (src[i] == '\'') { + in_single_quote = true; + } else if (src[i] == p_chr) { + return i; + } + } + } + + return -1; +} + +Vector<String> RichTextLabel::_split_unquoted(const String &p_src, char32_t p_splitter) { + Vector<String> ret; + + if (p_src.is_empty()) { + return ret; + } + + int from = 0; + int len = p_src.length(); + + while (true) { + int end = _find_unquoted(p_src, p_splitter, from); + if (end < 0) { + end = len; + } + if (end > from) { + ret.push_back(p_src.substr(from, end - from)); + } + if (end == len) { + break; + } + + from = end + 1; + } + + return ret; +} + void RichTextLabel::append_text(const String &p_bbcode) { _stop_thread(); MutexLock data_lock(data_mutex); @@ -4095,10 +4195,8 @@ void RichTextLabel::append_text(const String &p_bbcode) { bool after_list_open_tag = false; bool after_list_close_tag = false; - set_process_internal(false); - while (pos <= p_bbcode.length()) { - int brk_pos = p_bbcode.find("[", pos); + int brk_pos = p_bbcode.find_char('[', pos); if (brk_pos < 0) { brk_pos = p_bbcode.length(); @@ -4123,7 +4221,7 @@ void RichTextLabel::append_text(const String &p_bbcode) { break; //nothing else to add } - int brk_end = p_bbcode.find("]", brk_pos + 1); + int brk_end = _find_unquoted(p_bbcode, ']', brk_pos + 1); if (brk_end == -1) { //no close, add the rest @@ -4133,7 +4231,7 @@ void RichTextLabel::append_text(const String &p_bbcode) { } String tag = p_bbcode.substr(brk_pos + 1, brk_end - brk_pos - 1); - Vector<String> split_tag_block = tag.split(" ", false); + Vector<String> split_tag_block = _split_unquoted(tag, ' '); // Find optional parameters. String bbcode_name; @@ -4143,7 +4241,7 @@ void RichTextLabel::append_text(const String &p_bbcode) { bbcode_name = split_tag_block[0]; for (int i = 1; i < split_tag_block.size(); i++) { const String &expr = split_tag_block[i]; - int value_pos = expr.find("="); + int value_pos = expr.find_char('='); if (value_pos > -1) { bbcode_options[expr.substr(0, value_pos)] = expr.substr(value_pos + 1).unquote(); } @@ -4154,7 +4252,7 @@ void RichTextLabel::append_text(const String &p_bbcode) { // Find main parameter. String bbcode_value; - int main_value_pos = bbcode_name.find("="); + int main_value_pos = bbcode_name.find_char('='); if (main_value_pos > -1) { bbcode_value = bbcode_name.substr(main_value_pos + 1); bbcode_name = bbcode_name.substr(0, main_value_pos); @@ -4253,10 +4351,10 @@ void RichTextLabel::append_text(const String &p_bbcode) { pos = brk_end + 1; tag_stack.push_front(tag); } else if (tag.begins_with("table=")) { - Vector<String> subtag = tag.substr(6, tag.length()).split(","); + Vector<String> subtag = _split_unquoted(_get_tag_value(tag), U','); _normalize_subtags(subtag); - int columns = subtag[0].to_int(); + int columns = (subtag.is_empty()) ? 1 : subtag[0].to_int(); if (columns < 1) { columns = 1; } @@ -4303,7 +4401,7 @@ void RichTextLabel::append_text(const String &p_bbcode) { pos = brk_end + 1; tag_stack.push_front(tag); } else if (tag.begins_with("cell=")) { - int ratio = tag.substr(5, tag.length()).to_int(); + int ratio = _get_tag_value(tag).to_int(); if (ratio < 1) { ratio = 1; } @@ -4314,54 +4412,45 @@ void RichTextLabel::append_text(const String &p_bbcode) { pos = brk_end + 1; tag_stack.push_front("cell"); } else if (tag.begins_with("cell ")) { - Vector<String> subtag = tag.substr(5, tag.length()).split(" "); - _normalize_subtags(subtag); - - for (int i = 0; i < subtag.size(); i++) { - Vector<String> subtag_a = subtag[i].split("="); - _normalize_subtags(subtag_a); - - if (subtag_a.size() == 2) { - if (subtag_a[0] == "expand") { - int ratio = subtag_a[1].to_int(); - if (ratio < 1) { - ratio = 1; - } - set_table_column_expand(get_current_table_column(), true, ratio); - } + OptionMap::Iterator expand_option = bbcode_options.find("expand"); + if (expand_option) { + int ratio = expand_option->value.to_int(); + if (ratio < 1) { + ratio = 1; } + set_table_column_expand(get_current_table_column(), true, ratio); } + push_cell(); const Color fallback_color = Color(0, 0, 0, 0); - for (int i = 0; i < subtag.size(); i++) { - Vector<String> subtag_a = subtag[i].split("="); - _normalize_subtags(subtag_a); - - if (subtag_a.size() == 2) { - if (subtag_a[0] == "border") { - Color color = Color::from_string(subtag_a[1], fallback_color); - set_cell_border_color(color); - } else if (subtag_a[0] == "bg") { - Vector<String> subtag_b = subtag_a[1].split(","); - _normalize_subtags(subtag_b); - if (subtag_b.size() == 2) { - Color color1 = Color::from_string(subtag_b[0], fallback_color); - Color color2 = Color::from_string(subtag_b[1], fallback_color); - set_cell_row_background_color(color1, color2); - } - if (subtag_b.size() == 1) { - Color color1 = Color::from_string(subtag_a[1], fallback_color); - set_cell_row_background_color(color1, color1); - } - } else if (subtag_a[0] == "padding") { - Vector<String> subtag_b = subtag_a[1].split(","); - _normalize_subtags(subtag_b); + OptionMap::Iterator border_option = bbcode_options.find("border"); + if (border_option) { + Color color = Color::from_string(border_option->value, fallback_color); + set_cell_border_color(color); + } + OptionMap::Iterator bg_option = bbcode_options.find("bg"); + if (bg_option) { + Vector<String> subtag_b = _split_unquoted(bg_option->value, U','); + _normalize_subtags(subtag_b); - if (subtag_b.size() == 4) { - set_cell_padding(Rect2(subtag_b[0].to_float(), subtag_b[1].to_float(), subtag_b[2].to_float(), subtag_b[3].to_float())); - } - } + if (subtag_b.size() == 2) { + Color color1 = Color::from_string(subtag_b[0], fallback_color); + Color color2 = Color::from_string(subtag_b[1], fallback_color); + set_cell_row_background_color(color1, color2); + } + if (subtag_b.size() == 1) { + Color color1 = Color::from_string(bg_option->value, fallback_color); + set_cell_row_background_color(color1, color1); + } + } + OptionMap::Iterator padding_option = bbcode_options.find("padding"); + if (padding_option) { + Vector<String> subtag_b = _split_unquoted(padding_option->value, U','); + _normalize_subtags(subtag_b); + + if (subtag_b.size() == 4) { + set_cell_padding(Rect2(subtag_b[0].to_float(), subtag_b[1].to_float(), subtag_b[2].to_float(), subtag_b[3].to_float())); } } @@ -4378,7 +4467,7 @@ void RichTextLabel::append_text(const String &p_bbcode) { pos = brk_end + 1; tag_stack.push_front(tag); } else if (tag.begins_with("char=")) { - int32_t char_code = tag.substr(5, tag.length()).hex_to_int(); + int32_t char_code = _get_tag_value(tag).hex_to_int(); add_text(String::chr(char_code)); pos = brk_end + 1; } else if (tag == "lb") { @@ -4436,19 +4525,19 @@ void RichTextLabel::append_text(const String &p_bbcode) { add_text(String::chr(0x00AD)); pos = brk_end + 1; } else if (tag == "center") { - push_paragraph(HORIZONTAL_ALIGNMENT_CENTER); + push_paragraph(HORIZONTAL_ALIGNMENT_CENTER, text_direction, language, st_parser, default_jst_flags, default_tab_stops); pos = brk_end + 1; tag_stack.push_front(tag); } else if (tag == "fill") { - push_paragraph(HORIZONTAL_ALIGNMENT_FILL); + push_paragraph(HORIZONTAL_ALIGNMENT_FILL, text_direction, language, st_parser, default_jst_flags, default_tab_stops); pos = brk_end + 1; tag_stack.push_front(tag); } else if (tag == "left") { - push_paragraph(HORIZONTAL_ALIGNMENT_LEFT); + push_paragraph(HORIZONTAL_ALIGNMENT_LEFT, text_direction, language, st_parser, default_jst_flags, default_tab_stops); pos = brk_end + 1; tag_stack.push_front(tag); } else if (tag == "right") { - push_paragraph(HORIZONTAL_ALIGNMENT_RIGHT); + push_paragraph(HORIZONTAL_ALIGNMENT_RIGHT, text_direction, language, st_parser, default_jst_flags, default_tab_stops); pos = brk_end + 1; tag_stack.push_front(tag); } else if (tag == "ul") { @@ -4457,7 +4546,7 @@ void RichTextLabel::append_text(const String &p_bbcode) { pos = brk_end + 1; tag_stack.push_front(tag); } else if (tag.begins_with("ul bullet=")) { - String bullet = tag.substr(10, 1); + String bullet = _get_tag_value(tag); indent_level++; push_list(indent_level, LIST_DOTS, false, bullet); pos = brk_end + 1; @@ -4493,7 +4582,7 @@ void RichTextLabel::append_text(const String &p_bbcode) { pos = brk_end + 1; tag_stack.push_front(tag); } else if (tag.begins_with("lang=")) { - String lang = tag.substr(5, tag.length()).unquote(); + String lang = _get_tag_value(tag).unquote(); push_language(lang); pos = brk_end + 1; tag_stack.push_front("lang"); @@ -4502,89 +4591,104 @@ void RichTextLabel::append_text(const String &p_bbcode) { pos = brk_end + 1; tag_stack.push_front("p"); } else if (tag.begins_with("p ")) { - Vector<String> subtag = tag.substr(2, tag.length()).split(" "); - _normalize_subtags(subtag); - HorizontalAlignment alignment = HORIZONTAL_ALIGNMENT_LEFT; Control::TextDirection dir = Control::TEXT_DIRECTION_INHERITED; - String lang; - PackedFloat32Array tab_stops; + String lang = language; + PackedFloat32Array tab_stops = default_tab_stops; TextServer::StructuredTextParser st_parser_type = TextServer::STRUCTURED_TEXT_DEFAULT; BitField<TextServer::JustificationFlag> jst_flags = default_jst_flags; - for (int i = 0; i < subtag.size(); i++) { - Vector<String> subtag_a = subtag[i].split("="); - _normalize_subtags(subtag_a); - - if (subtag_a.size() == 2) { - if (subtag_a[0] == "justification_flags" || subtag_a[0] == "jst") { - Vector<String> subtag_b = subtag_a[1].split(","); - jst_flags = 0; // Clear flags. - for (const String &E : subtag_b) { - if (E == "kashida" || E == "k") { - jst_flags.set_flag(TextServer::JUSTIFICATION_KASHIDA); - } else if (E == "word" || E == "w") { - jst_flags.set_flag(TextServer::JUSTIFICATION_WORD_BOUND); - } else if (E == "trim" || E == "tr") { - jst_flags.set_flag(TextServer::JUSTIFICATION_TRIM_EDGE_SPACES); - } else if (E == "after_last_tab" || E == "lt") { - jst_flags.set_flag(TextServer::JUSTIFICATION_AFTER_LAST_TAB); - } else if (E == "skip_last" || E == "sl") { - jst_flags.set_flag(TextServer::JUSTIFICATION_SKIP_LAST_LINE); - } else if (E == "skip_last_with_chars" || E == "sv") { - jst_flags.set_flag(TextServer::JUSTIFICATION_SKIP_LAST_LINE_WITH_VISIBLE_CHARS); - } else if (E == "do_not_skip_single" || E == "ns") { - jst_flags.set_flag(TextServer::JUSTIFICATION_DO_NOT_SKIP_SINGLE_LINE); - } - } - } else if (subtag_a[0] == "tab_stops") { - Vector<String> splitters; - splitters.push_back(","); - splitters.push_back(";"); - tab_stops = subtag_a[1].split_floats_mk(splitters); - } else if (subtag_a[0] == "align") { - if (subtag_a[1] == "l" || subtag_a[1] == "left") { - alignment = HORIZONTAL_ALIGNMENT_LEFT; - } else if (subtag_a[1] == "c" || subtag_a[1] == "center") { - alignment = HORIZONTAL_ALIGNMENT_CENTER; - } else if (subtag_a[1] == "r" || subtag_a[1] == "right") { - alignment = HORIZONTAL_ALIGNMENT_RIGHT; - } else if (subtag_a[1] == "f" || subtag_a[1] == "fill") { - alignment = HORIZONTAL_ALIGNMENT_FILL; - } - } else if (subtag_a[0] == "dir" || subtag_a[0] == "direction") { - if (subtag_a[1] == "a" || subtag_a[1] == "auto") { - dir = Control::TEXT_DIRECTION_AUTO; - } else if (subtag_a[1] == "l" || subtag_a[1] == "ltr") { - dir = Control::TEXT_DIRECTION_LTR; - } else if (subtag_a[1] == "r" || subtag_a[1] == "rtl") { - dir = Control::TEXT_DIRECTION_RTL; - } - } else if (subtag_a[0] == "lang" || subtag_a[0] == "language") { - lang = subtag_a[1]; - } else if (subtag_a[0] == "st" || subtag_a[0] == "bidi_override") { - if (subtag_a[1] == "d" || subtag_a[1] == "default") { - st_parser_type = TextServer::STRUCTURED_TEXT_DEFAULT; - } else if (subtag_a[1] == "u" || subtag_a[1] == "uri") { - st_parser_type = TextServer::STRUCTURED_TEXT_URI; - } else if (subtag_a[1] == "f" || subtag_a[1] == "file") { - st_parser_type = TextServer::STRUCTURED_TEXT_FILE; - } else if (subtag_a[1] == "e" || subtag_a[1] == "email") { - st_parser_type = TextServer::STRUCTURED_TEXT_EMAIL; - } else if (subtag_a[1] == "l" || subtag_a[1] == "list") { - st_parser_type = TextServer::STRUCTURED_TEXT_LIST; - } else if (subtag_a[1] == "n" || subtag_a[1] == "gdscript") { - st_parser_type = TextServer::STRUCTURED_TEXT_GDSCRIPT; - } else if (subtag_a[1] == "c" || subtag_a[1] == "custom") { - st_parser_type = TextServer::STRUCTURED_TEXT_CUSTOM; - } + + OptionMap::Iterator justification_flags_option = bbcode_options.find("justification_flags"); + if (!justification_flags_option) { + justification_flags_option = bbcode_options.find("jst"); + } + if (justification_flags_option) { + Vector<String> subtag_b = _split_unquoted(justification_flags_option->value, U','); + jst_flags = 0; // Clear flags. + for (const String &E : subtag_b) { + if (E == "kashida" || E == "k") { + jst_flags.set_flag(TextServer::JUSTIFICATION_KASHIDA); + } else if (E == "word" || E == "w") { + jst_flags.set_flag(TextServer::JUSTIFICATION_WORD_BOUND); + } else if (E == "trim" || E == "tr") { + jst_flags.set_flag(TextServer::JUSTIFICATION_TRIM_EDGE_SPACES); + } else if (E == "after_last_tab" || E == "lt") { + jst_flags.set_flag(TextServer::JUSTIFICATION_AFTER_LAST_TAB); + } else if (E == "skip_last" || E == "sl") { + jst_flags.set_flag(TextServer::JUSTIFICATION_SKIP_LAST_LINE); + } else if (E == "skip_last_with_chars" || E == "sv") { + jst_flags.set_flag(TextServer::JUSTIFICATION_SKIP_LAST_LINE_WITH_VISIBLE_CHARS); + } else if (E == "do_not_skip_single" || E == "ns") { + jst_flags.set_flag(TextServer::JUSTIFICATION_DO_NOT_SKIP_SINGLE_LINE); } } } + OptionMap::Iterator tab_stops_option = bbcode_options.find("tab_stops"); + if (tab_stops_option) { + Vector<String> splitters; + splitters.push_back(","); + splitters.push_back(";"); + tab_stops = tab_stops_option->value.split_floats_mk(splitters); + } + OptionMap::Iterator align_option = bbcode_options.find("align"); + if (align_option) { + if (align_option->value == "l" || align_option->value == "left") { + alignment = HORIZONTAL_ALIGNMENT_LEFT; + } else if (align_option->value == "c" || align_option->value == "center") { + alignment = HORIZONTAL_ALIGNMENT_CENTER; + } else if (align_option->value == "r" || align_option->value == "right") { + alignment = HORIZONTAL_ALIGNMENT_RIGHT; + } else if (align_option->value == "f" || align_option->value == "fill") { + alignment = HORIZONTAL_ALIGNMENT_FILL; + } + } + OptionMap::Iterator direction_option = bbcode_options.find("direction"); + if (!direction_option) { + direction_option = bbcode_options.find("dir"); + } + if (direction_option) { + if (direction_option->value == "a" || direction_option->value == "auto") { + dir = Control::TEXT_DIRECTION_AUTO; + } else if (direction_option->value == "l" || direction_option->value == "ltr") { + dir = Control::TEXT_DIRECTION_LTR; + } else if (direction_option->value == "r" || direction_option->value == "rtl") { + dir = Control::TEXT_DIRECTION_RTL; + } + } + OptionMap::Iterator language_option = bbcode_options.find("language"); + if (!language_option) { + language_option = bbcode_options.find("lang"); + } + if (language_option) { + lang = language_option->value; + } + OptionMap::Iterator bidi_override_option = bbcode_options.find("bidi_override"); + if (!bidi_override_option) { + bidi_override_option = bbcode_options.find("st"); + } + if (bidi_override_option) { + if (bidi_override_option->value == "d" || bidi_override_option->value == "default") { + st_parser_type = TextServer::STRUCTURED_TEXT_DEFAULT; + } else if (bidi_override_option->value == "u" || bidi_override_option->value == "uri") { + st_parser_type = TextServer::STRUCTURED_TEXT_URI; + } else if (bidi_override_option->value == "f" || bidi_override_option->value == "file") { + st_parser_type = TextServer::STRUCTURED_TEXT_FILE; + } else if (bidi_override_option->value == "e" || bidi_override_option->value == "email") { + st_parser_type = TextServer::STRUCTURED_TEXT_EMAIL; + } else if (bidi_override_option->value == "l" || bidi_override_option->value == "list") { + st_parser_type = TextServer::STRUCTURED_TEXT_LIST; + } else if (bidi_override_option->value == "n" || bidi_override_option->value == "gdscript") { + st_parser_type = TextServer::STRUCTURED_TEXT_GDSCRIPT; + } else if (bidi_override_option->value == "c" || bidi_override_option->value == "custom") { + st_parser_type = TextServer::STRUCTURED_TEXT_CUSTOM; + } + } + push_paragraph(alignment, dir, lang, st_parser_type, jst_flags, tab_stops); pos = brk_end + 1; tag_stack.push_front("p"); } else if (tag == "url") { - int end = p_bbcode.find("[", brk_end); + int end = p_bbcode.find_char('[', brk_end); if (end == -1) { end = p_bbcode.length(); } @@ -4595,19 +4699,16 @@ void RichTextLabel::append_text(const String &p_bbcode) { tag_stack.push_front(tag); } else if (tag.begins_with("url=")) { - String url = tag.substr(4, tag.length()).unquote(); + String url = _get_tag_value(tag).unquote(); push_meta(url, META_UNDERLINE_ALWAYS); pos = brk_end + 1; tag_stack.push_front("url"); } else if (tag.begins_with("hint=")) { - String description = tag.substr(5, tag.length()).unquote(); + String description = _get_tag_value(tag).unquote(); push_hint(description); pos = brk_end + 1; tag_stack.push_front("hint"); } else if (tag.begins_with("dropcap")) { - Vector<String> subtag = tag.substr(5, tag.length()).split(" "); - _normalize_subtags(subtag); - int fs = theme_cache.normal_font_size * 3; Ref<Font> f = theme_cache.normal_font; Color color = theme_cache.default_color; @@ -4615,39 +4716,47 @@ void RichTextLabel::append_text(const String &p_bbcode) { int outline_size = theme_cache.outline_size; Rect2 dropcap_margins; - for (int i = 0; i < subtag.size(); i++) { - Vector<String> subtag_a = subtag[i].split("="); - _normalize_subtags(subtag_a); - - if (subtag_a.size() == 2) { - if (subtag_a[0] == "font" || subtag_a[0] == "f") { - const String &fnt = subtag_a[1]; - Ref<Font> font = ResourceLoader::load(fnt, "Font"); - if (font.is_valid()) { - f = font; - } - } else if (subtag_a[0] == "font_size") { - fs = subtag_a[1].to_int(); - } else if (subtag_a[0] == "margins") { - Vector<String> subtag_b = subtag_a[1].split(","); - _normalize_subtags(subtag_b); + OptionMap::Iterator font_option = bbcode_options.find("font"); + if (!font_option) { + font_option = bbcode_options.find("f"); + } + if (font_option) { + const String &fnt = font_option->value; + Ref<Font> font = ResourceLoader::load(fnt, "Font"); + if (font.is_valid()) { + f = font; + } + } + OptionMap::Iterator font_size_option = bbcode_options.find("font_size"); + if (font_size_option) { + fs = font_size_option->value.to_int(); + } + OptionMap::Iterator margins_option = bbcode_options.find("margins"); + if (margins_option) { + Vector<String> subtag_b = _split_unquoted(margins_option->value, U','); + _normalize_subtags(subtag_b); - if (subtag_b.size() == 4) { - dropcap_margins.position.x = subtag_b[0].to_float(); - dropcap_margins.position.y = subtag_b[1].to_float(); - dropcap_margins.size.x = subtag_b[2].to_float(); - dropcap_margins.size.y = subtag_b[3].to_float(); - } - } else if (subtag_a[0] == "outline_size") { - outline_size = subtag_a[1].to_int(); - } else if (subtag_a[0] == "color") { - color = Color::from_string(subtag_a[1], color); - } else if (subtag_a[0] == "outline_color") { - outline_color = Color::from_string(subtag_a[1], outline_color); - } + if (subtag_b.size() == 4) { + dropcap_margins.position.x = subtag_b[0].to_float(); + dropcap_margins.position.y = subtag_b[1].to_float(); + dropcap_margins.size.x = subtag_b[2].to_float(); + dropcap_margins.size.y = subtag_b[3].to_float(); } } - int end = p_bbcode.find("[", brk_end); + OptionMap::Iterator outline_size_option = bbcode_options.find("outline_size"); + if (outline_size_option) { + outline_size = outline_size_option->value.to_int(); + } + OptionMap::Iterator color_option = bbcode_options.find("color"); + if (color_option) { + color = Color::from_string(color_option->value, color); + } + OptionMap::Iterator outline_color_option = bbcode_options.find("outline_color"); + if (outline_color_option) { + outline_color = Color::from_string(outline_color_option->value, outline_color); + } + + int end = p_bbcode.find_char('[', brk_end); if (end == -1) { end = p_bbcode.length(); } @@ -4661,7 +4770,7 @@ void RichTextLabel::append_text(const String &p_bbcode) { } else if (tag.begins_with("img")) { int alignment = INLINE_ALIGNMENT_CENTER; if (tag.begins_with("img=")) { - Vector<String> subtag = tag.substr(4, tag.length()).split(","); + Vector<String> subtag = _split_unquoted(_get_tag_value(tag), U','); _normalize_subtags(subtag); if (subtag.size() > 1) { @@ -4692,7 +4801,7 @@ void RichTextLabel::append_text(const String &p_bbcode) { } } - int end = p_bbcode.find("[", brk_end); + int end = p_bbcode.find_char('[', brk_end); if (end == -1) { end = p_bbcode.length(); } @@ -4704,7 +4813,7 @@ void RichTextLabel::append_text(const String &p_bbcode) { Rect2 region; OptionMap::Iterator region_option = bbcode_options.find("region"); if (region_option) { - Vector<String> region_values = region_option->value.split(",", false); + Vector<String> region_values = _split_unquoted(region_option->value, U','); if (region_values.size() == 4) { region.position.x = region_values[0].to_float(); region.position.y = region_values[1].to_float(); @@ -4766,27 +4875,27 @@ void RichTextLabel::append_text(const String &p_bbcode) { pos = end; tag_stack.push_front(bbcode_name); } else if (tag.begins_with("color=")) { - String color_str = tag.substr(6, tag.length()).unquote(); + String color_str = _get_tag_value(tag).unquote(); Color color = Color::from_string(color_str, theme_cache.default_color); push_color(color); pos = brk_end + 1; tag_stack.push_front("color"); } else if (tag.begins_with("outline_color=")) { - String color_str = tag.substr(14, tag.length()).unquote(); + String color_str = _get_tag_value(tag).unquote(); Color color = Color::from_string(color_str, theme_cache.default_color); push_outline_color(color); pos = brk_end + 1; tag_stack.push_front("outline_color"); } else if (tag.begins_with("font_size=")) { - int fnt_size = tag.substr(10, tag.length()).to_int(); + int fnt_size = _get_tag_value(tag).to_int(); push_font_size(fnt_size); pos = brk_end + 1; tag_stack.push_front("font_size"); } else if (tag.begins_with("opentype_features=") || tag.begins_with("otf=")) { - int value_pos = tag.find("="); + int value_pos = tag.find_char('='); String fnt_ftr = tag.substr(value_pos + 1); Vector<String> subtag = fnt_ftr.split(","); _normalize_subtags(subtag); @@ -4830,7 +4939,7 @@ void RichTextLabel::append_text(const String &p_bbcode) { tag_stack.push_front(tag.substr(0, value_pos)); } else if (tag.begins_with("font=")) { - String fnt = tag.substr(5, tag.length()).unquote(); + String fnt = _get_tag_value(tag).unquote(); Ref<Font> fc = ResourceLoader::load(fnt, "Font"); if (fc.is_valid()) { @@ -4841,11 +4950,9 @@ void RichTextLabel::append_text(const String &p_bbcode) { tag_stack.push_front("font"); } else if (tag.begins_with("font ")) { - Vector<String> subtag = tag.substr(2, tag.length()).split(" "); - _normalize_subtags(subtag); - Ref<Font> font = theme_cache.normal_font; DefaultFont def_font = NORMAL_FONT; + int fnt_size = -1; ItemFont *font_it = _find_font(current); if (font_it) { @@ -4858,75 +4965,122 @@ void RichTextLabel::append_text(const String &p_bbcode) { Ref<FontVariation> fc; fc.instantiate(); - int fnt_size = -1; - for (int i = 1; i < subtag.size(); i++) { - Vector<String> subtag_a = subtag[i].split("=", true, 1); - _normalize_subtags(subtag_a); - - if (subtag_a.size() == 2) { - if (subtag_a[0] == "name" || subtag_a[0] == "n") { - const String &fnt = subtag_a[1]; - Ref<Font> font_data = ResourceLoader::load(fnt, "Font"); - if (font_data.is_valid()) { - font = font_data; - def_font = CUSTOM_FONT; - } - } else if (subtag_a[0] == "size" || subtag_a[0] == "s") { - fnt_size = subtag_a[1].to_int(); - } else if (subtag_a[0] == "glyph_spacing" || subtag_a[0] == "gl") { - int spacing = subtag_a[1].to_int(); - fc->set_spacing(TextServer::SPACING_GLYPH, spacing); - } else if (subtag_a[0] == "space_spacing" || subtag_a[0] == "sp") { - int spacing = subtag_a[1].to_int(); - fc->set_spacing(TextServer::SPACING_SPACE, spacing); - } else if (subtag_a[0] == "top_spacing" || subtag_a[0] == "top") { - int spacing = subtag_a[1].to_int(); - fc->set_spacing(TextServer::SPACING_TOP, spacing); - } else if (subtag_a[0] == "bottom_spacing" || subtag_a[0] == "bt") { - int spacing = subtag_a[1].to_int(); - fc->set_spacing(TextServer::SPACING_BOTTOM, spacing); - } else if (subtag_a[0] == "embolden" || subtag_a[0] == "emb") { - float emb = subtag_a[1].to_float(); - fc->set_variation_embolden(emb); - } else if (subtag_a[0] == "face_index" || subtag_a[0] == "fi") { - int fi = subtag_a[1].to_int(); - fc->set_variation_face_index(fi); - } else if (subtag_a[0] == "slant" || subtag_a[0] == "sln") { - float slant = subtag_a[1].to_float(); - fc->set_variation_transform(Transform2D(1.0, slant, 0.0, 1.0, 0.0, 0.0)); - } else if (subtag_a[0] == "opentype_variation" || subtag_a[0] == "otv") { - Dictionary variations; - if (!subtag_a[1].is_empty()) { - Vector<String> variation_tags = subtag_a[1].split(","); - for (int j = 0; j < variation_tags.size(); j++) { - Vector<String> subtag_b = variation_tags[j].split("="); - _normalize_subtags(subtag_b); - - if (subtag_b.size() == 2) { - variations[TS->name_to_tag(subtag_b[0])] = subtag_b[1].to_float(); - } - } - fc->set_variation_opentype(variations); + OptionMap::Iterator name_option = bbcode_options.find("name"); + if (!name_option) { + name_option = bbcode_options.find("n"); + } + if (name_option) { + const String &fnt = name_option->value; + Ref<Font> font_data = ResourceLoader::load(fnt, "Font"); + if (font_data.is_valid()) { + font = font_data; + def_font = CUSTOM_FONT; + } + } + OptionMap::Iterator size_option = bbcode_options.find("size"); + if (!size_option) { + size_option = bbcode_options.find("s"); + } + if (size_option) { + fnt_size = size_option->value.to_int(); + } + OptionMap::Iterator glyph_spacing_option = bbcode_options.find("glyph_spacing"); + if (!glyph_spacing_option) { + glyph_spacing_option = bbcode_options.find("gl"); + } + if (glyph_spacing_option) { + int spacing = glyph_spacing_option->value.to_int(); + fc->set_spacing(TextServer::SPACING_GLYPH, spacing); + } + OptionMap::Iterator space_spacing_option = bbcode_options.find("space_spacing"); + if (!space_spacing_option) { + space_spacing_option = bbcode_options.find("sp"); + } + if (space_spacing_option) { + int spacing = space_spacing_option->value.to_int(); + fc->set_spacing(TextServer::SPACING_SPACE, spacing); + } + OptionMap::Iterator top_spacing_option = bbcode_options.find("top_spacing"); + if (!top_spacing_option) { + top_spacing_option = bbcode_options.find("top"); + } + if (top_spacing_option) { + int spacing = top_spacing_option->value.to_int(); + fc->set_spacing(TextServer::SPACING_TOP, spacing); + } + OptionMap::Iterator bottom_spacing_option = bbcode_options.find("bottom_spacing"); + if (!bottom_spacing_option) { + bottom_spacing_option = bbcode_options.find("bt"); + } + if (bottom_spacing_option) { + int spacing = bottom_spacing_option->value.to_int(); + fc->set_spacing(TextServer::SPACING_BOTTOM, spacing); + } + OptionMap::Iterator embolden_option = bbcode_options.find("embolden"); + if (!embolden_option) { + embolden_option = bbcode_options.find("emb"); + } + if (embolden_option) { + float emb = embolden_option->value.to_float(); + fc->set_variation_embolden(emb); + } + OptionMap::Iterator face_index_option = bbcode_options.find("face_index"); + if (!face_index_option) { + face_index_option = bbcode_options.find("fi"); + } + if (face_index_option) { + int fi = face_index_option->value.to_int(); + fc->set_variation_face_index(fi); + } + OptionMap::Iterator slant_option = bbcode_options.find("slant"); + if (!slant_option) { + slant_option = bbcode_options.find("sln"); + } + if (slant_option) { + float slant = slant_option->value.to_float(); + fc->set_variation_transform(Transform2D(1.0, slant, 0.0, 1.0, 0.0, 0.0)); + } + OptionMap::Iterator opentype_variation_option = bbcode_options.find("opentype_variation"); + if (!opentype_variation_option) { + opentype_variation_option = bbcode_options.find("otv"); + } + if (opentype_variation_option) { + Dictionary variations; + if (!opentype_variation_option->value.is_empty()) { + Vector<String> variation_tags = opentype_variation_option->value.split(","); + for (int j = 0; j < variation_tags.size(); j++) { + Vector<String> subtag_b = variation_tags[j].split("="); + _normalize_subtags(subtag_b); + + if (subtag_b.size() == 2) { + variations[TS->name_to_tag(subtag_b[0])] = subtag_b[1].to_float(); } - } else if (subtag_a[0] == "opentype_features" || subtag_a[0] == "otf") { - Dictionary features; - if (!subtag_a[1].is_empty()) { - Vector<String> feature_tags = subtag_a[1].split(","); - for (int j = 0; j < feature_tags.size(); j++) { - Vector<String> subtag_b = feature_tags[j].split("="); - _normalize_subtags(subtag_b); - - if (subtag_b.size() == 2) { - features[TS->name_to_tag(subtag_b[0])] = subtag_b[1].to_float(); - } else if (subtag_b.size() == 1) { - features[TS->name_to_tag(subtag_b[0])] = 1; - } - } - fc->set_opentype_features(features); + } + fc->set_variation_opentype(variations); + } + } + OptionMap::Iterator opentype_features_option = bbcode_options.find("opentype_features"); + if (!opentype_features_option) { + opentype_features_option = bbcode_options.find("otf"); + } + if (opentype_features_option) { + Dictionary features; + if (!opentype_features_option->value.is_empty()) { + Vector<String> feature_tags = opentype_features_option->value.split(","); + for (int j = 0; j < feature_tags.size(); j++) { + Vector<String> subtag_b = feature_tags[j].split("="); + _normalize_subtags(subtag_b); + + if (subtag_b.size() == 2) { + features[TS->name_to_tag(subtag_b[0])] = subtag_b[1].to_float(); + } else if (subtag_b.size() == 1) { + features[TS->name_to_tag(subtag_b[0])] = 1; } } + fc->set_opentype_features(features); } } + fc->set_base_font(font); if (def_font != CUSTOM_FONT) { @@ -4939,7 +5093,7 @@ void RichTextLabel::append_text(const String &p_bbcode) { tag_stack.push_front("font"); } else if (tag.begins_with("outline_size=")) { - int fnt_size = tag.substr(13, tag.length()).to_int(); + int fnt_size = _get_tag_value(tag).to_int(); if (fnt_size > 0) { push_outline_size(fnt_size); } @@ -5078,7 +5232,7 @@ void RichTextLabel::append_text(const String &p_bbcode) { tag_stack.push_front("pulse"); set_process_internal(true); } else if (tag.begins_with("bgcolor=")) { - String color_str = tag.substr(8, tag.length()).unquote(); + String color_str = _get_tag_value(tag).unquote(); Color color = Color::from_string(color_str, theme_cache.default_color); push_bgcolor(color); @@ -5086,7 +5240,7 @@ void RichTextLabel::append_text(const String &p_bbcode) { tag_stack.push_front("bgcolor"); } else if (tag.begins_with("fgcolor=")) { - String color_str = tag.substr(8, tag.length()).unquote(); + String color_str = _get_tag_value(tag).unquote(); Color color = Color::from_string(color_str, theme_cache.default_color); push_fgcolor(color); @@ -5115,17 +5269,6 @@ void RichTextLabel::append_text(const String &p_bbcode) { } } } - - Vector<ItemFX *> fx_items; - for (Item *E : main->subitems) { - Item *subitem = static_cast<Item *>(E); - _fetch_item_fx_stack(subitem, fx_items); - - if (fx_items.size()) { - set_process_internal(true); - break; - } - } } void RichTextLabel::scroll_to_selection() { @@ -5726,19 +5869,89 @@ void RichTextLabel::set_text_direction(Control::TextDirection p_text_direction) if (text_direction != p_text_direction) { text_direction = p_text_direction; - main->first_invalid_line.store(0); //invalidate ALL - _validate_line_caches(); + if (!text.is_empty()) { + _apply_translation(); + } else { + main->first_invalid_line.store(0); // Invalidate all lines. + _validate_line_caches(); + } + queue_redraw(); + } +} + +Control::TextDirection RichTextLabel::get_text_direction() const { + return text_direction; +} + +void RichTextLabel::set_horizontal_alignment(HorizontalAlignment p_alignment) { + ERR_FAIL_INDEX((int)p_alignment, 4); + _stop_thread(); + + if (default_alignment != p_alignment) { + default_alignment = p_alignment; + if (!text.is_empty()) { + _apply_translation(); + } else { + main->first_invalid_line.store(0); // Invalidate all lines. + _validate_line_caches(); + } + queue_redraw(); + } +} + +HorizontalAlignment RichTextLabel::get_horizontal_alignment() const { + return default_alignment; +} + +void RichTextLabel::set_justification_flags(BitField<TextServer::JustificationFlag> p_flags) { + _stop_thread(); + + if (default_jst_flags != p_flags) { + default_jst_flags = p_flags; + if (!text.is_empty()) { + _apply_translation(); + } else { + main->first_invalid_line.store(0); // Invalidate all lines. + _validate_line_caches(); + } queue_redraw(); } } +BitField<TextServer::JustificationFlag> RichTextLabel::get_justification_flags() const { + return default_jst_flags; +} + +void RichTextLabel::set_tab_stops(const PackedFloat32Array &p_tab_stops) { + _stop_thread(); + + if (default_tab_stops != p_tab_stops) { + default_tab_stops = p_tab_stops; + if (!text.is_empty()) { + _apply_translation(); + } else { + main->first_invalid_line.store(0); // Invalidate all lines. + _validate_line_caches(); + } + queue_redraw(); + } +} + +PackedFloat32Array RichTextLabel::get_tab_stops() const { + return default_tab_stops; +} + void RichTextLabel::set_structured_text_bidi_override(TextServer::StructuredTextParser p_parser) { if (st_parser != p_parser) { _stop_thread(); st_parser = p_parser; - main->first_invalid_line.store(0); //invalidate ALL - _validate_line_caches(); + if (!text.is_empty()) { + _apply_translation(); + } else { + main->first_invalid_line.store(0); // Invalidate all lines. + _validate_line_caches(); + } queue_redraw(); } } @@ -5752,7 +5965,7 @@ void RichTextLabel::set_structured_text_bidi_override_options(Array p_args) { _stop_thread(); st_args = p_args; - main->first_invalid_line.store(0); //invalidate ALL + main->first_invalid_line.store(0); // Invalidate all lines. _validate_line_caches(); queue_redraw(); } @@ -5762,17 +5975,17 @@ Array RichTextLabel::get_structured_text_bidi_override_options() const { return st_args; } -Control::TextDirection RichTextLabel::get_text_direction() const { - return text_direction; -} - void RichTextLabel::set_language(const String &p_language) { if (language != p_language) { _stop_thread(); language = p_language; - main->first_invalid_line.store(0); //invalidate ALL - _validate_line_caches(); + if (!text.is_empty()) { + _apply_translation(); + } else { + main->first_invalid_line.store(0); // Invalidate all lines. + _validate_line_caches(); + } queue_redraw(); } } @@ -5786,7 +5999,7 @@ void RichTextLabel::set_autowrap_mode(TextServer::AutowrapMode p_mode) { _stop_thread(); autowrap_mode = p_mode; - main->first_invalid_line = 0; //invalidate ALL + main->first_invalid_line = 0; // Invalidate all lines. _validate_line_caches(); queue_redraw(); } @@ -5812,7 +6025,7 @@ void RichTextLabel::set_visible_ratio(float p_ratio) { } if (visible_chars_behavior == TextServer::VC_CHARS_BEFORE_SHAPING) { - main->first_invalid_line.store(0); // Invalidate ALL. + main->first_invalid_line.store(0); // Invalidate all lines.. _validate_line_caches(); } queue_redraw(); @@ -5940,6 +6153,13 @@ 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_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_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); + ClassDB::bind_method(D_METHOD("get_tab_stops"), &RichTextLabel::get_tab_stops); + 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); @@ -5994,7 +6214,10 @@ void RichTextLabel::_bind_methods() { ClassDB::bind_method(D_METHOD("get_text"), &RichTextLabel::get_text); - ClassDB::bind_method(D_METHOD("is_ready"), &RichTextLabel::is_ready); +#ifndef DISABLE_DEPRECATED + ClassDB::bind_method(D_METHOD("is_ready"), &RichTextLabel::is_finished); +#endif // DISABLE_DEPRECATED + ClassDB::bind_method(D_METHOD("is_finished"), &RichTextLabel::is_finished); ClassDB::bind_method(D_METHOD("set_threaded", "threaded"), &RichTextLabel::set_threaded); ClassDB::bind_method(D_METHOD("is_threaded"), &RichTextLabel::is_threaded); @@ -6057,6 +6280,10 @@ void RichTextLabel::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::BOOL, "context_menu_enabled"), "set_context_menu_enabled", "is_context_menu_enabled"); 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, "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"); + ADD_GROUP("Markup", ""); ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "custom_effects", PROPERTY_HINT_ARRAY_TYPE, MAKE_RESOURCE_TYPE_HINT("RichTextEffect"), (PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_SCRIPT_VARIABLE)), "set_effects", "get_effects"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "meta_underlined"), "set_meta_underline", "is_meta_underlined"); @@ -6159,7 +6386,7 @@ void RichTextLabel::set_visible_characters_behavior(TextServer::VisibleCharacter _stop_thread(); visible_chars_behavior = p_behavior; - main->first_invalid_line.store(0); //invalidate ALL + main->first_invalid_line.store(0); // Invalidate all lines. _validate_line_caches(); queue_redraw(); } @@ -6179,7 +6406,7 @@ void RichTextLabel::set_visible_characters(int p_visible) { } } if (visible_chars_behavior == TextServer::VC_CHARS_BEFORE_SHAPING) { - main->first_invalid_line.store(0); //invalidate ALL + main->first_invalid_line.store(0); // Invalidate all lines. _validate_line_caches(); } queue_redraw(); diff --git a/scene/gui/rich_text_label.h b/scene/gui/rich_text_label.h index 83285bd7cd..a01da02b27 100644 --- a/scene/gui/rich_text_label.h +++ b/scene/gui/rich_text_label.h @@ -482,6 +482,7 @@ private: HorizontalAlignment default_alignment = HORIZONTAL_ALIGNMENT_LEFT; BitField<TextServer::JustificationFlag> 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; ItemMeta *meta_hovering = nullptr; Variant current_meta; @@ -613,6 +614,10 @@ private: String _get_prefix(Item *p_item, const Vector<int> &p_list_index, const Vector<ItemList *> &p_list_items); + static int _find_unquoted(const String &p_src, char32_t p_chr, int p_from); + static Vector<String> _split_unquoted(const String &p_src, char32_t p_splitter); + static String _get_tag_value(const String &p_tag); + #ifndef DISABLE_DEPRECATED // Kept for compatibility from 3.x to 4.0. bool _set(const StringName &p_name, const Variant &p_value); @@ -785,7 +790,7 @@ public: void deselect(); int get_pending_paragraphs() const; - bool is_ready() const; + bool is_finished() const; bool is_updating() const; void set_threaded(bool p_threaded); @@ -808,6 +813,15 @@ public: void set_text(const String &p_bbcode); String get_text() const; + void set_horizontal_alignment(HorizontalAlignment p_alignment); + HorizontalAlignment get_horizontal_alignment() const; + + void set_justification_flags(BitField<TextServer::JustificationFlag> p_flags); + BitField<TextServer::JustificationFlag> get_justification_flags() const; + + void set_tab_stops(const PackedFloat32Array &p_tab_stops); + PackedFloat32Array get_tab_stops() const; + void set_text_direction(TextDirection p_text_direction); TextDirection get_text_direction() const; diff --git a/scene/gui/scroll_container.cpp b/scene/gui/scroll_container.cpp index d96809b67a..f1902bade4 100644 --- a/scene/gui/scroll_container.cpp +++ b/scene/gui/scroll_container.cpp @@ -35,8 +35,6 @@ #include "scene/theme/theme_db.h" Size2 ScrollContainer::get_minimum_size() const { - Size2 min_size; - // Calculated in this function, as it needs to traverse all child controls once to calculate; // and needs to be calculated before being used by update_scrollbars(). largest_child_min_size = Size2(); @@ -55,21 +53,23 @@ Size2 ScrollContainer::get_minimum_size() const { largest_child_min_size = largest_child_min_size.max(child_min_size); } + Size2 min_size; + const Size2 size = get_size(); + if (horizontal_scroll_mode == SCROLL_MODE_DISABLED) { - min_size.x = MAX(min_size.x, largest_child_min_size.x); - } - if (vertical_scroll_mode == SCROLL_MODE_DISABLED) { - min_size.y = MAX(min_size.y, largest_child_min_size.y); + min_size.x = largest_child_min_size.x; + bool v_scroll_show = vertical_scroll_mode == SCROLL_MODE_SHOW_ALWAYS || (vertical_scroll_mode == SCROLL_MODE_AUTO && largest_child_min_size.y > size.y); + if (v_scroll_show && v_scroll->get_parent() == this) { + min_size.x += v_scroll->get_minimum_size().x; + } } - bool h_scroll_show = horizontal_scroll_mode == SCROLL_MODE_SHOW_ALWAYS || (horizontal_scroll_mode == SCROLL_MODE_AUTO && largest_child_min_size.x > min_size.x); - bool v_scroll_show = vertical_scroll_mode == SCROLL_MODE_SHOW_ALWAYS || (vertical_scroll_mode == SCROLL_MODE_AUTO && largest_child_min_size.y > min_size.y); - - if (h_scroll_show && h_scroll->get_parent() == this) { - min_size.y += h_scroll->get_minimum_size().y; - } - if (v_scroll_show && v_scroll->get_parent() == this) { - min_size.x += v_scroll->get_minimum_size().x; + if (vertical_scroll_mode == SCROLL_MODE_DISABLED) { + min_size.y = largest_child_min_size.y; + bool h_scroll_show = horizontal_scroll_mode == SCROLL_MODE_SHOW_ALWAYS || (horizontal_scroll_mode == SCROLL_MODE_AUTO && largest_child_min_size.x > size.x); + if (h_scroll_show && h_scroll->get_parent() == this) { + min_size.y += h_scroll->get_minimum_size().y; + } } min_size += theme_cache.panel_style->get_minimum_size(); diff --git a/scene/gui/slider.cpp b/scene/gui/slider.cpp index f984d781d3..6098548d32 100644 --- a/scene/gui/slider.cpp +++ b/scene/gui/slider.cpp @@ -275,7 +275,7 @@ void Slider::_notification(int p_what) { double areasize = size.height - (theme_cache.center_grabber ? 0 : grabber->get_height()); int grabber_shift = theme_cache.center_grabber ? grabber->get_height() / 2 : 0; style->draw(ci, Rect2i(Point2i(size.width / 2 - widget_width / 2, 0), Size2i(widget_width, size.height))); - grabber_area->draw(ci, Rect2i(Point2i((size.width - widget_width) / 2, size.height - areasize * ratio - grabber->get_height() / 2 + grabber_shift), Size2i(widget_width, areasize * ratio + grabber->get_height() / 2 - grabber_shift))); + grabber_area->draw(ci, Rect2i(Point2i((size.width - widget_width) / 2, Math::round(size.height - areasize * ratio - grabber->get_height() / 2 + grabber_shift)), Size2i(widget_width, Math::round(areasize * ratio + grabber->get_height() / 2 - grabber_shift)))); if (ticks > 1) { int grabber_offset = (grabber->get_height() / 2 - tick->get_height() / 2); diff --git a/scene/gui/spin_box.cpp b/scene/gui/spin_box.cpp index 2c08d36e7e..01c2b9bffe 100644 --- a/scene/gui/spin_box.cpp +++ b/scene/gui/spin_box.cpp @@ -36,7 +36,7 @@ Size2 SpinBox::get_minimum_size() const { Size2 ms = line_edit->get_combined_minimum_size(); - ms.width += last_w; + ms.width += sizing_cache.buttons_block_width; return ms; } @@ -46,7 +46,7 @@ void SpinBox::_update_text(bool p_keep_line_edit) { value = TS->format_number(value); } - if (!line_edit->has_focus()) { + if (!line_edit->is_editing()) { if (!prefix.is_empty()) { value = prefix + " " + value; } @@ -128,7 +128,7 @@ void SpinBox::_range_click_timeout() { } } -void SpinBox::_release_mouse() { +void SpinBox::_release_mouse_from_drag_mode() { if (drag.enabled) { drag.enabled = false; Input::get_singleton()->set_mouse_mode(Input::MOUSE_MODE_HIDDEN); @@ -137,6 +137,14 @@ void SpinBox::_release_mouse() { } } +void SpinBox::_mouse_exited() { + if (state_cache.up_button_hovered || state_cache.down_button_hovered) { + state_cache.up_button_hovered = false; + state_cache.down_button_hovered = false; + queue_redraw(); + } +} + void SpinBox::gui_input(const Ref<InputEvent> &p_event) { ERR_FAIL_COND(p_event.is_null()); @@ -144,18 +152,36 @@ void SpinBox::gui_input(const Ref<InputEvent> &p_event) { return; } + Ref<InputEventMouse> me = p_event; Ref<InputEventMouseButton> mb = p_event; + Ref<InputEventMouseMotion> mm = p_event; double step = get_custom_arrow_step() != 0.0 ? get_custom_arrow_step() : get_step(); - if (mb.is_valid() && mb->is_pressed()) { - bool up = mb->get_position().y < (get_size().height / 2); + Vector2 mpos; + bool mouse_on_up_button = false; + bool mouse_on_down_button = false; + if (mb.is_valid() || mm.is_valid()) { + Rect2 up_button_rc = Rect2(sizing_cache.buttons_left, 0, sizing_cache.buttons_width, sizing_cache.button_up_height); + Rect2 down_button_rc = Rect2(sizing_cache.buttons_left, sizing_cache.second_button_top, sizing_cache.buttons_width, sizing_cache.button_down_height); + + mpos = me->get_position(); + + mouse_on_up_button = up_button_rc.has_point(mpos); + mouse_on_down_button = down_button_rc.has_point(mpos); + } + if (mb.is_valid() && mb->is_pressed()) { switch (mb->get_button_index()) { case MouseButton::LEFT: { line_edit->grab_focus(); - set_value(get_value() + (up ? step : -step)); + if (mouse_on_up_button || mouse_on_down_button) { + set_value(get_value() + (mouse_on_up_button ? step : -step)); + } + state_cache.up_button_pressed = mouse_on_up_button; + state_cache.down_button_pressed = mouse_on_down_button; + queue_redraw(); range_click_timer->set_wait_time(0.6); range_click_timer->set_one_shot(true); @@ -166,16 +192,18 @@ void SpinBox::gui_input(const Ref<InputEvent> &p_event) { } break; case MouseButton::RIGHT: { line_edit->grab_focus(); - set_value((up ? get_max() : get_min())); + if (mouse_on_up_button || mouse_on_down_button) { + set_value(mouse_on_up_button ? get_max() : get_min()); + } } break; case MouseButton::WHEEL_UP: { - if (line_edit->has_focus()) { + if (line_edit->is_editing()) { set_value(get_value() + step * mb->get_factor()); accept_event(); } } break; case MouseButton::WHEEL_DOWN: { - if (line_edit->has_focus()) { + if (line_edit->is_editing()) { set_value(get_value() - step * mb->get_factor()); accept_event(); } @@ -186,14 +214,30 @@ void SpinBox::gui_input(const Ref<InputEvent> &p_event) { } if (mb.is_valid() && !mb->is_pressed() && mb->get_button_index() == MouseButton::LEFT) { + if (state_cache.up_button_pressed || state_cache.down_button_pressed) { + state_cache.up_button_pressed = false; + state_cache.down_button_pressed = false; + queue_redraw(); + } + //set_default_cursor_shape(CURSOR_ARROW); range_click_timer->stop(); - _release_mouse(); + _release_mouse_from_drag_mode(); drag.allowed = false; line_edit->clear_pending_select_all_on_focus(); } - Ref<InputEventMouseMotion> mm = p_event; + if (mm.is_valid()) { + bool old_up_hovered = state_cache.up_button_hovered; + bool old_down_hovered = state_cache.down_button_hovered; + + state_cache.up_button_hovered = mouse_on_up_button; + state_cache.down_button_hovered = mouse_on_down_button; + + if (old_up_hovered != state_cache.up_button_hovered || old_down_hovered != state_cache.down_button_hovered) { + queue_redraw(); + } + } if (mm.is_valid() && (mm->get_button_mask().has_flag(MouseButtonMask::LEFT))) { if (drag.enabled) { @@ -209,71 +253,158 @@ void SpinBox::gui_input(const Ref<InputEvent> &p_event) { } } -void SpinBox::_line_edit_focus_enter() { - int col = line_edit->get_caret_column(); - _update_text(); - line_edit->set_caret_column(col); +void SpinBox::_line_edit_editing_toggled(bool p_toggled_on) { + if (p_toggled_on) { + int col = line_edit->get_caret_column(); + _update_text(); + line_edit->set_caret_column(col); - // LineEdit text might change and it clears any selection. Have to re-select here. - if (line_edit->is_select_all_on_focus() && !Input::get_singleton()->is_mouse_button_pressed(MouseButton::LEFT)) { - line_edit->select_all(); + // LineEdit text might change and it clears any selection. Have to re-select here. + if (line_edit->is_select_all_on_focus() && !Input::get_singleton()->is_mouse_button_pressed(MouseButton::LEFT)) { + line_edit->select_all(); + } + } else { + // Discontinue because the focus_exit was caused by canceling. + if (Input::get_singleton()->is_action_pressed("ui_cancel")) { + _update_text(); + return; + } + + line_edit->deselect(); + _text_submitted(line_edit->get_text()); } } -void SpinBox::_line_edit_focus_exit() { - // Discontinue because the focus_exit was caused by left-clicking the arrows. - const Viewport *viewport = get_viewport(); - if (!viewport || viewport->gui_get_focus_owner() == get_line_edit()) { - return; - } - // Discontinue because the focus_exit was caused by right-click context menu. - if (line_edit->is_menu_visible()) { - return; - } - // Discontinue because the focus_exit was caused by canceling. - if (Input::get_singleton()->is_action_pressed("ui_cancel")) { - _update_text(); - return; - } +inline void SpinBox::_compute_sizes() { + int buttons_block_wanted_width = theme_cache.buttons_width + theme_cache.field_and_buttons_separation; + int buttons_block_icon_enforced_width = _get_widest_button_icon_width() + theme_cache.field_and_buttons_separation; - _text_submitted(line_edit->get_text()); -} +#ifndef DISABLE_DEPRECATED + const bool min_width_from_icons = theme_cache.set_min_buttons_width_from_icons || (theme_cache.buttons_width < 0); +#else + const bool min_width_from_icons = theme_cache.buttons_width < 0; +#endif + int w = min_width_from_icons != 0 ? MAX(buttons_block_icon_enforced_width, buttons_block_wanted_width) : buttons_block_wanted_width; -inline void SpinBox::_adjust_width_for_icon(const Ref<Texture2D> &icon) { - int w = icon->get_width(); - if ((w != last_w)) { + if (w != sizing_cache.buttons_block_width) { line_edit->set_offset(SIDE_LEFT, 0); line_edit->set_offset(SIDE_RIGHT, -w); - last_w = w; + sizing_cache.buttons_block_width = w; } + + Size2i size = get_size(); + + sizing_cache.buttons_width = w - theme_cache.field_and_buttons_separation; + sizing_cache.buttons_vertical_separation = CLAMP(theme_cache.buttons_vertical_separation, 0, size.height); + sizing_cache.buttons_left = is_layout_rtl() ? 0 : size.width - sizing_cache.buttons_width; + sizing_cache.button_up_height = (size.height - sizing_cache.buttons_vertical_separation) / 2; + sizing_cache.button_down_height = size.height - sizing_cache.button_up_height - sizing_cache.buttons_vertical_separation; + sizing_cache.second_button_top = size.height - sizing_cache.button_down_height; + + sizing_cache.buttons_separator_top = sizing_cache.button_up_height; + sizing_cache.field_and_buttons_separator_left = is_layout_rtl() ? sizing_cache.buttons_width : size.width - sizing_cache.buttons_block_width; + sizing_cache.field_and_buttons_separator_width = theme_cache.field_and_buttons_separation; +} + +inline int SpinBox::_get_widest_button_icon_width() { + int max = 0; + max = MAX(max, theme_cache.updown_icon->get_width()); + max = MAX(max, theme_cache.up_icon->get_width()); + max = MAX(max, theme_cache.up_hover_icon->get_width()); + max = MAX(max, theme_cache.up_pressed_icon->get_width()); + max = MAX(max, theme_cache.up_disabled_icon->get_width()); + max = MAX(max, theme_cache.down_icon->get_width()); + max = MAX(max, theme_cache.down_hover_icon->get_width()); + max = MAX(max, theme_cache.down_pressed_icon->get_width()); + max = MAX(max, theme_cache.down_disabled_icon->get_width()); + return max; } void SpinBox::_notification(int p_what) { switch (p_what) { case NOTIFICATION_DRAW: { _update_text(true); - _adjust_width_for_icon(theme_cache.updown_icon); + _compute_sizes(); RID ci = get_canvas_item(); Size2i size = get_size(); - if (is_layout_rtl()) { - theme_cache.updown_icon->draw(ci, Point2i(0, (size.height - theme_cache.updown_icon->get_height()) / 2)); - } else { - theme_cache.updown_icon->draw(ci, Point2i(size.width - theme_cache.updown_icon->get_width(), (size.height - theme_cache.updown_icon->get_height()) / 2)); + Ref<StyleBox> up_stylebox = theme_cache.up_base_stylebox; + Ref<StyleBox> down_stylebox = theme_cache.down_base_stylebox; + Ref<Texture2D> up_icon = theme_cache.up_icon; + Ref<Texture2D> down_icon = theme_cache.down_icon; + Color up_icon_modulate = theme_cache.up_icon_modulate; + Color down_icon_modulate = theme_cache.down_icon_modulate; + + bool is_fully_disabled = !is_editable(); + + if (state_cache.up_button_disabled || is_fully_disabled) { + up_stylebox = theme_cache.up_disabled_stylebox; + up_icon = theme_cache.up_disabled_icon; + up_icon_modulate = theme_cache.up_disabled_icon_modulate; + } else if (state_cache.up_button_pressed && !drag.enabled) { + up_stylebox = theme_cache.up_pressed_stylebox; + up_icon = theme_cache.up_pressed_icon; + up_icon_modulate = theme_cache.up_pressed_icon_modulate; + } else if (state_cache.up_button_hovered && !drag.enabled) { + up_stylebox = theme_cache.up_hover_stylebox; + up_icon = theme_cache.up_hover_icon; + up_icon_modulate = theme_cache.up_hover_icon_modulate; } + + if (state_cache.down_button_disabled || is_fully_disabled) { + down_stylebox = theme_cache.down_disabled_stylebox; + down_icon = theme_cache.down_disabled_icon; + down_icon_modulate = theme_cache.down_disabled_icon_modulate; + } else if (state_cache.down_button_pressed && !drag.enabled) { + down_stylebox = theme_cache.down_pressed_stylebox; + down_icon = theme_cache.down_pressed_icon; + down_icon_modulate = theme_cache.down_pressed_icon_modulate; + } else if (state_cache.down_button_hovered && !drag.enabled) { + down_stylebox = theme_cache.down_hover_stylebox; + down_icon = theme_cache.down_hover_icon; + down_icon_modulate = theme_cache.down_hover_icon_modulate; + } + + int updown_icon_left = sizing_cache.buttons_left + (sizing_cache.buttons_width - theme_cache.updown_icon->get_width()) / 2; + int updown_icon_top = (size.height - theme_cache.updown_icon->get_height()) / 2; + + // Compute center icon positions once we know which one is used. + int up_icon_left = sizing_cache.buttons_left + (sizing_cache.buttons_width - up_icon->get_width()) / 2; + int up_icon_top = (sizing_cache.button_up_height - up_icon->get_height()) / 2; + int down_icon_left = sizing_cache.buttons_left + (sizing_cache.buttons_width - down_icon->get_width()) / 2; + int down_icon_top = sizing_cache.second_button_top + (sizing_cache.button_down_height - down_icon->get_height()) / 2; + + // Draw separators. + draw_style_box(theme_cache.up_down_buttons_separator, Rect2(sizing_cache.buttons_left, sizing_cache.buttons_separator_top, sizing_cache.buttons_width, sizing_cache.buttons_vertical_separation)); + draw_style_box(theme_cache.field_and_buttons_separator, Rect2(sizing_cache.field_and_buttons_separator_left, 0, sizing_cache.field_and_buttons_separator_width, size.height)); + + // Draw buttons. + draw_style_box(up_stylebox, Rect2(sizing_cache.buttons_left, 0, sizing_cache.buttons_width, sizing_cache.button_up_height)); + draw_style_box(down_stylebox, Rect2(sizing_cache.buttons_left, sizing_cache.second_button_top, sizing_cache.buttons_width, sizing_cache.button_down_height)); + + // Draw arrows. + theme_cache.updown_icon->draw(ci, Point2i(updown_icon_left, updown_icon_top)); + draw_texture(up_icon, Point2i(up_icon_left, up_icon_top), up_icon_modulate); + draw_texture(down_icon, Point2i(down_icon_left, down_icon_top), down_icon_modulate); + + } break; + + case NOTIFICATION_MOUSE_EXIT: { + _mouse_exited(); } break; case NOTIFICATION_ENTER_TREE: { - _adjust_width_for_icon(theme_cache.updown_icon); + _compute_sizes(); _update_text(); + _update_buttons_state_for_current_value(); } break; case NOTIFICATION_VISIBILITY_CHANGED: drag.allowed = false; [[fallthrough]]; case NOTIFICATION_EXIT_TREE: { - _release_mouse(); + _release_mouse_from_drag_mode(); } break; case NOTIFICATION_TRANSLATION_CHANGED: { @@ -353,6 +484,7 @@ bool SpinBox::is_select_all_on_focus() const { void SpinBox::set_editable(bool p_enabled) { line_edit->set_editable(p_enabled); + queue_redraw(); } bool SpinBox::is_editable() const { @@ -371,6 +503,22 @@ double SpinBox::get_custom_arrow_step() const { return custom_arrow_step; } +void SpinBox::_value_changed(double p_value) { + _update_buttons_state_for_current_value(); +} + +void SpinBox::_update_buttons_state_for_current_value() { + double value = get_value(); + bool should_disable_up = value == get_max() && !is_greater_allowed(); + bool should_disable_down = value == get_min() && !is_lesser_allowed(); + + if (state_cache.up_button_disabled != should_disable_up || state_cache.down_button_disabled != should_disable_down) { + state_cache.up_button_disabled = should_disable_up; + state_cache.down_button_disabled = should_disable_down; + queue_redraw(); + } +} + void SpinBox::_bind_methods() { ClassDB::bind_method(D_METHOD("set_horizontal_alignment", "alignment"), &SpinBox::set_horizontal_alignment); ClassDB::bind_method(D_METHOD("get_horizontal_alignment"), &SpinBox::get_horizontal_alignment); @@ -397,20 +545,56 @@ void SpinBox::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "custom_arrow_step", PROPERTY_HINT_RANGE, "0,10000,0.0001,or_greater"), "set_custom_arrow_step", "get_custom_arrow_step"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "select_all_on_focus"), "set_select_all_on_focus", "is_select_all_on_focus"); + BIND_THEME_ITEM(Theme::DATA_TYPE_CONSTANT, SpinBox, buttons_vertical_separation); + BIND_THEME_ITEM(Theme::DATA_TYPE_CONSTANT, SpinBox, field_and_buttons_separation); + BIND_THEME_ITEM(Theme::DATA_TYPE_CONSTANT, SpinBox, buttons_width); +#ifndef DISABLE_DEPRECATED + BIND_THEME_ITEM(Theme::DATA_TYPE_CONSTANT, SpinBox, set_min_buttons_width_from_icons); +#endif + BIND_THEME_ITEM_CUSTOM(Theme::DATA_TYPE_ICON, SpinBox, updown_icon, "updown"); + BIND_THEME_ITEM_CUSTOM(Theme::DATA_TYPE_ICON, SpinBox, up_icon, "up"); + BIND_THEME_ITEM_CUSTOM(Theme::DATA_TYPE_ICON, SpinBox, up_hover_icon, "up_hover"); + BIND_THEME_ITEM_CUSTOM(Theme::DATA_TYPE_ICON, SpinBox, up_pressed_icon, "up_pressed"); + BIND_THEME_ITEM_CUSTOM(Theme::DATA_TYPE_ICON, SpinBox, up_disabled_icon, "up_disabled"); + BIND_THEME_ITEM_CUSTOM(Theme::DATA_TYPE_ICON, SpinBox, down_icon, "down"); + BIND_THEME_ITEM_CUSTOM(Theme::DATA_TYPE_ICON, SpinBox, down_hover_icon, "down_hover"); + BIND_THEME_ITEM_CUSTOM(Theme::DATA_TYPE_ICON, SpinBox, down_pressed_icon, "down_pressed"); + BIND_THEME_ITEM_CUSTOM(Theme::DATA_TYPE_ICON, SpinBox, down_disabled_icon, "down_disabled"); + + BIND_THEME_ITEM_CUSTOM(Theme::DATA_TYPE_STYLEBOX, SpinBox, up_base_stylebox, "up_background"); + BIND_THEME_ITEM_CUSTOM(Theme::DATA_TYPE_STYLEBOX, SpinBox, up_hover_stylebox, "up_background_hovered"); + BIND_THEME_ITEM_CUSTOM(Theme::DATA_TYPE_STYLEBOX, SpinBox, up_pressed_stylebox, "up_background_pressed"); + BIND_THEME_ITEM_CUSTOM(Theme::DATA_TYPE_STYLEBOX, SpinBox, up_disabled_stylebox, "up_background_disabled"); + BIND_THEME_ITEM_CUSTOM(Theme::DATA_TYPE_STYLEBOX, SpinBox, down_base_stylebox, "down_background"); + BIND_THEME_ITEM_CUSTOM(Theme::DATA_TYPE_STYLEBOX, SpinBox, down_hover_stylebox, "down_background_hovered"); + BIND_THEME_ITEM_CUSTOM(Theme::DATA_TYPE_STYLEBOX, SpinBox, down_pressed_stylebox, "down_background_pressed"); + BIND_THEME_ITEM_CUSTOM(Theme::DATA_TYPE_STYLEBOX, SpinBox, down_disabled_stylebox, "down_background_disabled"); + + BIND_THEME_ITEM_CUSTOM(Theme::DATA_TYPE_COLOR, SpinBox, up_icon_modulate, "up_icon_modulate"); + BIND_THEME_ITEM_CUSTOM(Theme::DATA_TYPE_COLOR, SpinBox, up_hover_icon_modulate, "up_hover_icon_modulate"); + BIND_THEME_ITEM_CUSTOM(Theme::DATA_TYPE_COLOR, SpinBox, up_pressed_icon_modulate, "up_pressed_icon_modulate"); + BIND_THEME_ITEM_CUSTOM(Theme::DATA_TYPE_COLOR, SpinBox, up_disabled_icon_modulate, "up_disabled_icon_modulate"); + BIND_THEME_ITEM_CUSTOM(Theme::DATA_TYPE_COLOR, SpinBox, down_icon_modulate, "down_icon_modulate"); + BIND_THEME_ITEM_CUSTOM(Theme::DATA_TYPE_COLOR, SpinBox, down_hover_icon_modulate, "down_hover_icon_modulate"); + BIND_THEME_ITEM_CUSTOM(Theme::DATA_TYPE_COLOR, SpinBox, down_pressed_icon_modulate, "down_pressed_icon_modulate"); + BIND_THEME_ITEM_CUSTOM(Theme::DATA_TYPE_COLOR, SpinBox, down_disabled_icon_modulate, "down_disabled_icon_modulate"); + + BIND_THEME_ITEM_CUSTOM(Theme::DATA_TYPE_STYLEBOX, SpinBox, field_and_buttons_separator, "field_and_buttons_separator"); + BIND_THEME_ITEM_CUSTOM(Theme::DATA_TYPE_STYLEBOX, SpinBox, up_down_buttons_separator, "up_down_buttons_separator"); } SpinBox::SpinBox() { line_edit = memnew(LineEdit); add_child(line_edit, false, INTERNAL_MODE_FRONT); + line_edit->set_theme_type_variation("SpinBoxInnerLineEdit"); line_edit->set_anchors_and_offsets_preset(Control::PRESET_FULL_RECT); line_edit->set_mouse_filter(MOUSE_FILTER_PASS); line_edit->set_horizontal_alignment(HORIZONTAL_ALIGNMENT_LEFT); line_edit->connect("text_submitted", callable_mp(this, &SpinBox::_text_submitted), CONNECT_DEFERRED); - line_edit->connect(SceneStringName(focus_entered), callable_mp(this, &SpinBox::_line_edit_focus_enter), CONNECT_DEFERRED); - line_edit->connect(SceneStringName(focus_exited), callable_mp(this, &SpinBox::_line_edit_focus_exit), CONNECT_DEFERRED); + line_edit->connect("editing_toggled", callable_mp(this, &SpinBox::_line_edit_editing_toggled), CONNECT_DEFERRED); line_edit->connect(SceneStringName(gui_input), callable_mp(this, &SpinBox::_line_edit_input)); range_click_timer = memnew(Timer); diff --git a/scene/gui/spin_box.h b/scene/gui/spin_box.h index 4d49626d71..294dc3e5d5 100644 --- a/scene/gui/spin_box.h +++ b/scene/gui/spin_box.h @@ -39,12 +39,24 @@ class SpinBox : public Range { GDCLASS(SpinBox, Range); LineEdit *line_edit = nullptr; - int last_w = 0; bool update_on_text_changed = false; + struct SizingCache { + int buttons_block_width = 0; + int buttons_width = 0; + int buttons_vertical_separation = 0; + int buttons_left = 0; + int button_up_height = 0; + int button_down_height = 0; + int second_button_top = 0; + int buttons_separator_top = 0; + int field_and_buttons_separator_left = 0; + int field_and_buttons_separator_width = 0; + } sizing_cache; + Timer *range_click_timer = nullptr; void _range_click_timeout(); - void _release_mouse(); + void _release_mouse_from_drag_mode(); void _update_text(bool p_keep_line_edit = false); void _text_submitted(const String &p_string); @@ -65,17 +77,66 @@ class SpinBox : public Range { double diff_y = 0.0; } drag; - void _line_edit_focus_enter(); - void _line_edit_focus_exit(); + struct StateCache { + bool up_button_hovered = false; + bool up_button_pressed = false; + bool up_button_disabled = false; + bool down_button_hovered = false; + bool down_button_pressed = false; + bool down_button_disabled = false; + } state_cache; + + void _line_edit_editing_toggled(bool p_toggled_on); - inline void _adjust_width_for_icon(const Ref<Texture2D> &icon); + inline void _compute_sizes(); + inline int _get_widest_button_icon_width(); struct ThemeCache { Ref<Texture2D> updown_icon; + Ref<Texture2D> up_icon; + Ref<Texture2D> up_hover_icon; + Ref<Texture2D> up_pressed_icon; + Ref<Texture2D> up_disabled_icon; + Ref<Texture2D> down_icon; + Ref<Texture2D> down_hover_icon; + Ref<Texture2D> down_pressed_icon; + Ref<Texture2D> down_disabled_icon; + + Ref<StyleBox> up_base_stylebox; + Ref<StyleBox> up_hover_stylebox; + Ref<StyleBox> up_pressed_stylebox; + Ref<StyleBox> up_disabled_stylebox; + Ref<StyleBox> down_base_stylebox; + Ref<StyleBox> down_hover_stylebox; + Ref<StyleBox> down_pressed_stylebox; + Ref<StyleBox> down_disabled_stylebox; + + Color up_icon_modulate; + Color up_hover_icon_modulate; + Color up_pressed_icon_modulate; + Color up_disabled_icon_modulate; + Color down_icon_modulate; + Color down_hover_icon_modulate; + Color down_pressed_icon_modulate; + Color down_disabled_icon_modulate; + + Ref<StyleBox> field_and_buttons_separator; + Ref<StyleBox> up_down_buttons_separator; + + int buttons_vertical_separation = 0; + int field_and_buttons_separation = 0; + int buttons_width = 0; +#ifndef DISABLE_DEPRECATED + bool set_min_buttons_width_from_icons = false; +#endif } theme_cache; + void _mouse_exited(); + void _update_buttons_state_for_current_value(); + protected: virtual void gui_input(const Ref<InputEvent> &p_event) override; + void _value_changed(double p_value) override; void _notification(int p_what); static void _bind_methods(); diff --git a/scene/gui/split_container.cpp b/scene/gui/split_container.cpp index 8ab9b4c1cd..be03db526d 100644 --- a/scene/gui/split_container.cpp +++ b/scene/gui/split_container.cpp @@ -32,6 +32,7 @@ #include "scene/gui/label.h" #include "scene/gui/margin_container.h" +#include "scene/main/window.h" #include "scene/theme/theme_db.h" void SplitContainerDragger::gui_input(const Ref<InputEvent> &p_event) { @@ -39,7 +40,7 @@ void SplitContainerDragger::gui_input(const Ref<InputEvent> &p_event) { SplitContainer *sc = Object::cast_to<SplitContainer>(get_parent()); - if (sc->collapsed || !sc->_get_sortable_child(0) || !sc->_get_sortable_child(1) || sc->dragger_visibility != SplitContainer::DRAGGER_VISIBLE) { + if (sc->collapsed || !sc->_get_sortable_child(0) || !sc->_get_sortable_child(1) || !sc->dragging_enabled) { return; } @@ -48,8 +49,9 @@ void SplitContainerDragger::gui_input(const Ref<InputEvent> &p_event) { if (mb.is_valid()) { if (mb->get_button_index() == MouseButton::LEFT) { if (mb->is_pressed()) { - sc->_compute_middle_sep(true); + sc->_compute_split_offset(true); dragging = true; + sc->emit_signal(SNAME("drag_started")); drag_ofs = sc->split_offset; if (sc->vertical) { drag_from = get_transform().xform(mb->get_position()).y; @@ -59,6 +61,7 @@ void SplitContainerDragger::gui_input(const Ref<InputEvent> &p_event) { } else { dragging = false; queue_redraw(); + sc->emit_signal(SNAME("drag_ended")); } } } @@ -76,7 +79,7 @@ void SplitContainerDragger::gui_input(const Ref<InputEvent> &p_event) { } else { sc->split_offset = drag_ofs + ((sc->vertical ? in_parent_pos.y : in_parent_pos.x) - drag_from); } - sc->_compute_middle_sep(true); + sc->_compute_split_offset(true); sc->queue_sort(); sc->emit_signal(SNAME("dragged"), sc->get_split_offset()); } @@ -84,11 +87,9 @@ void SplitContainerDragger::gui_input(const Ref<InputEvent> &p_event) { Control::CursorShape SplitContainerDragger::get_cursor_shape(const Point2 &p_pos) const { SplitContainer *sc = Object::cast_to<SplitContainer>(get_parent()); - - if (!sc->collapsed && sc->dragger_visibility == SplitContainer::DRAGGER_VISIBLE) { + if (!sc->collapsed && sc->dragging_enabled) { return (sc->vertical ? CURSOR_VSPLIT : CURSOR_HSPLIT); } - return Control::get_cursor_shape(p_pos); } @@ -101,7 +102,6 @@ void SplitContainerDragger::_notification(int p_what) { queue_redraw(); } } break; - case NOTIFICATION_MOUSE_EXIT: { mouse_inside = false; SplitContainer *sc = Object::cast_to<SplitContainer>(get_parent()); @@ -109,22 +109,25 @@ void SplitContainerDragger::_notification(int p_what) { queue_redraw(); } } break; - case NOTIFICATION_DRAW: { SplitContainer *sc = Object::cast_to<SplitContainer>(get_parent()); - if (!dragging && !mouse_inside && sc->theme_cache.autohide) { - return; + draw_style_box(sc->theme_cache.split_bar_background, split_bar_rect); + if (sc->dragger_visibility == sc->DRAGGER_VISIBLE && (dragging || mouse_inside || !sc->theme_cache.autohide)) { + Ref<Texture2D> tex = sc->_get_grabber_icon(); + float available_size = sc->vertical ? (sc->get_size().x - tex->get_size().x) : (sc->get_size().y - tex->get_size().y); + if (available_size - sc->drag_area_margin_begin - sc->drag_area_margin_end > 0) { // Draw the grabber only if it fits. + draw_texture(tex, (split_bar_rect.get_position() + (split_bar_rect.get_size() - tex->get_size()) * 0.5)); + } + } + if (sc->show_drag_area && Engine::get_singleton()->is_editor_hint()) { + draw_rect(Rect2(Vector2(0, 0), get_size()), sc->dragging_enabled ? Color(1, 1, 0, 0.3) : Color(1, 0, 0, 0.3)); } - - Ref<Texture2D> tex = sc->_get_grabber_icon(); - draw_texture(tex, (get_size() - tex->get_size()) / 2); } break; } } Control *SplitContainer::_get_sortable_child(int p_idx, SortableVisbilityMode p_visibility_mode) const { int idx = 0; - for (int i = 0; i < get_child_count(false); i++) { Control *c = as_sortable_control(get_child(i, false), p_visibility_mode); if (!c) { @@ -137,7 +140,6 @@ Control *SplitContainer::_get_sortable_child(int p_idx, SortableVisbilityMode p_ idx++; } - return nullptr; } @@ -153,45 +155,48 @@ Ref<Texture2D> SplitContainer::_get_grabber_icon() const { } } -void SplitContainer::_compute_middle_sep(bool p_clamp) { +int SplitContainer::_get_separation() const { + if (dragger_visibility == DRAGGER_HIDDEN_COLLAPSED) { + return 0; + } + // DRAGGER_VISIBLE or DRAGGER_HIDDEN. + Ref<Texture2D> g = _get_grabber_icon(); + return MAX(theme_cache.separation, vertical ? g->get_height() : g->get_width()); +} + +void SplitContainer::_compute_split_offset(bool p_clamp) { Control *first = _get_sortable_child(0); Control *second = _get_sortable_child(1); + int axis_index = vertical ? 1 : 0; + int size = get_size()[axis_index]; + int sep = _get_separation(); - // Determine expanded children. - bool first_expanded = (vertical ? first->get_v_size_flags() : first->get_h_size_flags()) & SIZE_EXPAND; - bool second_expanded = (vertical ? second->get_v_size_flags() : second->get_h_size_flags()) & SIZE_EXPAND; - - // Compute the minimum size. - int axis = vertical ? 1 : 0; - int size = get_size()[axis]; - int ms_first = first->get_combined_minimum_size()[axis]; - int ms_second = second->get_combined_minimum_size()[axis]; - - // Determine the separation between items. - Ref<Texture2D> g = _get_grabber_icon(); - int sep = (dragger_visibility != DRAGGER_HIDDEN_COLLAPSED) ? MAX(theme_cache.separation, vertical ? g->get_height() : g->get_width()) : 0; - - // Compute the wished separation_point. - int wished_middle_sep = 0; + // Compute the wished size. + int wished_size = 0; int split_offset_with_collapse = 0; if (!collapsed) { split_offset_with_collapse = split_offset; } - if (first_expanded && second_expanded) { + bool first_is_expanded = (vertical ? first->get_v_size_flags() : first->get_h_size_flags()) & SIZE_EXPAND; + bool second_is_expanded = (vertical ? second->get_v_size_flags() : second->get_h_size_flags()) & SIZE_EXPAND; + + if (first_is_expanded && second_is_expanded) { float ratio = first->get_stretch_ratio() / (first->get_stretch_ratio() + second->get_stretch_ratio()); - wished_middle_sep = size * ratio - sep / 2 + split_offset_with_collapse; - } else if (first_expanded) { - wished_middle_sep = size - sep + split_offset_with_collapse; + wished_size = size * ratio - sep * 0.5 + split_offset_with_collapse; + } else if (first_is_expanded) { + wished_size = size - sep + split_offset_with_collapse; } else { - wished_middle_sep = split_offset_with_collapse; + wished_size = split_offset_with_collapse; } - // Clamp the middle sep to acceptatble values. - middle_sep = CLAMP(wished_middle_sep, ms_first, size - sep - ms_second); + // Clamp the split offset to acceptable values. + int first_min_size = first->get_combined_minimum_size()[axis_index]; + int second_min_size = second->get_combined_minimum_size()[axis_index]; + computed_split_offset = CLAMP(wished_size, first_min_size, size - sep - second_min_size); // Clamp the split_offset if requested. if (p_clamp) { - split_offset -= wished_middle_sep - middle_sep; + split_offset -= wished_size - computed_split_offset; } } @@ -199,8 +204,7 @@ void SplitContainer::_resort() { Control *first = _get_sortable_child(0); Control *second = _get_sortable_child(1); - // If we have only one element. - if (!first || !second) { + if (!first || !second) { // Only one child. if (first) { fit_child_in_rect(first, Rect2(Point2(), get_size())); } else if (second) { @@ -209,53 +213,50 @@ void SplitContainer::_resort() { dragging_area_control->hide(); return; } + dragging_area_control->set_visible(!collapsed); - // If we have more that one. - _compute_middle_sep(false); + _compute_split_offset(false); // This recalculates and sets computed_split_offset. - // Determine the separation between items. - Ref<Texture2D> g = _get_grabber_icon(); - int sep = (dragger_visibility != DRAGGER_HIDDEN_COLLAPSED) ? MAX(theme_cache.separation, vertical ? g->get_height() : g->get_width()) : 0; + int sep = _get_separation(); + bool is_rtl = is_layout_rtl(); - // Move the children, including the dragger. + // Move the children. if (vertical) { - fit_child_in_rect(first, Rect2(Point2(0, 0), Size2(get_size().width, middle_sep))); - int sofs = middle_sep + sep; + fit_child_in_rect(first, Rect2(Point2(0, 0), Size2(get_size().width, computed_split_offset))); + int sofs = computed_split_offset + sep; fit_child_in_rect(second, Rect2(Point2(0, sofs), Size2(get_size().width, get_size().height - sofs))); } else { - if (is_layout_rtl()) { - middle_sep = get_size().width - middle_sep - sep; - fit_child_in_rect(second, Rect2(Point2(0, 0), Size2(middle_sep, get_size().height))); - int sofs = middle_sep + sep; + if (is_rtl) { + computed_split_offset = get_size().width - computed_split_offset - sep; + fit_child_in_rect(second, Rect2(Point2(0, 0), Size2(computed_split_offset, get_size().height))); + int sofs = computed_split_offset + sep; fit_child_in_rect(first, Rect2(Point2(sofs, 0), Size2(get_size().width - sofs, get_size().height))); } else { - fit_child_in_rect(first, Rect2(Point2(0, 0), Size2(middle_sep, get_size().height))); - int sofs = middle_sep + sep; + fit_child_in_rect(first, Rect2(Point2(0, 0), Size2(computed_split_offset, get_size().height))); + int sofs = computed_split_offset + sep; fit_child_in_rect(second, Rect2(Point2(sofs, 0), Size2(get_size().width - sofs, get_size().height))); } } - // Handle the dragger visibility and position. - if (dragger_visibility == DRAGGER_VISIBLE && !collapsed) { - dragging_area_control->show(); - - int dragger_ctrl_size = MAX(sep, theme_cache.minimum_grab_thickness); - if (vertical) { - dragging_area_control->set_rect(Rect2(Point2(0, middle_sep - (dragger_ctrl_size - sep) / 2), Size2(get_size().width, dragger_ctrl_size))); - } else { - dragging_area_control->set_rect(Rect2(Point2(middle_sep - (dragger_ctrl_size - sep) / 2, 0), Size2(dragger_ctrl_size, get_size().height))); - } - - dragging_area_control->queue_redraw(); + dragging_area_control->set_mouse_filter(dragging_enabled ? MOUSE_FILTER_STOP : MOUSE_FILTER_IGNORE); + const int dragger_ctrl_size = MAX(sep, theme_cache.minimum_grab_thickness); + float split_bar_offset = (dragger_ctrl_size - sep) * 0.5; + if (vertical) { + Rect2 split_bar_rect = Rect2(is_rtl ? drag_area_margin_end : drag_area_margin_begin, computed_split_offset, get_size().width - drag_area_margin_begin - drag_area_margin_end, sep); + dragging_area_control->set_rect(Rect2(split_bar_rect.position.x, split_bar_rect.position.y - split_bar_offset + drag_area_offset, split_bar_rect.size.x, dragger_ctrl_size)); + dragging_area_control->split_bar_rect = Rect2(Vector2(0.0, int(split_bar_offset) - drag_area_offset), split_bar_rect.size); } else { - dragging_area_control->hide(); + Rect2 split_bar_rect = Rect2(computed_split_offset, drag_area_margin_begin, sep, get_size().height - drag_area_margin_begin - drag_area_margin_end); + dragging_area_control->set_rect(Rect2(split_bar_rect.position.x - split_bar_offset + drag_area_offset * (is_rtl ? -1 : 1), split_bar_rect.position.y, dragger_ctrl_size, split_bar_rect.size.y)); + dragging_area_control->split_bar_rect = Rect2(Vector2(int(split_bar_offset) - drag_area_offset * (is_rtl ? -1 : 1), 0.0), split_bar_rect.size); } + queue_redraw(); + dragging_area_control->queue_redraw(); } Size2 SplitContainer::get_minimum_size() const { Size2i minimum; - Ref<Texture2D> g = _get_grabber_icon(); - int sep = (dragger_visibility != DRAGGER_HIDDEN_COLLAPSED) ? MAX(theme_cache.separation, vertical ? g->get_height() : g->get_width()) : 0; + int sep = _get_separation(); for (int i = 0; i < 2; i++) { Control *child = _get_sortable_child(i, SortableVisbilityMode::VISIBLE); @@ -297,11 +298,9 @@ void SplitContainer::_notification(int p_what) { case NOTIFICATION_LAYOUT_DIRECTION_CHANGED: { queue_sort(); } break; - case NOTIFICATION_SORT_CHILDREN: { _resort(); } break; - case NOTIFICATION_THEME_CHANGED: { update_minimum_size(); } break; @@ -312,9 +311,7 @@ void SplitContainer::set_split_offset(int p_offset) { if (split_offset == p_offset) { return; } - split_offset = p_offset; - queue_sort(); } @@ -326,8 +323,7 @@ void SplitContainer::clamp_split_offset() { if (!_get_sortable_child(0) || !_get_sortable_child(1)) { return; } - - _compute_middle_sep(true); + _compute_split_offset(true); queue_sort(); } @@ -335,7 +331,6 @@ void SplitContainer::set_collapsed(bool p_collapsed) { if (collapsed == p_collapsed) { return; } - collapsed = p_collapsed; queue_sort(); } @@ -344,7 +339,6 @@ void SplitContainer::set_dragger_visibility(DraggerVisibility p_visibility) { if (dragger_visibility == p_visibility) { return; } - dragger_visibility = p_visibility; queue_sort(); } @@ -368,6 +362,26 @@ bool SplitContainer::is_vertical() const { return vertical; } +void SplitContainer::set_dragging_enabled(bool p_enabled) { + if (dragging_enabled == p_enabled) { + return; + } + dragging_enabled = p_enabled; + if (!dragging_enabled && dragging_area_control->dragging) { + dragging_area_control->dragging = false; + // queue_redraw() is called by _resort(). + emit_signal(SNAME("drag_ended")); + } + if (get_viewport()) { + get_viewport()->update_mouse_cursor_state(); + } + _resort(); +} + +bool SplitContainer::is_dragging_enabled() const { + return dragging_enabled; +} + Vector<int> SplitContainer::get_allowed_size_flags_horizontal() const { Vector<int> flags; flags.append(SIZE_FILL); @@ -392,6 +406,51 @@ Vector<int> SplitContainer::get_allowed_size_flags_vertical() const { return flags; } +void SplitContainer::set_drag_area_margin_begin(int p_margin) { + if (drag_area_margin_begin == p_margin) { + return; + } + drag_area_margin_begin = p_margin; + queue_sort(); +} + +int SplitContainer::get_drag_area_margin_begin() const { + return drag_area_margin_begin; +} + +void SplitContainer::set_drag_area_margin_end(int p_margin) { + if (drag_area_margin_end == p_margin) { + return; + } + drag_area_margin_end = p_margin; + queue_sort(); +} + +int SplitContainer::get_drag_area_margin_end() const { + return drag_area_margin_end; +} + +void SplitContainer::set_drag_area_offset(int p_offset) { + if (drag_area_offset == p_offset) { + return; + } + drag_area_offset = p_offset; + queue_sort(); +} + +int SplitContainer::get_drag_area_offset() const { + return drag_area_offset; +} + +void SplitContainer::set_show_drag_area_enabled(bool p_enabled) { + show_drag_area = p_enabled; + dragging_area_control->queue_redraw(); +} + +bool SplitContainer::is_show_drag_area_enabled() const { + return show_drag_area; +} + void SplitContainer::_bind_methods() { ClassDB::bind_method(D_METHOD("set_split_offset", "offset"), &SplitContainer::set_split_offset); ClassDB::bind_method(D_METHOD("get_split_offset"), &SplitContainer::get_split_offset); @@ -406,13 +465,39 @@ void SplitContainer::_bind_methods() { ClassDB::bind_method(D_METHOD("set_vertical", "vertical"), &SplitContainer::set_vertical); ClassDB::bind_method(D_METHOD("is_vertical"), &SplitContainer::is_vertical); + ClassDB::bind_method(D_METHOD("set_dragging_enabled", "dragging_enabled"), &SplitContainer::set_dragging_enabled); + ClassDB::bind_method(D_METHOD("is_dragging_enabled"), &SplitContainer::is_dragging_enabled); + + ClassDB::bind_method(D_METHOD("set_drag_area_margin_begin", "margin"), &SplitContainer::set_drag_area_margin_begin); + ClassDB::bind_method(D_METHOD("get_drag_area_margin_begin"), &SplitContainer::get_drag_area_margin_begin); + + ClassDB::bind_method(D_METHOD("set_drag_area_margin_end", "margin"), &SplitContainer::set_drag_area_margin_end); + ClassDB::bind_method(D_METHOD("get_drag_area_margin_end"), &SplitContainer::get_drag_area_margin_end); + + ClassDB::bind_method(D_METHOD("set_drag_area_offset", "offset"), &SplitContainer::set_drag_area_offset); + ClassDB::bind_method(D_METHOD("get_drag_area_offset"), &SplitContainer::get_drag_area_offset); + + ClassDB::bind_method(D_METHOD("set_drag_area_highlight_in_editor", "drag_area_highlight_in_editor"), &SplitContainer::set_show_drag_area_enabled); + ClassDB::bind_method(D_METHOD("is_drag_area_highlight_in_editor_enabled"), &SplitContainer::is_show_drag_area_enabled); + + ClassDB::bind_method(D_METHOD("get_drag_area_control"), &SplitContainer::get_drag_area_control); + ADD_SIGNAL(MethodInfo("dragged", PropertyInfo(Variant::INT, "offset"))); + ADD_SIGNAL(MethodInfo("drag_started")); + ADD_SIGNAL(MethodInfo("drag_ended")); ADD_PROPERTY(PropertyInfo(Variant::INT, "split_offset", PROPERTY_HINT_NONE, "suffix:px"), "set_split_offset", "get_split_offset"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "collapsed"), "set_collapsed", "is_collapsed"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "dragging_enabled"), "set_dragging_enabled", "is_dragging_enabled"); ADD_PROPERTY(PropertyInfo(Variant::INT, "dragger_visibility", PROPERTY_HINT_ENUM, "Visible,Hidden,Hidden and Collapsed"), "set_dragger_visibility", "get_dragger_visibility"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "vertical"), "set_vertical", "is_vertical"); + ADD_GROUP("Drag Area", "drag_area_"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "drag_area_margin_begin", PROPERTY_HINT_NONE, "suffix:px"), "set_drag_area_margin_begin", "get_drag_area_margin_begin"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "drag_area_margin_end", PROPERTY_HINT_NONE, "suffix:px"), "set_drag_area_margin_end", "get_drag_area_margin_end"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "drag_area_offset", PROPERTY_HINT_NONE, "suffix:px"), "set_drag_area_offset", "get_drag_area_offset"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "drag_area_highlight_in_editor"), "set_drag_area_highlight_in_editor", "is_drag_area_highlight_in_editor_enabled"); + BIND_ENUM_CONSTANT(DRAGGER_VISIBLE); BIND_ENUM_CONSTANT(DRAGGER_HIDDEN); BIND_ENUM_CONSTANT(DRAGGER_HIDDEN_COLLAPSED); @@ -423,6 +508,7 @@ void SplitContainer::_bind_methods() { BIND_THEME_ITEM_CUSTOM(Theme::DATA_TYPE_ICON, SplitContainer, grabber_icon, "grabber"); BIND_THEME_ITEM_CUSTOM(Theme::DATA_TYPE_ICON, SplitContainer, grabber_icon_h, "h_grabber"); BIND_THEME_ITEM_CUSTOM(Theme::DATA_TYPE_ICON, SplitContainer, grabber_icon_v, "v_grabber"); + BIND_THEME_ITEM_CUSTOM(Theme::DATA_TYPE_STYLEBOX, SplitContainer, split_bar_background, "split_bar_background"); } SplitContainer::SplitContainer(bool p_vertical) { diff --git a/scene/gui/split_container.h b/scene/gui/split_container.h index db870554c2..2bba96b4b8 100644 --- a/scene/gui/split_container.h +++ b/scene/gui/split_container.h @@ -35,6 +35,8 @@ class SplitContainerDragger : public Control { GDCLASS(SplitContainerDragger, Control); + friend class SplitContainer; + Rect2 split_bar_rect; protected: void _notification(int p_what); @@ -62,11 +64,16 @@ public: }; private: + int show_drag_area = false; + int drag_area_margin_begin = 0; + int drag_area_margin_end = 0; + int drag_area_offset = 0; int split_offset = 0; - int middle_sep = 0; + int computed_split_offset = 0; bool vertical = false; bool collapsed = false; DraggerVisibility dragger_visibility = DRAGGER_VISIBLE; + bool dragging_enabled = true; SplitContainerDragger *dragging_area_control = nullptr; @@ -77,10 +84,13 @@ private: Ref<Texture2D> grabber_icon; Ref<Texture2D> grabber_icon_h; Ref<Texture2D> grabber_icon_v; + float base_scale = 1.0; + Ref<StyleBox> split_bar_background; } theme_cache; Ref<Texture2D> _get_grabber_icon() const; - void _compute_middle_sep(bool p_clamp); + void _compute_split_offset(bool p_clamp); + int _get_separation() const; void _resort(); Control *_get_sortable_child(int p_idx, SortableVisbilityMode p_visibility_mode = SortableVisbilityMode::VISIBLE_IN_TREE) const; @@ -105,11 +115,28 @@ public: void set_vertical(bool p_vertical); bool is_vertical() const; + void set_dragging_enabled(bool p_enabled); + bool is_dragging_enabled() const; + virtual Size2 get_minimum_size() const override; virtual Vector<int> get_allowed_size_flags_horizontal() const override; virtual Vector<int> get_allowed_size_flags_vertical() const override; + void set_drag_area_margin_begin(int p_margin); + int get_drag_area_margin_begin() const; + + void set_drag_area_margin_end(int p_margin); + int get_drag_area_margin_end() const; + + void set_drag_area_offset(int p_offset); + int get_drag_area_offset() const; + + void set_show_drag_area_enabled(bool p_enabled); + bool is_show_drag_area_enabled() const; + + Control *get_drag_area_control() { return dragging_area_control; } + SplitContainer(bool p_vertical = false); }; diff --git a/scene/gui/subviewport_container.cpp b/scene/gui/subviewport_container.cpp index c715aceb0b..a443ae9abf 100644 --- a/scene/gui/subviewport_container.cpp +++ b/scene/gui/subviewport_container.cpp @@ -259,7 +259,7 @@ void SubViewportContainer::remove_child_notify(Node *p_child) { } PackedStringArray SubViewportContainer::get_configuration_warnings() const { - PackedStringArray warnings = Node::get_configuration_warnings(); + PackedStringArray warnings = Container::get_configuration_warnings(); bool has_viewport = false; for (int i = 0; i < get_child_count(); i++) { diff --git a/scene/gui/tab_bar.cpp b/scene/gui/tab_bar.cpp index 1ae18f5728..3e0d6adf42 100644 --- a/scene/gui/tab_bar.cpp +++ b/scene/gui/tab_bar.cpp @@ -620,6 +620,8 @@ void TabBar::_draw_tab(Ref<StyleBox> &p_tab_style, Color &p_font_color, int p_in } cb->draw(ci, Point2i(cb_rect.position.x + style->get_margin(SIDE_LEFT), cb_rect.position.y + style->get_margin(SIDE_TOP))); + } else { + tabs.write[p_index].cb_rect = Rect2(); } } diff --git a/scene/gui/text_edit.cpp b/scene/gui/text_edit.cpp index 3f1b9fc981..d7799588ea 100644 --- a/scene/gui/text_edit.cpp +++ b/scene/gui/text_edit.cpp @@ -38,7 +38,7 @@ #include "core/os/keyboard.h" #include "core/os/os.h" #include "core/string/string_builder.h" -#include "core/string/translation.h" +#include "core/string/translation_server.h" #include "scene/gui/label.h" #include "scene/main/window.h" #include "scene/theme/theme_db.h" @@ -112,8 +112,34 @@ int TextEdit::Text::get_line_width(int p_line, int p_wrap_index) const { return text[p_line].data_buf->get_size().x; } +int TextEdit::Text::get_max_width() const { + if (max_line_width_dirty) { + int new_max_line_width = 0; + for (const Line &l : text) { + if (l.hidden) { + continue; + } + new_max_line_width = MAX(new_max_line_width, l.width); + } + max_line_width = new_max_line_width; + } + + return max_line_width; +} + int TextEdit::Text::get_line_height() const { - return line_height; + if (max_line_height_dirty) { + int new_max_line_height = 0; + for (const Line &l : text) { + if (l.hidden) { + continue; + } + new_max_line_height = MAX(new_max_line_height, l.height); + } + max_line_height = new_max_line_height; + } + + return max_line_height; } void TextEdit::Text::set_width(float p_width) { @@ -135,15 +161,17 @@ BitField<TextServer::LineBreakFlag> TextEdit::Text::get_brk_flags() const { int TextEdit::Text::get_line_wrap_amount(int p_line) const { ERR_FAIL_INDEX_V(p_line, text.size(), 0); - return text[p_line].data_buf->get_line_count() - 1; + return text[p_line].line_count - 1; } Vector<Vector2i> TextEdit::Text::get_line_wrap_ranges(int p_line) const { Vector<Vector2i> ret; ERR_FAIL_INDEX_V(p_line, text.size(), ret); - for (int i = 0; i < text[p_line].data_buf->get_line_count(); i++) { - ret.push_back(text[p_line].data_buf->get_line_range(i)); + Ref<TextParagraph> data_buf = text[p_line].data_buf; + int line_count = data_buf->get_line_count(); + for (int i = 0; i < line_count; i++) { + ret.push_back(data_buf->get_line_range(i)); } return ret; } @@ -153,40 +181,11 @@ const Ref<TextParagraph> TextEdit::Text::get_line_data(int p_line) const { return text[p_line].data_buf; } -_FORCE_INLINE_ const String &TextEdit::Text::operator[](int p_line) const { +_FORCE_INLINE_ String TextEdit::Text::operator[](int p_line) const { + ERR_FAIL_INDEX_V(p_line, text.size(), ""); return text[p_line].data; } -void TextEdit::Text::_calculate_line_height() { - int height = 0; - for (const Line &l : text) { - // Found another line with the same height...nothing to update. - if (l.height == line_height) { - height = line_height; - break; - } - height = MAX(height, l.height); - } - line_height = height; -} - -void TextEdit::Text::_calculate_max_line_width() { - int line_width = 0; - for (const Line &l : text) { - if (l.hidden) { - continue; - } - - // Found another line with the same width...nothing to update. - if (l.width == max_width) { - line_width = max_width; - break; - } - line_width = MAX(line_width, l.width); - } - max_width = line_width; -} - 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()); @@ -194,8 +193,9 @@ void TextEdit::Text::invalidate_cache(int p_line, int p_column, bool p_text_chan return; // Not in tree? } + Line &text_line = text.write[p_line]; if (p_text_changed) { - text.write[p_line].data_buf->clear(); + text_line.data_buf->clear(); } BitField<TextServer::LineBreakFlag> flags = brk_flags; @@ -203,30 +203,30 @@ void TextEdit::Text::invalidate_cache(int p_line, int p_column, bool p_text_chan flags.set_flag(TextServer::BREAK_TRIM_INDENT); } - 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_break_flags(flags); - text.write[p_line].data_buf->set_preserve_control(draw_control_chars); - text.write[p_line].data_buf->set_custom_punctuation(get_enabled_word_separators()); + text_line.data_buf->set_width(width); + text_line.data_buf->set_direction((TextServer::Direction)direction); + text_line.data_buf->set_break_flags(flags); + text_line.data_buf->set_preserve_control(draw_control_chars); + text_line.data_buf->set_custom_punctuation(get_enabled_word_separators()); if (p_ime_text.length() > 0) { if (p_text_changed) { - text.write[p_line].data_buf->add_string(p_ime_text, font, font_size, language); + text_line.data_buf->add_string(p_ime_text, font, font_size, language); } if (!p_bidi_override.is_empty()) { - TS->shaped_text_set_bidi_override(text.write[p_line].data_buf->get_rid(), p_bidi_override); + TS->shaped_text_set_bidi_override(text_line.data_buf->get_rid(), p_bidi_override); } } else { if (p_text_changed) { - text.write[p_line].data_buf->add_string(text[p_line].data, font, font_size, language); + text_line.data_buf->add_string(text_line.data, font, font_size, 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 (!text_line.bidi_override.is_empty()) { + TS->shaped_text_set_bidi_override(text_line.data_buf->get_rid(), text_line.bidi_override); } } if (!p_text_changed) { - RID r = text.write[p_line].data_buf->get_rid(); + RID r = text_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, font->get_opentype_features()); @@ -237,61 +237,58 @@ void TextEdit::Text::invalidate_cache(int p_line, int p_column, bool p_text_chan if (tab_size > 0) { Vector<float> tabs; tabs.push_back(font->get_char_size(' ', font_size).width * tab_size); - text.write[p_line].data_buf->tab_align(tabs); + text_line.data_buf->tab_align(tabs); + } + + // Update wrap amount. + const int old_line_count = text_line.line_count; + text_line.line_count = text_line.data_buf->get_line_count(); + if (!text_line.hidden && text_line.line_count != old_line_count) { + total_visible_line_count += text_line.line_count - old_line_count; } // Update height. - const int old_height = text.write[p_line].height; - const int wrap_amount = get_line_wrap_amount(p_line); - int height = font_height; - for (int i = 0; i <= wrap_amount; i++) { - height = MAX(height, text[p_line].data_buf->get_line_size(i).y); + const int old_height = text_line.height; + text_line.height = font_height; + for (int i = 0; i < text_line.line_count; i++) { + text_line.height = MAX(text_line.height, text_line.data_buf->get_line_size(i).y); } - text.write[p_line].height = height; - // If this line has shrunk, this may no longer the tallest line. - if (old_height == line_height && height < line_height) { - _calculate_line_height(); - } else { - line_height = MAX(height, line_height); + // If this line has shrunk, this may no longer be the tallest line. + if (!text_line.hidden) { + if (old_height == max_line_height && text_line.height < old_height) { + max_line_height_dirty = true; + } else { + max_line_height = MAX(text_line.height, max_line_height); + } } // Update width. - const int old_width = text.write[p_line].width; - int line_width = get_line_width(p_line); - text.write[p_line].width = line_width; + const int old_width = text_line.width; + text_line.width = get_line_width(p_line); - // If this line has shrunk, this may no longer the longest line. - if (old_width == max_width && line_width < max_width) { - _calculate_max_line_width(); - } else if (!is_hidden(p_line)) { - max_width = MAX(line_width, max_width); + if (!text_line.hidden) { + // If this line has shrunk, this may no longer be the longest line. + if (old_width == max_line_width && text_line.width < old_width) { + max_line_width_dirty = true; + } else { + max_line_width = MAX(text_line.width, max_line_width); + } } } void TextEdit::Text::invalidate_all_lines() { for (int i = 0; i < text.size(); i++) { - BitField<TextServer::LineBreakFlag> flags = brk_flags; - if (indent_wrapped_lines) { - flags.set_flag(TextServer::BREAK_TRIM_INDENT); - } - text.write[i].data_buf->set_width(width); - text.write[i].data_buf->set_break_flags(flags); - text.write[i].data_buf->set_custom_punctuation(get_enabled_word_separators()); - if (tab_size_dirty) { if (tab_size > 0) { Vector<float> tabs; tabs.push_back(font->get_char_size(' ', font_size).width * tab_size); - text.write[i].data_buf->tab_align(tabs); + text[i].data_buf->tab_align(tabs); } } - text.write[i].width = get_line_width(i); + invalidate_cache(i, -1, false); } tab_size_dirty = false; - - max_width = -1; - _calculate_max_line_width(); } void TextEdit::Text::invalidate_font() { @@ -299,8 +296,8 @@ void TextEdit::Text::invalidate_font() { return; } - max_width = -1; - line_height = -1; + max_line_width_dirty = true; + max_line_height_dirty = true; if (font.is_valid() && font_size > 0) { font_height = font->get_height(font_size); @@ -317,8 +314,8 @@ void TextEdit::Text::invalidate_all() { return; } - max_width = -1; - line_height = -1; + max_line_width_dirty = true; + max_line_height_dirty = true; if (font.is_valid() && font_size > 0) { font_height = font->get_height(font_size); @@ -333,8 +330,8 @@ void TextEdit::Text::invalidate_all() { void TextEdit::Text::clear() { text.clear(); - max_width = -1; - line_height = -1; + max_line_width_dirty = true; + max_line_height_dirty = true; Line line; line.gutters.resize(gutter_count); @@ -343,8 +340,8 @@ void TextEdit::Text::clear() { invalidate_cache(0, -1, true); } -int TextEdit::Text::get_max_width() const { - return max_width; +int TextEdit::Text::get_total_visible_line_count() const { + return total_visible_line_count; } void TextEdit::Text::set(int p_line, const String &p_text, const Array &p_bidi_override) { @@ -355,7 +352,37 @@ void TextEdit::Text::set(int p_line, const String &p_text, const Array &p_bidi_o invalidate_cache(p_line, -1, true); } +void TextEdit::Text::set_hidden(int p_line, bool p_hidden) { + ERR_FAIL_INDEX(p_line, text.size()); + + Line &text_line = text.write[p_line]; + if (text_line.hidden == p_hidden) { + return; + } + text_line.hidden = p_hidden; + if (p_hidden) { + total_visible_line_count -= text_line.line_count; + if (text_line.width == max_line_width) { + max_line_width_dirty = true; + } + if (text_line.height == max_line_height) { + max_line_height_dirty = true; + } + } else { + total_visible_line_count += text_line.line_count; + max_line_width = MAX(text_line.width, max_line_width); + max_line_height = MAX(text_line.height, max_line_height); + } +} + +bool TextEdit::Text::is_hidden(int p_line) const { + ERR_FAIL_INDEX_V(p_line, text.size(), true); + return text[p_line].hidden; +} + void TextEdit::Text::insert(int p_at, const Vector<String> &p_text, const Vector<Array> &p_bidi_override) { + ERR_FAIL_INDEX(p_at, text.size() + 1); + int new_line_count = p_text.size() - 1; if (new_line_count > 0) { text.resize(text.size() + new_line_count); @@ -382,24 +409,25 @@ void TextEdit::Text::insert(int p_at, const Vector<String> &p_text, const Vector } void TextEdit::Text::remove_range(int p_from_line, int p_to_line) { + p_from_line = MAX(p_from_line, 0); + ERR_FAIL_INDEX(p_from_line, text.size()); + + p_to_line = MIN(p_to_line, text.size()); + ERR_FAIL_COND(p_to_line < p_from_line); + if (p_from_line == p_to_line) { return; } - bool dirty_height = false; - bool dirty_width = false; for (int i = p_from_line; i < p_to_line; i++) { - if (!dirty_height && text[i].height == line_height) { - dirty_height = true; - } - - if (!dirty_width && text[i].width == max_width) { - dirty_width = true; + const Line &text_line = text[i]; + if (text_line.height == max_line_height) { + max_line_height_dirty = true; } - - if (dirty_height && dirty_width) { - break; + if (text_line.width == max_line_width) { + max_line_width_dirty = true; } + total_visible_line_count -= text_line.line_count; } int diff = (p_to_line - p_from_line); @@ -407,16 +435,6 @@ void TextEdit::Text::remove_range(int p_from_line, int p_to_line) { text.write[(i - diff) + 1] = text[i + 1]; } text.resize(text.size() - diff); - - if (dirty_height) { - line_height = -1; - _calculate_line_height(); - } - - if (dirty_width) { - max_width = -1; - _calculate_max_line_width(); - } } void TextEdit::Text::add_gutter(int p_at) { @@ -431,6 +449,8 @@ void TextEdit::Text::add_gutter(int p_at) { } void TextEdit::Text::remove_gutter(int p_gutter) { + ERR_FAIL_INDEX(p_gutter, text.size()); + for (int i = 0; i < text.size(); i++) { text.write[i].gutters.remove_at(p_gutter); } @@ -438,6 +458,9 @@ void TextEdit::Text::remove_gutter(int p_gutter) { } void TextEdit::Text::move_gutters(int p_from_line, int p_to_line) { + ERR_FAIL_INDEX(p_from_line, text.size()); + ERR_FAIL_INDEX(p_to_line, text.size()); + text.write[p_to_line].gutters = text[p_from_line].gutters; text.write[p_from_line].gutters.clear(); text.write[p_from_line].gutters.resize(gutter_count); @@ -625,6 +648,8 @@ void TextEdit::_notification(int p_what) { brace_matching.resize(get_caret_count()); for (int caret = 0; caret < get_caret_count(); caret++) { + BraceMatchingData &brace_match = brace_matching.write[caret]; + if (get_caret_line(caret) < 0 || get_caret_line(caret) >= text.size() || get_caret_column(caret) < 0) { continue; } @@ -678,20 +703,20 @@ void TextEdit::_notification(int p_what) { } if (stack == 0) { - brace_matching.write[caret].open_match_line = i; - brace_matching.write[caret].open_match_column = j; - brace_matching.write[caret].open_matching = true; + brace_match.open_match_line = i; + brace_match.open_match_column = j; + brace_match.open_matching = true; break; } } - if (brace_matching.write[caret].open_match_line != -1) { + if (brace_match.open_match_line != -1) { break; } } - if (!brace_matching.write[caret].open_matching) { - brace_matching.write[caret].open_mismatch = true; + if (!brace_match.open_matching) { + brace_match.open_mismatch = true; } } } @@ -744,27 +769,27 @@ void TextEdit::_notification(int p_what) { } if (stack == 0) { - brace_matching.write[caret].close_match_line = i; - brace_matching.write[caret].close_match_column = j; - brace_matching.write[caret].close_matching = true; + brace_match.close_match_line = i; + brace_match.close_match_column = j; + brace_match.close_matching = true; break; } } - if (brace_matching.write[caret].close_match_line != -1) { + if (brace_match.close_match_line != -1) { break; } } - if (!brace_matching.write[caret].close_matching) { - brace_matching.write[caret].close_mismatch = true; + if (!brace_match.close_matching) { + brace_match.close_mismatch = true; } } } } } - bool draw_placeholder = text.size() == 1 && text[0].is_empty() && ime_text.is_empty(); + bool draw_placeholder = _using_placeholder(); // Get the highlighted words. String highlighted_text = get_selected_text(0); @@ -772,13 +797,14 @@ void TextEdit::_notification(int p_what) { // Check if highlighted words contain only whitespaces (tabs or spaces). bool only_whitespaces_highlighted = highlighted_text.strip_edges().is_empty(); - HashMap<int, HashSet<int>> caret_line_wrap_index_map; + Vector<Pair<int, int>> highlighted_lines; + highlighted_lines.resize(carets.size()); Vector<int> carets_wrap_index; carets_wrap_index.resize(carets.size()); for (int i = 0; i < carets.size(); i++) { carets.write[i].visible = false; int wrap_index = get_caret_wrap_index(i); - caret_line_wrap_index_map[get_caret_line(i)].insert(wrap_index); + highlighted_lines.write[i] = Pair<int, int>(get_caret_line(i), wrap_index); carets_wrap_index.write[i] = wrap_index; } @@ -842,7 +868,7 @@ void TextEdit::_notification(int p_what) { break; } - Dictionary color_map = _get_line_syntax_highlighting(minimap_line); + const Vector<Pair<int64_t, Color>> color_map = _get_line_syntax_highlighting(minimap_line); Color line_background_color = text.get_line_background_color(minimap_line); @@ -853,12 +879,9 @@ void TextEdit::_notification(int p_what) { line_background_color.a *= 0.6; } - Color current_color = theme_cache.font_color; - if (!editable) { - current_color = theme_cache.font_readonly_color; - } + Color current_color = editable ? theme_cache.font_color : theme_cache.font_readonly_color; - Vector<String> wrap_rows = get_line_wrapped_text(minimap_line); + const Vector<String> wrap_rows = get_line_wrapped_text(minimap_line); int line_wrap_amount = get_line_wrap_count(minimap_line); int last_wrap_column = 0; @@ -881,13 +904,13 @@ void TextEdit::_notification(int p_what) { last_wrap_column += wrap_rows[line_wrap_index - 1].length(); } - if (caret_line_wrap_index_map.has(minimap_line) && caret_line_wrap_index_map[minimap_line].has(line_wrap_index) && highlight_current_line) { + if (highlight_current_line && highlighted_lines.has(Pair<int, int>(minimap_line, line_wrap_index))) { if (rtl) { RenderingServer::get_singleton()->canvas_item_add_rect(ci, Rect2(size.width - (xmargin_end + 2) - minimap_width, i * 3, minimap_width, 2), theme_cache.current_line_color); } else { RenderingServer::get_singleton()->canvas_item_add_rect(ci, Rect2((xmargin_end + 2), i * 3, minimap_width, 2), theme_cache.current_line_color); } - } else if (line_background_color != Color(0, 0, 0, 0)) { + } else if (line_background_color.a > 0) { if (rtl) { RenderingServer::get_singleton()->canvas_item_add_rect(ci, Rect2(size.width - (xmargin_end + 2) - minimap_width, i * 3, minimap_width, 2), line_background_color); } else { @@ -905,13 +928,17 @@ void TextEdit::_notification(int p_what) { // Get the number of characters to draw together. for (characters = 0; j + characters < str.length(); characters++) { int next_char_index = j + characters; - const Variant *color_data = color_map.getptr(last_wrap_column + next_char_index); - if (color_data != nullptr) { - next_color = (color_data->operator Dictionary()).get("color", theme_cache.font_color); - if (!editable) { - next_color.a = theme_cache.font_readonly_color.a; + + for (const Pair<int64_t, Color> &color_data : color_map) { + if (last_wrap_column + next_char_index >= color_data.first) { + next_color = color_data.second; + if (!editable) { + next_color.a = theme_cache.font_readonly_color.a; + } + next_color.a *= 0.6; + } else { + break; } - next_color.a *= 0.6; } if (characters == 0) { current_color = next_color; @@ -1002,7 +1029,7 @@ void TextEdit::_notification(int p_what) { LineDrawingCache cache_entry; - Dictionary color_map = _get_line_syntax_highlighting(line); + const Vector<Pair<int64_t, Color>> color_map = _get_line_syntax_highlighting(line); // Ensure we at least use the font color. Color current_color = !editable ? theme_cache.font_readonly_color : theme_cache.font_color; @@ -1012,7 +1039,7 @@ void TextEdit::_notification(int p_what) { const Ref<TextParagraph> ldata = draw_placeholder ? placeholder_data_buf : text.get_line_data(line); - Vector<String> wrap_rows = draw_placeholder ? placeholder_wraped_rows : get_line_wrapped_text(line); + const Vector<String> wrap_rows = draw_placeholder ? placeholder_wraped_rows : get_line_wrapped_text(line); int line_wrap_amount = draw_placeholder ? placeholder_wraped_rows.size() - 1 : get_line_wrap_count(line); for (int line_wrap_index = 0; line_wrap_index <= line_wrap_amount; line_wrap_index++) { @@ -1053,20 +1080,20 @@ void TextEdit::_notification(int p_what) { break; } - if (text.get_line_background_color(line) != Color(0, 0, 0, 0)) { + if (text.get_line_background_color(line).a > 0.0) { if (rtl) { - RenderingServer::get_singleton()->canvas_item_add_rect(ci, Rect2(size.width - ofs_x - xmargin_end, ofs_y, xmargin_end - xmargin_beg, row_height), text.get_line_background_color(line)); + RS::get_singleton()->canvas_item_add_rect(ci, Rect2(size.width - ofs_x - xmargin_end, ofs_y, xmargin_end - xmargin_beg, row_height), text.get_line_background_color(line)); } else { - RenderingServer::get_singleton()->canvas_item_add_rect(ci, Rect2(xmargin_beg + ofs_x, ofs_y, xmargin_end - xmargin_beg, row_height), text.get_line_background_color(line)); + RS::get_singleton()->canvas_item_add_rect(ci, Rect2(xmargin_beg + ofs_x, ofs_y, xmargin_end - xmargin_beg, row_height), text.get_line_background_color(line)); } } // Draw current line highlight. - if (highlight_current_line && caret_line_wrap_index_map.has(line) && caret_line_wrap_index_map[line].has(line_wrap_index)) { + if (highlight_current_line && highlighted_lines.has(Pair<int, int>(line, line_wrap_index))) { if (rtl) { - RenderingServer::get_singleton()->canvas_item_add_rect(ci, Rect2(size.width - ofs_x - xmargin_end, ofs_y, xmargin_end, row_height), theme_cache.current_line_color); + RS::get_singleton()->canvas_item_add_rect(ci, Rect2(size.width - ofs_x - xmargin_end, ofs_y, xmargin_end, row_height), theme_cache.current_line_color); } else { - RenderingServer::get_singleton()->canvas_item_add_rect(ci, Rect2(ofs_x, ofs_y, xmargin_end, row_height), theme_cache.current_line_color); + RS::get_singleton()->canvas_item_add_rect(ci, Rect2(ofs_x, ofs_y, xmargin_end, row_height), theme_cache.current_line_color); } } @@ -1077,7 +1104,7 @@ void TextEdit::_notification(int p_what) { int gutter_offset = theme_cache.style_normal->get_margin(SIDE_LEFT); for (int g = 0; g < gutters.size(); g++) { - const GutterInfo gutter = gutters[g]; + const GutterInfo &gutter = gutters[g]; if (!gutter.draw || gutter.width <= 0) { continue; @@ -1184,7 +1211,7 @@ void TextEdit::_notification(int p_what) { if (rect.position.x + rect.size.x > xmargin_end) { rect.size.x = xmargin_end - rect.position.x; } - draw_rect(rect, theme_cache.selection_color, true); + RS::get_singleton()->canvas_item_add_rect(ci, rect, theme_cache.selection_color); } } } @@ -1194,7 +1221,7 @@ void TextEdit::_notification(int p_what) { int search_text_col = _get_column_pos_of_word(search_text, str, search_flags, 0); int search_text_len = search_text.length(); while (search_text_col != -1) { - Vector<Vector2> sel = TS->shaped_text_get_selection(rid, search_text_col + start, search_text_col + search_text_len + start); + const Vector<Vector2> sel = TS->shaped_text_get_selection(rid, search_text_col + start, search_text_col + search_text_len + start); for (int j = 0; j < sel.size(); j++) { Rect2 rect = Rect2(sel[j].x + char_margin + ofs_x, ofs_y, sel[j].y - sel[j].x, row_height); if (rect.position.x + rect.size.x <= xmargin_beg || rect.position.x > xmargin_end) { @@ -1206,7 +1233,7 @@ void TextEdit::_notification(int p_what) { } else if (rect.position.x + rect.size.x > xmargin_end) { rect.size.x = xmargin_end - rect.position.x; } - draw_rect(rect, theme_cache.search_result_color, true); + RS::get_singleton()->canvas_item_add_rect(ci, rect, theme_cache.search_result_color); draw_rect(rect, theme_cache.search_result_border_color, false); } @@ -1218,7 +1245,7 @@ void TextEdit::_notification(int p_what) { int highlighted_text_col = _get_column_pos_of_word(highlighted_text, str, SEARCH_MATCH_CASE | SEARCH_WHOLE_WORDS, 0); int highlighted_text_len = highlighted_text.length(); while (highlighted_text_col != -1) { - Vector<Vector2> sel = TS->shaped_text_get_selection(rid, highlighted_text_col + start, highlighted_text_col + highlighted_text_len + start); + const Vector<Vector2> sel = TS->shaped_text_get_selection(rid, highlighted_text_col + start, highlighted_text_col + highlighted_text_len + start); for (int j = 0; j < sel.size(); j++) { Rect2 rect = Rect2(sel[j].x + char_margin + ofs_x, ofs_y, sel[j].y - sel[j].x, row_height); if (rect.position.x + rect.size.x <= xmargin_beg || rect.position.x > xmargin_end) { @@ -1230,7 +1257,7 @@ void TextEdit::_notification(int p_what) { } else if (rect.position.x + rect.size.x > xmargin_end) { rect.size.x = xmargin_end - rect.position.x; } - draw_rect(rect, theme_cache.word_highlighted_color); + RS::get_singleton()->canvas_item_add_rect(ci, rect, theme_cache.word_highlighted_color); } highlighted_text_col = _get_column_pos_of_word(highlighted_text, str, SEARCH_MATCH_CASE | SEARCH_WHOLE_WORDS, highlighted_text_col + highlighted_text_len); @@ -1243,7 +1270,7 @@ void TextEdit::_notification(int p_what) { int lookup_symbol_word_col = _get_column_pos_of_word(lookup_symbol_word, str, SEARCH_MATCH_CASE | SEARCH_WHOLE_WORDS, 0); int lookup_symbol_word_len = lookup_symbol_word.length(); while (lookup_symbol_word_col != -1) { - Vector<Vector2> sel = TS->shaped_text_get_selection(rid, lookup_symbol_word_col + start, lookup_symbol_word_col + lookup_symbol_word_len + start); + const Vector<Vector2> sel = TS->shaped_text_get_selection(rid, lookup_symbol_word_col + start, lookup_symbol_word_col + lookup_symbol_word_len + start); for (int j = 0; j < sel.size(); j++) { Rect2 rect = Rect2(sel[j].x + char_margin + ofs_x, ofs_y + (theme_cache.line_spacing / 2), sel[j].y - sel[j].x, row_height); if (rect.position.x + rect.size.x <= xmargin_beg || rect.position.x > xmargin_end) { @@ -1257,7 +1284,7 @@ void TextEdit::_notification(int p_what) { } rect.position.y += ceil(TS->shaped_text_get_ascent(rid)) + ceil(theme_cache.font->get_underline_position(theme_cache.font_size)); rect.size.y = MAX(1, theme_cache.font->get_underline_thickness(theme_cache.font_size)); - draw_rect(rect, highlight_underline_color); + RS::get_singleton()->canvas_item_add_rect(ci, rect, highlight_underline_color); } lookup_symbol_word_col = _get_column_pos_of_word(lookup_symbol_word, str, SEARCH_MATCH_CASE | SEARCH_WHOLE_WORDS, lookup_symbol_word_col + lookup_symbol_word_len); @@ -1293,21 +1320,16 @@ void TextEdit::_notification(int p_what) { char_ofs = 0; } for (int j = 0; j < gl_size; j++) { - int64_t color_start = -1; - for (const Variant *key = color_map.next(nullptr); key; key = color_map.next(key)) { - if (int64_t(*key) <= glyphs[j].start) { - color_start = *key; + for (const Pair<int64_t, Color> &color_data : color_map) { + if (color_data.first <= glyphs[j].start) { + current_color = color_data.second; + if (!editable && current_color.a > theme_cache.font_readonly_color.a) { + current_color.a = theme_cache.font_readonly_color.a; + } } else { break; } } - const Variant *color_data = (color_start >= 0) ? color_map.getptr(color_start) : nullptr; - if (color_data != nullptr) { - current_color = (color_data->operator Dictionary()).get("color", theme_cache.font_color); - if (!editable && current_color.a > theme_cache.font_readonly_color.a) { - current_color.a = theme_cache.font_readonly_color.a; - } - } Color gl_color = current_color; for (int c = 0; c < get_caret_count(); c++) { @@ -1325,22 +1347,23 @@ void TextEdit::_notification(int p_what) { if (char_pos >= xmargin_beg) { if (highlight_matching_braces_enabled) { for (int c = 0; c < get_caret_count(); c++) { - if ((brace_matching[c].open_match_line == line && brace_matching[c].open_match_column == glyphs[j].start) || - (get_caret_column(c) == glyphs[j].start && get_caret_line(c) == line && carets_wrap_index[c] == line_wrap_index && (brace_matching[c].open_matching || brace_matching[c].open_mismatch))) { - if (brace_matching[c].open_mismatch) { + const BraceMatchingData &brace_match = brace_matching[c]; + if ((brace_match.open_match_line == line && brace_match.open_match_column == glyphs[j].start) || + (get_caret_column(c) == glyphs[j].start && get_caret_line(c) == line && carets_wrap_index[c] == line_wrap_index && (brace_match.open_matching || brace_match.open_mismatch))) { + if (brace_match.open_mismatch) { gl_color = _get_brace_mismatch_color(); } Rect2 rect = Rect2(char_pos, ofs_y + theme_cache.font->get_underline_position(theme_cache.font_size), glyphs[j].advance * glyphs[j].repeat, MAX(theme_cache.font->get_underline_thickness(theme_cache.font_size) * theme_cache.base_scale, 1)); - draw_rect(rect, gl_color); + RS::get_singleton()->canvas_item_add_rect(ci, rect, gl_color); } - if ((brace_matching[c].close_match_line == line && brace_matching[c].close_match_column == glyphs[j].start) || - (get_caret_column(c) == glyphs[j].start + 1 && get_caret_line(c) == line && carets_wrap_index[c] == line_wrap_index && (brace_matching[c].close_matching || brace_matching[c].close_mismatch))) { - if (brace_matching[c].close_mismatch) { + if ((brace_match.close_match_line == line && brace_match.close_match_column == glyphs[j].start) || + (get_caret_column(c) == glyphs[j].start + 1 && get_caret_line(c) == line && carets_wrap_index[c] == line_wrap_index && (brace_match.close_matching || brace_match.close_mismatch))) { + if (brace_match.close_mismatch) { gl_color = _get_brace_mismatch_color(); } Rect2 rect = Rect2(char_pos, ofs_y + theme_cache.font->get_underline_position(theme_cache.font_size), glyphs[j].advance * glyphs[j].repeat, MAX(theme_cache.font->get_underline_thickness(theme_cache.font_size) * theme_cache.base_scale, 1)); - draw_rect(rect, gl_color); + RS::get_singleton()->canvas_item_add_rect(ci, rect, gl_color); } } } @@ -1451,11 +1474,11 @@ void TextEdit::_notification(int p_what) { // Draw split caret (leading part). ts_caret.l_caret.position += Vector2(char_margin + ofs_x, ofs_y); ts_caret.l_caret.size.x = caret_width; - draw_rect(ts_caret.l_caret, theme_cache.caret_color); + RS::get_singleton()->canvas_item_add_rect(ci, ts_caret.l_caret, theme_cache.caret_color); // Draw extra direction marker on top of split caret. float d = (ts_caret.l_dir == TextServer::DIRECTION_LTR) ? 0.5 : -3; Rect2 trect = Rect2(ts_caret.l_caret.position.x + d * caret_width, ts_caret.l_caret.position.y + ts_caret.l_caret.size.y - caret_width, 3 * caret_width, caret_width); - RenderingServer::get_singleton()->canvas_item_add_rect(ci, trect, theme_cache.caret_color); + RS::get_singleton()->canvas_item_add_rect(ci, trect, theme_cache.caret_color); } } else { // End of the line. if (gl_size > 0) { @@ -1488,28 +1511,28 @@ void TextEdit::_notification(int p_what) { // Draw extra marker on top of mid caret. Rect2 trect = Rect2(ts_caret.l_caret.position.x - 2.5 * caret_width, ts_caret.l_caret.position.y, 6 * caret_width, caret_width); trect.position += Vector2(char_margin + ofs_x, ofs_y); - RenderingServer::get_singleton()->canvas_item_add_rect(ci, trect, theme_cache.caret_color); + RS::get_singleton()->canvas_item_add_rect(ci, trect, theme_cache.caret_color); } else if (ts_caret.l_caret != Rect2() && ts_caret.t_caret != Rect2() && ts_caret.l_dir != ts_caret.t_dir) { // Draw extra direction marker on top of split caret. float d = (ts_caret.l_dir == TextServer::DIRECTION_LTR) ? 0.5 : -3; Rect2 trect = Rect2(ts_caret.l_caret.position.x + d * caret_width, ts_caret.l_caret.position.y + ts_caret.l_caret.size.y - caret_width, 3 * caret_width, caret_width); trect.position += Vector2(char_margin + ofs_x, ofs_y); - RenderingServer::get_singleton()->canvas_item_add_rect(ci, trect, theme_cache.caret_color); + RS::get_singleton()->canvas_item_add_rect(ci, trect, theme_cache.caret_color); d = (ts_caret.t_dir == TextServer::DIRECTION_LTR) ? 0.5 : -3; trect = Rect2(ts_caret.t_caret.position.x + d * caret_width, ts_caret.t_caret.position.y, 3 * caret_width, caret_width); trect.position += Vector2(char_margin + ofs_x, ofs_y); - RenderingServer::get_singleton()->canvas_item_add_rect(ci, trect, theme_cache.caret_color); + RS::get_singleton()->canvas_item_add_rect(ci, trect, theme_cache.caret_color); } ts_caret.l_caret.position += Vector2(char_margin + ofs_x, ofs_y); ts_caret.l_caret.size.x = caret_width; - draw_rect(ts_caret.l_caret, theme_cache.caret_color); + RS::get_singleton()->canvas_item_add_rect(ci, ts_caret.l_caret, theme_cache.caret_color); ts_caret.t_caret.position += Vector2(char_margin + ofs_x, ofs_y); ts_caret.t_caret.size.x = caret_width; - draw_rect(ts_caret.t_caret, theme_cache.caret_color); + RS::get_singleton()->canvas_item_add_rect(ci, ts_caret.t_caret, theme_cache.caret_color); } } } @@ -1517,7 +1540,7 @@ void TextEdit::_notification(int p_what) { if (!ime_text.is_empty()) { { // IME Intermediate text range. - Vector<Vector2> sel = TS->shaped_text_get_selection(rid, get_caret_column(c), get_caret_column(c) + ime_text.length()); + const Vector<Vector2> sel = TS->shaped_text_get_selection(rid, get_caret_column(c), get_caret_column(c) + ime_text.length()); for (int j = 0; j < sel.size(); j++) { Rect2 rect = Rect2(sel[j].x + char_margin + ofs_x, ofs_y, sel[j].y - sel[j].x, text_height); if (rect.position.x + rect.size.x <= xmargin_beg || rect.position.x > xmargin_end) { @@ -1534,9 +1557,9 @@ void TextEdit::_notification(int p_what) { carets.write[c].draw_pos.x = rect.position.x; } } - { + if (ime_selection.y > 0) { // IME caret. - Vector<Vector2> sel = TS->shaped_text_get_selection(rid, get_caret_column(c) + ime_selection.x, get_caret_column(c) + ime_selection.x + ime_selection.y); + const Vector<Vector2> sel = TS->shaped_text_get_selection(rid, get_caret_column(c) + ime_selection.x, get_caret_column(c) + ime_selection.x + ime_selection.y); for (int j = 0; j < sel.size(); j++) { Rect2 rect = Rect2(sel[j].x + char_margin + ofs_x, ofs_y, sel[j].y - sel[j].x, text_height); if (rect.position.x + rect.size.x <= xmargin_beg || rect.position.x > xmargin_end) { @@ -1575,26 +1598,7 @@ void TextEdit::_notification(int p_what) { draw_caret = true; } - _update_ime_window_position(); - - if (DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_VIRTUAL_KEYBOARD) && virtual_keyboard_enabled) { - int caret_start = -1; - int caret_end = -1; - - if (!has_selection(0)) { - String full_text = _base_get_text(0, 0, get_caret_line(), get_caret_column()); - - caret_start = full_text.length(); - } else { - String pre_text = _base_get_text(0, 0, get_selection_from_line(), get_selection_from_column()); - String post_text = get_selected_text(0); - - caret_start = pre_text.length(); - caret_end = caret_start + post_text.length(); - } - - DisplayServer::get_singleton()->virtual_keyboard_show(get_text(), get_global_rect(), DisplayServer::KEYBOARD_TYPE_MULTILINE, -1, caret_start, caret_end); - } + _show_virtual_keyboard(); } break; case NOTIFICATION_FOCUS_EXIT: { @@ -1684,39 +1688,93 @@ void TextEdit::unhandled_key_input(const Ref<InputEvent> &p_event) { bool TextEdit::alt_input(const Ref<InputEvent> &p_gui_input) { Ref<InputEventKey> k = p_gui_input; if (k.is_valid()) { - if (!k->is_pressed()) { - if (alt_start && k->get_keycode() == Key::ALT) { - alt_start = false; - if ((alt_code > 0x31 && alt_code < 0xd800) || (alt_code > 0xdfff && alt_code <= 0x10ffff)) { - handle_unicode_input(alt_code); - } - return true; + // Start Unicode input (hold). + if (k->is_alt_pressed() && k->get_keycode() == Key::KP_ADD && !alt_start && !alt_start_no_hold) { + if (has_selection()) { + delete_selection(); } - return false; + alt_start = true; + alt_code = 0; + ime_text = "u"; + ime_selection = Vector2i(0, -1); + _update_ime_text(); + return true; } - if (k->is_alt_pressed()) { - if (!alt_start) { - if (k->get_keycode() == Key::KP_ADD) { - alt_start = true; - alt_code = 0; - return true; - } + // Start Unicode input (press). + if (k->is_action("ui_unicode_start", true) && !alt_start && !alt_start_no_hold) { + if (has_selection()) { + delete_selection(); + } + alt_start_no_hold = true; + alt_code = 0; + ime_text = "u"; + ime_selection = Vector2i(0, -1); + _update_ime_text(); + return true; + } + + // Update Unicode input. + if (k->is_pressed() && ((k->is_alt_pressed() && alt_start) || alt_start_no_hold)) { + if (k->get_keycode() >= Key::KEY_0 && k->get_keycode() <= Key::KEY_9) { + alt_code = alt_code << 4; + alt_code += (uint32_t)(k->get_keycode() - Key::KEY_0); + } else if (k->get_keycode() >= Key::KP_0 && k->get_keycode() <= Key::KP_9) { + alt_code = alt_code << 4; + alt_code += (uint32_t)(k->get_keycode() - Key::KP_0); + } else if (k->get_keycode() >= Key::A && k->get_keycode() <= Key::F) { + alt_code = alt_code << 4; + alt_code += (uint32_t)(k->get_keycode() - Key::A) + 10; + } else if ((Key)k->get_unicode() >= Key::KEY_0 && (Key)k->get_unicode() <= Key::KEY_9) { + alt_code = alt_code << 4; + alt_code += (uint32_t)((Key)k->get_unicode() - Key::KEY_0); + } else if ((Key)k->get_unicode() >= Key::A && (Key)k->get_unicode() <= Key::F) { + alt_code = alt_code << 4; + alt_code += (uint32_t)((Key)k->get_unicode() - Key::A) + 10; + } else if (k->get_physical_keycode() >= Key::KEY_0 && k->get_physical_keycode() <= Key::KEY_9) { + alt_code = alt_code << 4; + alt_code += (uint32_t)(k->get_physical_keycode() - Key::KEY_0); + } + if (k->get_keycode() == Key::BACKSPACE) { + alt_code = alt_code >> 4; + } + if (alt_code > 0x10ffff) { + alt_code = 0x10ffff; + } + if (alt_code > 0) { + ime_text = vformat("u%s", String::num_int64(alt_code, 16, true)); } else { - if (k->get_keycode() >= Key::KEY_0 && k->get_keycode() <= Key::KEY_9) { - alt_code = alt_code << 4; - alt_code += (uint32_t)(k->get_keycode() - Key::KEY_0); - } - if (k->get_keycode() >= Key::KP_0 && k->get_keycode() <= Key::KP_9) { - alt_code = alt_code << 4; - alt_code += (uint32_t)(k->get_keycode() - Key::KP_0); - } - if (k->get_keycode() >= Key::A && k->get_keycode() <= Key::F) { - alt_code = alt_code << 4; - alt_code += (uint32_t)(k->get_keycode() - Key::A) + 10; - } - return true; + ime_text = "u"; } + ime_selection = Vector2i(0, -1); + _update_ime_text(); + return true; + } + + // Submit Unicode input. + if ((!k->is_pressed() && alt_start && k->get_keycode() == Key::ALT) || (alt_start_no_hold && (k->is_action("ui_text_submit", true) || k->is_action("ui_accept", true)))) { + alt_start = false; + alt_start_no_hold = false; + if ((alt_code > 0x31 && alt_code < 0xd800) || (alt_code > 0xdfff && alt_code <= 0x10ffff)) { + ime_text = String(); + ime_selection = Vector2i(); + handle_unicode_input(alt_code); + } else { + ime_text = String(); + ime_selection = Vector2i(); + } + _update_ime_text(); + return true; + } + + // Cancel Unicode input. + if (alt_start_no_hold && k->is_action("ui_cancel", true)) { + alt_start = false; + alt_start_no_hold = false; + ime_text = String(); + ime_selection = Vector2i(); + _update_ime_text(); + return true; } } return false; @@ -1943,8 +2001,7 @@ void TextEdit::gui_input(const Ref<InputEvent> &p_gui_input) { } } - // Notify to show soft keyboard. - notification(NOTIFICATION_FOCUS_ENTER); + _show_virtual_keyboard(); } } @@ -2393,7 +2450,7 @@ void TextEdit::_move_caret_left(bool p_select, bool p_move_by_word) { } else { PackedInt32Array words = TS->shaped_text_get_word_breaks(text.get_line_data(get_caret_line(i))->get_rid()); if (words.is_empty() || cc <= words[0]) { - // This solves the scenario where there are no words but glyfs that can be ignored. + // Move to the start when there are no more words. cc = 0; } else { for (int j = words.size() - 2; j >= 0; j = j - 2) { @@ -2450,7 +2507,7 @@ void TextEdit::_move_caret_right(bool p_select, bool p_move_by_word) { } else { PackedInt32Array words = TS->shaped_text_get_word_breaks(text.get_line_data(get_caret_line(i))->get_rid()); if (words.is_empty() || cc >= words[words.size() - 1]) { - // This solves the scenario where there are no words but glyfs that can be ignored. + // Move to the end when there are no more words. cc = text[get_caret_line(i)].length(); } else { for (int j = 1; j < words.size(); j = j + 2) { @@ -2666,7 +2723,7 @@ void TextEdit::_do_backspace(bool p_word, bool p_all_to_left) { // Get a list with the indices of the word bounds of the given text line. const PackedInt32Array words = TS->shaped_text_get_word_breaks(text.get_line_data(get_caret_line(caret_index))->get_rid()); if (words.is_empty() || column <= words[0]) { - // If "words" is empty, meaning no words are left, we can remove everything until the beginning of the line. + // Delete to the start when there are no more words. column = 0; } else { // Otherwise search for the first word break that is smaller than the index from we're currently deleting. @@ -2731,10 +2788,15 @@ void TextEdit::_delete(bool p_word, bool p_all_to_right) { int column = get_caret_column(caret_index); PackedInt32Array words = TS->shaped_text_get_word_breaks(text.get_line_data(line)->get_rid()); - for (int j = 1; j < words.size(); j = j + 2) { - if (words[j] > column) { - column = words[j]; - break; + if (words.is_empty() || column >= words[words.size() - 1]) { + // Delete to the end when there are no more words. + column = text[get_caret_line(i)].length(); + } else { + for (int j = 1; j < words.size(); j = j + 2) { + if (words[j] > column) { + column = words[j]; + break; + } } } @@ -2849,6 +2911,10 @@ void TextEdit::_update_placeholder() { } } +bool TextEdit::_using_placeholder() const { + return text.size() == 1 && text[0].is_empty() && ime_text.is_empty(); +} + void TextEdit::_update_theme_item_cache() { Control::_update_theme_item_cache(); @@ -2920,11 +2986,37 @@ void TextEdit::_update_ime_text() { queue_redraw(); } +void TextEdit::_show_virtual_keyboard() { + _update_ime_window_position(); + + if (virtual_keyboard_enabled && DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_VIRTUAL_KEYBOARD)) { + int caret_start = -1; + int caret_end = -1; + + if (!has_selection(0)) { + String full_text = _base_get_text(0, 0, get_caret_line(), get_caret_column()); + + caret_start = full_text.length(); + } else { + String pre_text = _base_get_text(0, 0, get_selection_from_line(), get_selection_from_column()); + String post_text = get_selected_text(0); + + caret_start = pre_text.length(); + caret_end = caret_start + post_text.length(); + } + + DisplayServer::get_singleton()->virtual_keyboard_show(get_text(), get_global_rect(), DisplayServer::KEYBOARD_TYPE_MULTILINE, -1, caret_start, caret_end); + } +} + /* General overrides. */ Size2 TextEdit::get_minimum_size() const { Size2 size = theme_cache.style_normal->get_minimum_size(); if (fit_content_height) { - size.y += content_height_cache; + size.y += content_size_cache.y; + } + if (fit_content_width) { + size.x += content_size_cache.x; } return size; } @@ -2956,13 +3048,13 @@ bool TextEdit::can_drop_data(const Point2 &p_point, const Variant &p_data) const return drop_override; } - return is_editable() && p_data.get_type() == Variant::STRING; + return is_editable() && p_data.is_string(); } void TextEdit::drop_data(const Point2 &p_point, const Variant &p_data) { Control::drop_data(p_point, p_data); - if (p_data.get_type() == Variant::STRING && is_editable()) { + if (p_data.is_string() && is_editable()) { Point2i pos = get_line_column_at_pos(get_local_mouse_pos()); int drop_at_line = pos.y; int drop_at_column = pos.x; @@ -3079,7 +3171,9 @@ void TextEdit::cancel_ime() { return; } ime_text = String(); - ime_selection = Point2(); + ime_selection = Vector2i(); + alt_start = false; + alt_start_no_hold = false; _close_ime_window(); _update_ime_text(); } @@ -3088,13 +3182,21 @@ void TextEdit::apply_ime() { if (!has_ime_text()) { return; } + // Force apply the current IME text. - String insert_ime_text = ime_text; - cancel_ime(); - insert_text_at_caret(insert_ime_text); + if (alt_start || alt_start_no_hold) { + cancel_ime(); + if ((alt_code > 0x31 && alt_code < 0xd800) || (alt_code > 0xdfff && alt_code <= 0x10ffff)) { + handle_unicode_input(alt_code); + } + } else { + String insert_ime_text = ime_text; + cancel_ime(); + insert_text_at_caret(insert_ime_text); + } } -void TextEdit::set_editable(const bool p_editable) { +void TextEdit::set_editable(bool p_editable) { if (editable == p_editable) { return; } @@ -3219,7 +3321,7 @@ bool TextEdit::is_indent_wrapped_lines() const { } // User controls -void TextEdit::set_overtype_mode_enabled(const bool p_enabled) { +void TextEdit::set_overtype_mode_enabled(bool p_enabled) { if (overtype_mode == p_enabled) { return; } @@ -4324,25 +4426,12 @@ Point2i TextEdit::get_line_column_at_pos(const Point2i &p_pos, bool p_allow_out_ return Point2i(text[row].length(), row); } - int col = 0; int colx = p_pos.x - (theme_cache.style_normal->get_margin(SIDE_LEFT) + gutters_width + gutter_padding); colx += first_visible_col; if (!editable) { colx -= theme_cache.style_readonly->get_offset().x / 2; colx += theme_cache.style_normal->get_offset().x / 2; } - col = _get_char_pos_for_line(colx, row, wrap_index); - if (get_line_wrapping_mode() != LineWrappingMode::LINE_WRAPPING_NONE && wrap_index < get_line_wrap_count(row)) { - // Move back one if we are at the end of the row. - Vector<String> rows2 = get_line_wrapped_text(row); - int row_end_col = 0; - for (int i = 0; i < wrap_index + 1; i++) { - row_end_col += rows2[i].length(); - } - if (col >= row_end_col) { - col -= 1; - } - } RID text_rid = text.get_line_data(row)->get_line_rid(wrap_index); float wrap_indent = (text.is_indent_wrapped_lines() && wrap_index > 0) ? get_indent_level(row) * theme_cache.font->get_char_size(' ', theme_cache.font_size).width : 0.0; @@ -4351,7 +4440,7 @@ Point2i TextEdit::get_line_column_at_pos(const Point2i &p_pos, bool p_allow_out_ } else { colx -= wrap_indent; } - col = TS->shaped_text_hit_test_position(text_rid, colx); + int col = TS->shaped_text_hit_test_position(text_rid, colx); if (!caret_mid_grapheme_enabled) { col = TS->shaped_text_closest_character_pos(text_rid, col); } @@ -4482,7 +4571,7 @@ TextEdit::CaretType TextEdit::get_caret_type() const { return caret_type; } -void TextEdit::set_caret_blink_enabled(const bool p_enabled) { +void TextEdit::set_caret_blink_enabled(bool p_enabled) { if (caret_blink_enabled == p_enabled) { return; } @@ -4524,7 +4613,7 @@ bool TextEdit::is_drawing_caret_when_editable_disabled() const { return draw_caret_when_editable_disabled; } -void TextEdit::set_move_caret_on_right_click_enabled(const bool p_enabled) { +void TextEdit::set_move_caret_on_right_click_enabled(bool p_enabled) { move_caret_on_right_click = p_enabled; } @@ -4532,7 +4621,7 @@ bool TextEdit::is_move_caret_on_right_click_enabled() const { return move_caret_on_right_click; } -void TextEdit::set_caret_mid_grapheme_enabled(const bool p_enabled) { +void TextEdit::set_caret_mid_grapheme_enabled(bool p_enabled) { caret_mid_grapheme_enabled = p_enabled; } @@ -4642,7 +4731,7 @@ void TextEdit::add_caret_at_carets(bool p_below) { for (int i = 0; i < num_carets; i++) { const int caret_line = get_caret_line(i); const int caret_column = get_caret_column(i); - const bool is_selected = has_selection(i) || carets[i].last_fit_x != carets[i].selection.origin_last_fit_x; + bool is_selected = has_selection(i) || carets[i].last_fit_x != carets[i].selection.origin_last_fit_x; const int selection_origin_line = get_selection_origin_line(i); const int selection_origin_column = get_selection_origin_column(i); const int caret_wrap_index = get_caret_wrap_index(i); @@ -5107,7 +5196,7 @@ String TextEdit::get_word_under_caret(int p_caret) const { } /* Selection. */ -void TextEdit::set_selecting_enabled(const bool p_enabled) { +void TextEdit::set_selecting_enabled(bool p_enabled) { if (selecting_enabled == p_enabled) { return; } @@ -5123,7 +5212,7 @@ bool TextEdit::is_selecting_enabled() const { return selecting_enabled; } -void TextEdit::set_deselect_on_focus_loss_enabled(const bool p_enabled) { +void TextEdit::set_deselect_on_focus_loss_enabled(bool p_enabled) { if (deselect_on_focus_loss_enabled == p_enabled) { return; } @@ -5138,7 +5227,7 @@ bool TextEdit::is_deselect_on_focus_loss_enabled() const { return deselect_on_focus_loss_enabled; } -void TextEdit::set_drag_and_drop_selection_enabled(const bool p_enabled) { +void TextEdit::set_drag_and_drop_selection_enabled(bool p_enabled) { drag_and_drop_selection_enabled = p_enabled; } @@ -5698,7 +5787,7 @@ Vector<String> TextEdit::get_line_wrapped_text(int p_line) const { /* Viewport */ // Scrolling. -void TextEdit::set_smooth_scroll_enabled(const bool p_enabled) { +void TextEdit::set_smooth_scroll_enabled(bool p_enabled) { v_scroll->set_smooth_scroll_enabled(p_enabled); smooth_scroll_enabled = p_enabled; } @@ -5707,7 +5796,7 @@ bool TextEdit::is_smooth_scroll_enabled() const { return smooth_scroll_enabled; } -void TextEdit::set_scroll_past_end_of_file_enabled(const bool p_enabled) { +void TextEdit::set_scroll_past_end_of_file_enabled(bool p_enabled) { if (scroll_past_end_of_file_enabled == p_enabled) { return; } @@ -5761,7 +5850,7 @@ float TextEdit::get_v_scroll_speed() const { return v_scroll_speed; } -void TextEdit::set_fit_content_height_enabled(const bool p_enabled) { +void TextEdit::set_fit_content_height_enabled(bool p_enabled) { if (fit_content_height == p_enabled) { return; } @@ -5773,6 +5862,18 @@ bool TextEdit::is_fit_content_height_enabled() const { return fit_content_height; } +void TextEdit::set_fit_content_width_enabled(bool p_enabled) { + if (fit_content_width == p_enabled) { + return; + } + fit_content_width = p_enabled; + update_minimum_size(); +} + +bool TextEdit::is_fit_content_width_enabled() const { + return fit_content_width; +} + double TextEdit::get_scroll_pos_for_line(int p_line, int p_wrap_index) const { ERR_FAIL_INDEX_V(p_line, text.size(), 0); ERR_FAIL_COND_V(p_wrap_index < 0, 0); @@ -5877,7 +5978,7 @@ int TextEdit::get_visible_line_count_in_range(int p_from_line, int p_to_line) co } int TextEdit::get_total_visible_line_count() const { - return get_visible_line_count_in_range(0, text.size() - 1); + return text.get_total_visible_line_count(); } // Auto adjust. @@ -5929,7 +6030,7 @@ void TextEdit::adjust_viewport_to_caret(int p_caret) { // Get position of the end of caret. if (has_ime_text()) { - if (ime_selection.y != 0) { + if (ime_selection.y > 0) { caret_pos.y = _get_column_x_offset_for_line(get_caret_column(p_caret) + ime_selection.x + ime_selection.y, get_caret_line(p_caret), get_caret_column(p_caret)); } else { caret_pos.y = _get_column_x_offset_for_line(get_caret_column(p_caret) + ime_text.size(), get_caret_line(p_caret), get_caret_column(p_caret)); @@ -5981,7 +6082,7 @@ void TextEdit::center_viewport_to_caret(int p_caret) { // Get position of the end of caret. if (has_ime_text()) { - if (ime_selection.y != 0) { + if (ime_selection.y > 0) { caret_pos.y = _get_column_x_offset_for_line(get_caret_column(p_caret) + ime_selection.x + ime_selection.y, get_caret_line(p_caret), get_caret_column(p_caret)); } else { caret_pos.y = _get_column_x_offset_for_line(get_caret_column(p_caret) + ime_text.size(), get_caret_line(p_caret), get_caret_column(p_caret)); @@ -6326,7 +6427,7 @@ bool TextEdit::is_highlight_current_line_enabled() const { return highlight_current_line; } -void TextEdit::set_highlight_all_occurrences(const bool p_enabled) { +void TextEdit::set_highlight_all_occurrences(bool p_enabled) { if (highlight_all_occurrences == p_enabled) { return; } @@ -6744,6 +6845,9 @@ void TextEdit::_bind_methods() { ClassDB::bind_method(D_METHOD("set_fit_content_height_enabled", "enabled"), &TextEdit::set_fit_content_height_enabled); ClassDB::bind_method(D_METHOD("is_fit_content_height_enabled"), &TextEdit::is_fit_content_height_enabled); + ClassDB::bind_method(D_METHOD("set_fit_content_width_enabled", "enabled"), &TextEdit::set_fit_content_width_enabled); + ClassDB::bind_method(D_METHOD("is_fit_content_width_enabled"), &TextEdit::is_fit_content_width_enabled); + ClassDB::bind_method(D_METHOD("get_scroll_pos_for_line", "line", "wrap_index"), &TextEdit::get_scroll_pos_for_line, DEFVAL(0)); // Visible lines. @@ -6863,11 +6967,12 @@ void TextEdit::_bind_methods() { ADD_GROUP("Scroll", "scroll_"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "scroll_smooth"), "set_smooth_scroll_enabled", "is_smooth_scroll_enabled"); - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "scroll_v_scroll_speed", PROPERTY_HINT_NONE, "suffix:px/s"), "set_v_scroll_speed", "get_v_scroll_speed"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "scroll_v_scroll_speed", PROPERTY_HINT_NONE, "suffix:lines/s"), "set_v_scroll_speed", "get_v_scroll_speed"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "scroll_past_end_of_file"), "set_scroll_past_end_of_file_enabled", "is_scroll_past_end_of_file_enabled"); - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "scroll_vertical", PROPERTY_HINT_NONE, "suffix:px"), "set_v_scroll", "get_v_scroll"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "scroll_vertical", PROPERTY_HINT_NONE, "suffix:lines"), "set_v_scroll", "get_v_scroll"); ADD_PROPERTY(PropertyInfo(Variant::INT, "scroll_horizontal", PROPERTY_HINT_NONE, "suffix:px"), "set_h_scroll", "get_h_scroll"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "scroll_fit_content_height"), "set_fit_content_height_enabled", "is_fit_content_height_enabled"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "scroll_fit_content_width"), "set_fit_content_width_enabled", "is_fit_content_width_enabled"); ADD_GROUP("Minimap", "minimap_"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "minimap_draw"), "set_draw_minimap", "is_drawing_minimap"); @@ -7527,7 +7632,7 @@ int TextEdit::_get_column_x_offset_for_line(int p_char, int p_line, int p_column int row = 0; Vector<Vector2i> rows2 = text.get_line_wrap_ranges(p_line); for (int i = 0; i < rows2.size(); i++) { - if ((p_char >= rows2[i].x) && (p_char <= rows2[i].y)) { + if ((p_char >= rows2[i].x) && (p_char < rows2[i].y || (i == rows2.size() - 1 && p_char == rows2[i].y))) { row = i; break; } @@ -7840,10 +7945,10 @@ void TextEdit::_update_scrollbars() { h_scroll->set_begin(Point2(0, size.height - hmin.height)); h_scroll->set_end(Point2(size.width - vmin.width, size.height)); - bool draw_placeholder = text.size() == 1 && text[0].length() == 0; + bool draw_placeholder = _using_placeholder(); int visible_rows = get_visible_line_count(); - int total_rows = draw_placeholder ? placeholder_wraped_rows.size() - 1 : get_total_visible_line_count(); + int total_rows = draw_placeholder ? placeholder_wraped_rows.size() : get_total_visible_line_count(); if (scroll_past_end_of_file_enabled && !fit_content_height) { total_rows += visible_rows - 1; } @@ -7855,8 +7960,8 @@ void TextEdit::_update_scrollbars() { total_width += minimap_width; } - content_height_cache = MAX(total_rows, 1) * get_line_height(); - if (fit_content_height) { + content_size_cache = Vector2i(total_width + 10, MAX(total_rows, 1) * get_line_height()); + if (fit_content_height || fit_content_width) { update_minimum_size(); } @@ -7921,7 +8026,7 @@ void TextEdit::_scroll_moved(double p_to_val) { } if (v_scroll->is_visible_in_tree()) { // Set line ofs and wrap ofs. - bool draw_placeholder = text.size() == 1 && text[0].length() == 0; + bool draw_placeholder = _using_placeholder(); int v_scroll_i = floor(get_v_scroll()); int sc = 0; @@ -8063,7 +8168,7 @@ void TextEdit::_update_minimap_hover() { const Point2 mp = get_local_mouse_pos(); const int xmargin_end = get_size().width - theme_cache.style_normal->get_margin(SIDE_RIGHT); - const bool hovering_sidebar = mp.x > xmargin_end - minimap_width && mp.x < xmargin_end; + bool hovering_sidebar = mp.x > xmargin_end - minimap_width && mp.x < xmargin_end; if (!hovering_sidebar) { if (hovering_minimap) { // Only redraw if the hovering status changed. @@ -8077,7 +8182,7 @@ void TextEdit::_update_minimap_hover() { const int row = get_minimap_line_at_pos(mp); - const bool new_hovering_minimap = row >= get_first_visible_line() && row <= get_last_full_visible_line(); + bool new_hovering_minimap = row >= get_first_visible_line() && row <= get_last_full_visible_line(); if (new_hovering_minimap != hovering_minimap) { // Only redraw if the hovering status changed. hovering_minimap = new_hovering_minimap; @@ -8147,8 +8252,36 @@ void TextEdit::_update_gutter_width() { } /* Syntax highlighting. */ -Dictionary TextEdit::_get_line_syntax_highlighting(int p_line) { - return (syntax_highlighter.is_null() || setting_text) ? Dictionary() : syntax_highlighter->get_line_syntax_highlighting(p_line); +Vector<Pair<int64_t, Color>> TextEdit::_get_line_syntax_highlighting(int p_line) { + if (syntax_highlighter.is_null() || setting_text) { + return Vector<Pair<int64_t, Color>>(); + } + + HashMap<int, Vector<Pair<int64_t, Color>>>::Iterator E = syntax_highlighting_cache.find(p_line); + if (E) { + return E->value; + } + + Dictionary color_map = syntax_highlighter->get_line_syntax_highlighting(p_line); + Vector<Pair<int64_t, Color>> result; + result.resize(color_map.size()); + int i = 0; + for (const Variant *key = color_map.next(nullptr); key; key = color_map.next(key), i++) { + int64_t key_data = *key; + const Variant *color_data = color_map.getptr(*key); + Color color_value = editable ? theme_cache.font_color : theme_cache.font_readonly_color; + if (color_data != nullptr) { + color_value = (color_data->operator Dictionary()).get("color", color_value); + } + result.write[i] = Pair<int64_t, Color>(key_data, color_value); + } + syntax_highlighting_cache.insert(p_line, result); + + return result; +} + +void TextEdit::_clear_syntax_highlighting_cache() { + syntax_highlighting_cache.clear(); } /* Deprecated. */ @@ -8174,6 +8307,7 @@ int TextEdit::get_selection_column(int p_caret) const { /*** Super internal Core API. Everything builds on it. ***/ void TextEdit::_text_changed() { + _clear_syntax_highlighting_cache(); _cancel_drag_and_drop_text(); queue_redraw(); diff --git a/scene/gui/text_edit.h b/scene/gui/text_edit.h index 4d9d169c1c..c5f838020b 100644 --- a/scene/gui/text_edit.h +++ b/scene/gui/text_edit.h @@ -153,6 +153,7 @@ private: Color background_color = Color(0, 0, 0, 0); bool hidden = false; + int line_count = 0; int height = 0; int width = 0; @@ -178,16 +179,19 @@ private: bool use_default_word_separators = true; bool use_custom_word_separators = false; - int line_height = -1; - int max_width = -1; + mutable bool max_line_width_dirty = true; + mutable bool max_line_height_dirty = true; + mutable int max_line_width = 0; + mutable int max_line_height = 0; + mutable int total_visible_line_count = 0; int width = -1; int tab_size = 4; int gutter_count = 0; bool indent_wrapped_lines = false; - void _calculate_line_height(); - void _calculate_max_line_width(); + void _calculate_line_height() const; + void _calculate_max_line_width() const; public: void set_tab_size(int p_tab_size); @@ -203,6 +207,7 @@ private: int get_line_height() const; int get_line_width(int p_line, int p_wrap_index = -1) const; int get_max_width() const; + int get_total_visible_line_count() const; void set_use_default_word_separators(bool p_enabled); bool is_default_word_separators_enabled() const; @@ -226,18 +231,8 @@ private: const Ref<TextParagraph> get_line_data(int p_line) const; void set(int p_line, const String &p_text, const Array &p_bidi_override); - void set_hidden(int p_line, bool p_hidden) { - if (text[p_line].hidden == p_hidden) { - return; - } - text.write[p_line].hidden = p_hidden; - if (!p_hidden && text[p_line].width > max_width) { - max_width = text[p_line].width; - } else if (p_hidden && text[p_line].width == max_width) { - _calculate_max_line_width(); - } - } - bool is_hidden(int p_line) const { return text[p_line].hidden; } + void set_hidden(int p_line, bool p_hidden); + bool is_hidden(int p_line) const; void insert(int p_at, const Vector<String> &p_text, const Vector<Array> &p_bidi_override); void remove_range(int p_from_line, int p_to_line); int size() const { return text.size(); } @@ -248,7 +243,7 @@ private: void invalidate_all(); void invalidate_all_lines(); - _FORCE_INLINE_ const String &operator[](int p_line) const; + _FORCE_INLINE_ String operator[](int p_line) const; /* Gutters. */ void add_gutter(int p_at); @@ -281,6 +276,7 @@ private: bool setting_text = false; bool alt_start = false; + bool alt_start_no_hold = false; uint32_t alt_code = 0; // Text properties. @@ -297,6 +293,7 @@ private: Vector<String> placeholder_wraped_rows; void _update_placeholder(); + bool _using_placeholder() const; /* Initialize to opposite first, so we get past the early-out in set_editable. */ bool editable = false; @@ -452,6 +449,7 @@ private: void _caret_changed(int p_caret = -1); void _emit_caret_changed(); + void _show_virtual_keyboard(); void _reset_caret_blink_timer(); void _toggle_draw_caret(); @@ -504,8 +502,9 @@ private: HScrollBar *h_scroll = nullptr; VScrollBar *v_scroll = nullptr; - float content_height_cache = 0.0; + Vector2i content_size_cache; bool fit_content_height = false; + bool fit_content_width = false; bool scroll_past_end_of_file_enabled = false; // Smooth scrolling. @@ -566,8 +565,10 @@ private: /* Syntax highlighting. */ Ref<SyntaxHighlighter> syntax_highlighter; + HashMap<int, Vector<Pair<int64_t, Color>>> syntax_highlighting_cache; - Dictionary _get_line_syntax_highlighting(int p_line); + Vector<Pair<int64_t, Color>> _get_line_syntax_highlighting(int p_line); + void _clear_syntax_highlighting_cache(); /* Visual. */ struct ThemeCache { @@ -733,7 +734,7 @@ public: void cancel_ime(); void apply_ime(); - void set_editable(const bool p_editable); + void set_editable(bool p_editable); bool is_editable() const; void set_text_direction(TextDirection p_text_direction); @@ -754,7 +755,7 @@ public: bool is_indent_wrapped_lines() const; // User controls - void set_overtype_mode_enabled(const bool p_enabled); + void set_overtype_mode_enabled(bool p_enabled); bool is_overtype_mode_enabled() const; void set_context_menu_enabled(bool p_enabled); @@ -861,7 +862,7 @@ public: void set_caret_type(CaretType p_type); CaretType get_caret_type() const; - void set_caret_blink_enabled(const bool p_enabled); + void set_caret_blink_enabled(bool p_enabled); bool is_caret_blink_enabled() const; void set_caret_blink_interval(const float p_interval); @@ -870,10 +871,10 @@ public: void set_draw_caret_when_editable_disabled(bool p_enable); bool is_drawing_caret_when_editable_disabled() const; - void set_move_caret_on_right_click_enabled(const bool p_enabled); + void set_move_caret_on_right_click_enabled(bool p_enabled); bool is_move_caret_on_right_click_enabled() const; - void set_caret_mid_grapheme_enabled(const bool p_enabled); + void set_caret_mid_grapheme_enabled(bool p_enabled); bool is_caret_mid_grapheme_enabled() const; void set_multiple_carets_enabled(bool p_enabled); @@ -909,13 +910,13 @@ public: String get_word_under_caret(int p_caret = -1) const; /* Selection. */ - void set_selecting_enabled(const bool p_enabled); + void set_selecting_enabled(bool p_enabled); bool is_selecting_enabled() const; - void set_deselect_on_focus_loss_enabled(const bool p_enabled); + void set_deselect_on_focus_loss_enabled(bool p_enabled); bool is_deselect_on_focus_loss_enabled() const; - void set_drag_and_drop_selection_enabled(const bool p_enabled); + void set_drag_and_drop_selection_enabled(bool p_enabled); bool is_drag_and_drop_selection_enabled() const; void set_selection_mode(SelectionMode p_mode); @@ -964,10 +965,10 @@ public: /* Viewport. */ // Scrolling. - void set_smooth_scroll_enabled(const bool p_enabled); + void set_smooth_scroll_enabled(bool p_enabled); bool is_smooth_scroll_enabled() const; - void set_scroll_past_end_of_file_enabled(const bool p_enabled); + void set_scroll_past_end_of_file_enabled(bool p_enabled); bool is_scroll_past_end_of_file_enabled() const; VScrollBar *get_v_scroll_bar() const; @@ -982,9 +983,12 @@ public: void set_v_scroll_speed(float p_speed); float get_v_scroll_speed() const; - void set_fit_content_height_enabled(const bool p_enabled); + void set_fit_content_height_enabled(bool p_enabled); bool is_fit_content_height_enabled() const; + void set_fit_content_width_enabled(bool p_enabled); + bool is_fit_content_width_enabled() const; + double get_scroll_pos_for_line(int p_line, int p_wrap_index = 0) const; // Visible lines. @@ -1018,6 +1022,7 @@ public: void add_gutter(int p_at = -1); void remove_gutter(int p_gutter); int get_gutter_count() const; + Vector2i get_hovered_gutter() const { return hovered_gutter; } void set_gutter_name(int p_gutter, const String &p_name); String get_gutter_name(int p_gutter) const; @@ -1070,7 +1075,7 @@ public: void set_highlight_current_line(bool p_enabled); bool is_highlight_current_line_enabled() const; - void set_highlight_all_occurrences(const bool p_enabled); + void set_highlight_all_occurrences(bool p_enabled); bool is_highlight_all_occurrences_enabled() const; void set_draw_control_chars(bool p_enabled); diff --git a/scene/gui/tree.cpp b/scene/gui/tree.cpp index 46fcdcf7f6..e4f52ee8ee 100644 --- a/scene/gui/tree.cpp +++ b/scene/gui/tree.cpp @@ -62,8 +62,15 @@ void TreeItem::Cell::draw_icon(const RID &p_where, const Point2 &p_pos, const Si if (icon_region == Rect2i()) { icon->draw_rect_region(p_where, Rect2(p_pos, dsize), Rect2(Point2(), icon->get_size()), p_color); + if (icon_overlay.is_valid()) { + Vector2 offset = icon->get_size() - icon_overlay->get_size(); + icon_overlay->draw_rect_region(p_where, Rect2(p_pos + offset, dsize), Rect2(Point2(), icon_overlay->get_size()), p_color); + } } else { icon->draw_rect_region(p_where, Rect2(p_pos, dsize), icon_region, p_color); + if (icon_overlay.is_valid()) { + icon_overlay->draw_rect_region(p_where, Rect2(p_pos, dsize), icon_region, p_color); + } } } @@ -477,6 +484,24 @@ Ref<Texture2D> TreeItem::get_icon(int p_column) const { return cells[p_column].icon; } +void TreeItem::set_icon_overlay(int p_column, const Ref<Texture2D> &p_icon_overlay) { + ERR_FAIL_INDEX(p_column, cells.size()); + + if (cells[p_column].icon_overlay == p_icon_overlay) { + return; + } + + cells.write[p_column].icon_overlay = p_icon_overlay; + cells.write[p_column].cached_minimum_size_dirty = true; + + _changed_notify(p_column); +} + +Ref<Texture2D> TreeItem::get_icon_overlay(int p_column) const { + ERR_FAIL_INDEX_V(p_column, cells.size(), Ref<Texture2D>()); + return cells[p_column].icon_overlay; +} + void TreeItem::set_icon_region(int p_column, const Rect2 &p_icon_region) { ERR_FAIL_INDEX(p_column, cells.size()); @@ -773,17 +798,21 @@ TreeItem *TreeItem::create_child(int p_index) { TreeItem *item_prev = nullptr; TreeItem *item_next = first_child; - int idx = 0; - while (item_next) { - if (idx == p_index) { - item_next->prev = ti; - ti->next = item_next; - break; - } + if (p_index < 0 && last_child) { + item_prev = last_child; + } else { + int idx = 0; + while (item_next) { + if (idx == p_index) { + item_next->prev = ti; + ti->next = item_next; + break; + } - item_prev = item_next; - item_next = item_next->next; - idx++; + item_prev = item_next; + item_next = item_next->next; + idx++; + } } if (item_prev) { @@ -804,6 +833,10 @@ TreeItem *TreeItem::create_child(int p_index) { } } + if (item_prev == last_child) { + last_child = ti; + } + ti->parent = this; ti->parent_visible_in_tree = is_visible_in_tree(); @@ -820,17 +853,13 @@ void TreeItem::add_child(TreeItem *p_item) { p_item->parent_visible_in_tree = is_visible_in_tree(); p_item->_handle_visibility_changed(p_item->parent_visible_in_tree); - TreeItem *item_prev = first_child; - while (item_prev && item_prev->next) { - item_prev = item_prev->next; - } - - if (item_prev) { - item_prev->next = p_item; - p_item->prev = item_prev; + if (last_child) { + last_child->next = p_item; + p_item->prev = last_child; } else { first_child = p_item; } + last_child = p_item; if (!children_cache.is_empty()) { children_cache.append(p_item); @@ -910,13 +939,8 @@ TreeItem *TreeItem::_get_prev_in_tree(bool p_wrap, bool p_include_invisible) { } } else { current = prev_item; - while ((!current->collapsed || p_include_invisible) && current->first_child) { - //go to the very end - - current = current->first_child; - while (current->next) { - current = current->next; - } + while ((!current->collapsed || p_include_invisible) && current->last_child) { + current = current->last_child; } } @@ -1037,6 +1061,8 @@ void TreeItem::clear_children() { } first_child = nullptr; + last_child = nullptr; + children_cache.clear(); }; int TreeItem::get_index() { @@ -1141,6 +1167,7 @@ void TreeItem::move_after(TreeItem *p_item) { if (next) { parent->children_cache.clear(); } else { + parent->last_child = this; // If the cache is empty, it has not been built but there // are items in the tree (note p_item != nullptr,) so we cannot update it. if (!parent->children_cache.is_empty()) { @@ -1562,7 +1589,7 @@ void TreeItem::_call_recursive_bind(const Variant **p_args, int p_argcount, Call return; } - if (p_args[0]->get_type() != Variant::STRING && p_args[0]->get_type() != Variant::STRING_NAME) { + if (!p_args[0]->is_string()) { r_error.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT; r_error.argument = 0; r_error.expected = Variant::STRING_NAME; @@ -1631,6 +1658,9 @@ void TreeItem::_bind_methods() { ClassDB::bind_method(D_METHOD("set_icon", "column", "texture"), &TreeItem::set_icon); ClassDB::bind_method(D_METHOD("get_icon", "column"), &TreeItem::get_icon); + ClassDB::bind_method(D_METHOD("set_icon_overlay", "column", "texture"), &TreeItem::set_icon_overlay); + ClassDB::bind_method(D_METHOD("get_icon_overlay", "column"), &TreeItem::get_icon_overlay); + ClassDB::bind_method(D_METHOD("set_icon_region", "column", "region"), &TreeItem::set_icon_region); ClassDB::bind_method(D_METHOD("get_icon_region", "column"), &TreeItem::get_icon_region); @@ -3272,12 +3302,10 @@ void Tree::value_editor_changed(double p_value) { return; } - TreeItem::Cell &c = popup_edited_item->cells.write[popup_edited_item_col]; - c.val = p_value; + const TreeItem::Cell &c = popup_edited_item->cells[popup_edited_item_col]; - line_editor->set_text(String::num(c.val, Math::range_step_decimals(c.step))); + line_editor->set_text(String::num(p_value, Math::range_step_decimals(c.step))); - item_edited(popup_edited_item_col, popup_edited_item); queue_redraw(); } @@ -3466,29 +3494,37 @@ void Tree::gui_input(const Ref<InputEvent> &p_event) { accept_event(); } - if (!selected_item || select_mode == SELECT_ROW || selected_col > (columns.size() - 1)) { + if (!selected_item || selected_col > (columns.size() - 1)) { return; } + if (k.is_valid() && k->is_shift_pressed()) { selected_item->set_collapsed_recursive(false); - } else { + } else if (select_mode != SELECT_ROW) { _go_right(); + } else if (selected_item->get_first_child() != nullptr && selected_item->is_collapsed()) { + selected_item->set_collapsed(false); + } else { + _go_down(); } } else if (p_event->is_action("ui_left") && p_event->is_pressed()) { if (!cursor_can_exit_tree) { accept_event(); } - if (!selected_item || select_mode == SELECT_ROW || selected_col < 0) { + if (!selected_item || selected_col < 0) { return; } if (k.is_valid() && k->is_shift_pressed()) { selected_item->set_collapsed_recursive(true); - } else { + } else if (select_mode != SELECT_ROW) { _go_left(); + } else if (selected_item->get_first_child() != nullptr && !selected_item->is_collapsed()) { + selected_item->set_collapsed(true); + } else { + _go_up(); } - } else if (p_event->is_action("ui_up") && p_event->is_pressed() && !is_command) { if (!cursor_can_exit_tree) { accept_event(); @@ -4468,15 +4504,8 @@ TreeItem *Tree::get_root() const { TreeItem *Tree::get_last_item() const { TreeItem *last = root; - - while (last) { - if (last->next) { - last = last->next; - } else if (last->first_child && !last->collapsed) { - last = last->first_child; - } else { - break; - } + while (last && last->last_child && !last->collapsed) { + last = last->last_child; } return last; @@ -4495,9 +4524,16 @@ void Tree::item_edited(int p_column, TreeItem *p_item, MouseButton p_custom_mous } void Tree::item_changed(int p_column, TreeItem *p_item) { - if (p_item != nullptr && p_column >= 0 && p_column < p_item->cells.size()) { - p_item->cells.write[p_column].dirty = true; - columns.write[p_column].cached_minimum_width_dirty = true; + if (p_item != nullptr) { + if (p_column >= 0 && p_column < p_item->cells.size()) { + p_item->cells.write[p_column].dirty = true; + columns.write[p_column].cached_minimum_width_dirty = true; + } else if (p_column == -1) { + for (int i = 0; i < p_item->cells.size(); i++) { + p_item->cells.write[i].dirty = true; + columns.write[i].cached_minimum_width_dirty = true; + } + } } queue_redraw(); } diff --git a/scene/gui/tree.h b/scene/gui/tree.h index 3200459b5a..17ea31a733 100644 --- a/scene/gui/tree.h +++ b/scene/gui/tree.h @@ -60,6 +60,7 @@ private: TreeCellMode mode = TreeItem::CELL_MODE_STRING; Ref<Texture2D> icon; + Ref<Texture2D> icon_overlay; Rect2i icon_region; String text; String xl_text; @@ -136,6 +137,7 @@ private: TreeItem *prev = nullptr; // previous in list TreeItem *next = nullptr; // next in list TreeItem *first_child = nullptr; + TreeItem *last_child = nullptr; Vector<TreeItem *> children_cache; bool is_root = false; // for tree root @@ -177,6 +179,9 @@ private: if (parent->first_child == this) { parent->first_child = next; } + if (parent->last_child == this) { + parent->last_child = prev; + } } } @@ -253,6 +258,9 @@ public: void set_icon(int p_column, const Ref<Texture2D> &p_icon); Ref<Texture2D> get_icon(int p_column) const; + void set_icon_overlay(int p_column, const Ref<Texture2D> &p_icon_overlay); + Ref<Texture2D> get_icon_overlay(int p_column) const; + void set_icon_region(int p_column, const Rect2 &p_icon_region); Rect2 get_icon_region(int p_column) const; |