summaryrefslogtreecommitdiffstats
path: root/scene/gui
diff options
context:
space:
mode:
Diffstat (limited to 'scene/gui')
-rw-r--r--scene/gui/base_button.cpp12
-rw-r--r--scene/gui/code_edit.cpp108
-rw-r--r--scene/gui/code_edit.h4
-rw-r--r--scene/gui/color_picker.cpp26
-rw-r--r--scene/gui/control.compat.inc48
-rw-r--r--scene/gui/control.cpp92
-rw-r--r--scene/gui/control.h4
-rw-r--r--scene/gui/file_dialog.cpp10
-rw-r--r--scene/gui/graph_edit.cpp50
-rw-r--r--scene/gui/line_edit.cpp730
-rw-r--r--scene/gui/line_edit.h14
-rw-r--r--scene/gui/menu_bar.cpp25
-rw-r--r--scene/gui/menu_button.cpp5
-rw-r--r--scene/gui/option_button.cpp2
-rw-r--r--scene/gui/popup_menu.cpp14
-rw-r--r--scene/gui/popup_menu.h2
-rw-r--r--scene/gui/range.cpp2
-rw-r--r--scene/gui/rich_text_label.cpp947
-rw-r--r--scene/gui/rich_text_label.h16
-rw-r--r--scene/gui/scroll_container.cpp28
-rw-r--r--scene/gui/slider.cpp2
-rw-r--r--scene/gui/spin_box.cpp280
-rw-r--r--scene/gui/spin_box.h71
-rw-r--r--scene/gui/split_container.cpp244
-rw-r--r--scene/gui/split_container.h31
-rw-r--r--scene/gui/subviewport_container.cpp2
-rw-r--r--scene/gui/tab_bar.cpp2
-rw-r--r--scene/gui/text_edit.cpp714
-rw-r--r--scene/gui/text_edit.h67
-rw-r--r--scene/gui/tree.cpp130
-rw-r--r--scene/gui/tree.h8
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;