summaryrefslogtreecommitdiff
path: root/scene/gui
diff options
context:
space:
mode:
Diffstat (limited to 'scene/gui')
-rw-r--r--scene/gui/aspect_ratio_container.cpp2
-rw-r--r--scene/gui/base_button.cpp32
-rw-r--r--scene/gui/base_button.h3
-rw-r--r--scene/gui/box_container.cpp1
-rw-r--r--scene/gui/button.cpp141
-rw-r--r--scene/gui/button.h4
-rw-r--r--scene/gui/code_edit.cpp2186
-rw-r--r--scene/gui/code_edit.h225
-rw-r--r--scene/gui/color_picker.cpp520
-rw-r--r--scene/gui/color_picker.h66
-rw-r--r--scene/gui/container.cpp12
-rw-r--r--scene/gui/container.h2
-rw-r--r--scene/gui/control.cpp697
-rw-r--r--scene/gui/control.h67
-rw-r--r--scene/gui/dialogs.cpp33
-rw-r--r--scene/gui/dialogs.h5
-rw-r--r--scene/gui/file_dialog.cpp18
-rw-r--r--scene/gui/file_dialog.h6
-rw-r--r--scene/gui/gradient_edit.cpp12
-rw-r--r--scene/gui/graph_edit.cpp193
-rw-r--r--scene/gui/graph_edit.h31
-rw-r--r--scene/gui/graph_node.cpp84
-rw-r--r--scene/gui/graph_node.h12
-rw-r--r--scene/gui/grid_container.cpp2
-rw-r--r--scene/gui/item_list.cpp19
-rw-r--r--scene/gui/label.cpp131
-rw-r--r--scene/gui/label.h27
-rw-r--r--scene/gui/line_edit.cpp399
-rw-r--r--scene/gui/line_edit.h50
-rw-r--r--scene/gui/link_button.cpp4
-rw-r--r--scene/gui/menu_button.cpp2
-rw-r--r--scene/gui/nine_patch_rect.cpp3
-rw-r--r--scene/gui/popup_menu.cpp10
-rw-r--r--scene/gui/popup_menu.h4
-rw-r--r--scene/gui/range.cpp18
-rw-r--r--scene/gui/range.h2
-rw-r--r--scene/gui/rich_text_effect.h4
-rw-r--r--scene/gui/rich_text_label.cpp346
-rw-r--r--scene/gui/rich_text_label.h25
-rw-r--r--scene/gui/scroll_bar.cpp2
-rw-r--r--scene/gui/scroll_container.cpp179
-rw-r--r--scene/gui/scroll_container.h29
-rw-r--r--scene/gui/shortcut.cpp2
-rw-r--r--scene/gui/slider.cpp4
-rw-r--r--scene/gui/spin_box.cpp14
-rw-r--r--scene/gui/spin_box.h2
-rw-r--r--scene/gui/split_container.cpp6
-rw-r--r--scene/gui/subviewport_container.cpp4
-rw-r--r--scene/gui/tab_container.cpp5
-rw-r--r--scene/gui/tabs.cpp68
-rw-r--r--scene/gui/tabs.h4
-rw-r--r--scene/gui/text_edit.cpp1738
-rw-r--r--scene/gui/text_edit.h125
-rw-r--r--scene/gui/texture_button.cpp3
-rw-r--r--scene/gui/texture_progress_bar.cpp140
-rw-r--r--scene/gui/texture_progress_bar.h3
-rw-r--r--scene/gui/tree.cpp1006
-rw-r--r--scene/gui/tree.h151
-rw-r--r--scene/gui/video_player.cpp4
59 files changed, 5844 insertions, 3043 deletions
diff --git a/scene/gui/aspect_ratio_container.cpp b/scene/gui/aspect_ratio_container.cpp
index c7f6c0e2da..fb6fa9dec9 100644
--- a/scene/gui/aspect_ratio_container.cpp
+++ b/scene/gui/aspect_ratio_container.cpp
@@ -155,7 +155,7 @@ void AspectRatioContainer::_bind_methods() {
ClassDB::bind_method(D_METHOD("get_alignment_vertical"), &AspectRatioContainer::get_alignment_vertical);
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "ratio"), "set_ratio", "get_ratio");
- ADD_PROPERTY(PropertyInfo(Variant::INT, "stretch_mode", PROPERTY_HINT_ENUM, "Width controls height,Height controls width,Fit,Cover"), "set_stretch_mode", "get_stretch_mode");
+ ADD_PROPERTY(PropertyInfo(Variant::INT, "stretch_mode", PROPERTY_HINT_ENUM, "Width Controls Height,Height Controls Width,Fit,Cover"), "set_stretch_mode", "get_stretch_mode");
ADD_GROUP("Alignment", "alignment_");
ADD_PROPERTY(PropertyInfo(Variant::INT, "alignment_horizontal", PROPERTY_HINT_ENUM, "Begin,Center,End"), "set_alignment_horizontal", "get_alignment_horizontal");
diff --git a/scene/gui/base_button.cpp b/scene/gui/base_button.cpp
index db13b9b11f..8414c4dc78 100644
--- a/scene/gui/base_button.cpp
+++ b/scene/gui/base_button.cpp
@@ -53,6 +53,8 @@ void BaseButton::_unpress_group() {
}
void BaseButton::_gui_input(Ref<InputEvent> p_event) {
+ ERR_FAIL_COND(p_event.is_null());
+
if (status.disabled) { // no interaction with disabled button
return;
}
@@ -96,17 +98,14 @@ void BaseButton::_notification(int p_what) {
}
if (p_what == NOTIFICATION_FOCUS_ENTER) {
- status.hovering = true;
update();
}
if (p_what == NOTIFICATION_FOCUS_EXIT) {
if (status.press_attempt) {
status.press_attempt = false;
- status.hovering = false;
update();
} else if (status.hovering) {
- status.hovering = false;
update();
}
}
@@ -153,6 +152,9 @@ void BaseButton::on_action_event(Ref<InputEvent> p_event) {
}
status.pressed = !status.pressed;
_unpress_group();
+ if (button_group.is_valid()) {
+ button_group->emit_signal("pressed", this);
+ }
_toggled(status.pressed);
_pressed();
}
@@ -170,10 +172,9 @@ void BaseButton::on_action_event(Ref<InputEvent> p_event) {
status.hovering = false;
}
}
- // pressed state should be correct with button_up signal
- emit_signal("button_up");
status.press_attempt = false;
status.pressing_inside = false;
+ emit_signal("button_up");
}
update();
@@ -216,12 +217,27 @@ void BaseButton::set_pressed(bool p_pressed) {
if (p_pressed) {
_unpress_group();
+ if (button_group.is_valid()) {
+ button_group->emit_signal("pressed", this);
+ }
}
_toggled(status.pressed);
update();
}
+void BaseButton::set_pressed_no_signal(bool p_pressed) {
+ if (!toggle_mode) {
+ return;
+ }
+ if (status.pressed == p_pressed) {
+ return;
+ }
+ status.pressed = p_pressed;
+
+ update();
+}
+
bool BaseButton::is_pressing() const {
return status.press_attempt;
}
@@ -323,6 +339,8 @@ Ref<Shortcut> BaseButton::get_shortcut() const {
}
void BaseButton::_unhandled_key_input(Ref<InputEvent> p_event) {
+ ERR_FAIL_COND(p_event.is_null());
+
if (!_is_focus_owner_in_shorcut_context()) {
return;
}
@@ -385,7 +403,7 @@ bool BaseButton::_is_focus_owner_in_shorcut_context() const {
Control *vp_focus = get_focus_owner();
// If the context is valid and the viewport focus is valid, check if the context is the focus or is a parent of it.
- return ctx_node && vp_focus && (ctx_node == vp_focus || ctx_node->is_a_parent_of(vp_focus));
+ return ctx_node && vp_focus && (ctx_node == vp_focus || ctx_node->is_ancestor_of(vp_focus));
}
void BaseButton::_bind_methods() {
@@ -393,6 +411,7 @@ void BaseButton::_bind_methods() {
ClassDB::bind_method(D_METHOD("_unhandled_key_input"), &BaseButton::_unhandled_key_input);
ClassDB::bind_method(D_METHOD("set_pressed", "pressed"), &BaseButton::set_pressed);
ClassDB::bind_method(D_METHOD("is_pressed"), &BaseButton::is_pressed);
+ ClassDB::bind_method(D_METHOD("set_pressed_no_signal", "pressed"), &BaseButton::set_pressed_no_signal);
ClassDB::bind_method(D_METHOD("is_hovered"), &BaseButton::is_hovered);
ClassDB::bind_method(D_METHOD("set_toggle_mode", "enabled"), &BaseButton::set_toggle_mode);
ClassDB::bind_method(D_METHOD("is_toggle_mode"), &BaseButton::is_toggle_mode);
@@ -483,6 +502,7 @@ BaseButton *ButtonGroup::get_pressed_button() {
void ButtonGroup::_bind_methods() {
ClassDB::bind_method(D_METHOD("get_pressed_button"), &ButtonGroup::get_pressed_button);
ClassDB::bind_method(D_METHOD("get_buttons"), &ButtonGroup::_get_buttons);
+ ADD_SIGNAL(MethodInfo("pressed", PropertyInfo(Variant::OBJECT, "button")));
}
ButtonGroup::ButtonGroup() {
diff --git a/scene/gui/base_button.h b/scene/gui/base_button.h
index 6c7a8f3433..d86b35daf0 100644
--- a/scene/gui/base_button.h
+++ b/scene/gui/base_button.h
@@ -98,7 +98,8 @@ public:
bool is_pressing() const; ///< return whether button is pressed (toggled in)
bool is_hovered() const;
- void set_pressed(bool p_pressed); ///only works in toggle mode
+ void set_pressed(bool p_pressed); // Only works in toggle mode.
+ void set_pressed_no_signal(bool p_pressed);
void set_toggle_mode(bool p_on);
bool is_toggle_mode() const;
diff --git a/scene/gui/box_container.cpp b/scene/gui/box_container.cpp
index 7407ad5b8f..40a49dbb58 100644
--- a/scene/gui/box_container.cpp
+++ b/scene/gui/box_container.cpp
@@ -349,6 +349,7 @@ void BoxContainer::_bind_methods() {
MarginContainer *VBoxContainer::add_margin_child(const String &p_label, Control *p_control, bool p_expand) {
Label *l = memnew(Label);
+ l->set_theme_type_variation("HeaderSmall");
l->set_text(p_label);
add_child(l);
MarginContainer *mc = memnew(MarginContainer);
diff --git a/scene/gui/button.cpp b/scene/gui/button.cpp
index b0bcde8865..bcc273114b 100644
--- a/scene/gui/button.cpp
+++ b/scene/gui/button.cpp
@@ -49,9 +49,14 @@ Size2 Button::get_minimum_size() const {
if (!_icon.is_null()) {
minsize.height = MAX(minsize.height, _icon->get_height());
- minsize.width += _icon->get_width();
- if (xl_text != "") {
- minsize.width += get_theme_constant("hseparation");
+
+ if (icon_align != ALIGN_CENTER) {
+ minsize.width += _icon->get_width();
+ if (xl_text != "") {
+ minsize.width += get_theme_constant("hseparation");
+ }
+ } else {
+ minsize.width = MAX(minsize.width, _icon->get_width());
}
}
}
@@ -202,6 +207,21 @@ void Button::_notification(int p_what) {
}
Rect2 icon_region = Rect2();
+ TextAlign icon_align_rtl_checked = icon_align;
+ TextAlign align_rtl_checked = align;
+ // Swap icon and text alignment sides if right-to-left layout is set.
+ if (rtl) {
+ if (icon_align == ALIGN_RIGHT) {
+ icon_align_rtl_checked = ALIGN_LEFT;
+ } else if (icon_align == ALIGN_LEFT) {
+ icon_align_rtl_checked = ALIGN_RIGHT;
+ }
+ if (align == ALIGN_RIGHT) {
+ align_rtl_checked = ALIGN_LEFT;
+ } else if (align == ALIGN_LEFT) {
+ align_rtl_checked = ALIGN_RIGHT;
+ }
+ }
if (!_icon.is_null()) {
int valign = size.height - style->get_minimum_size().y;
if (is_disabled()) {
@@ -209,20 +229,27 @@ void Button::_notification(int p_what) {
}
float icon_ofs_region = 0.0;
- if (rtl) {
- if (_internal_margin[SIDE_RIGHT] > 0) {
- icon_ofs_region = _internal_margin[SIDE_RIGHT] + get_theme_constant("hseparation");
- }
- } else {
+ Point2 style_offset;
+ Size2 icon_size = _icon->get_size();
+ if (icon_align_rtl_checked == ALIGN_LEFT) {
+ style_offset.x = style->get_margin(SIDE_LEFT);
if (_internal_margin[SIDE_LEFT] > 0) {
icon_ofs_region = _internal_margin[SIDE_LEFT] + get_theme_constant("hseparation");
}
+ } else if (icon_align_rtl_checked == ALIGN_CENTER) {
+ style_offset.x = 0.0;
+ } else if (icon_align_rtl_checked == ALIGN_RIGHT) {
+ style_offset.x = -style->get_margin(SIDE_RIGHT);
+ if (_internal_margin[SIDE_RIGHT] > 0) {
+ icon_ofs_region = -_internal_margin[SIDE_RIGHT] - get_theme_constant("hseparation");
+ }
}
+ style_offset.y = style->get_margin(SIDE_TOP);
if (expand_icon) {
Size2 _size = get_size() - style->get_offset() * 2;
_size.width -= get_theme_constant("hseparation") + icon_ofs_region;
- if (!clip_text) {
+ if (!clip_text && icon_align_rtl_checked != ALIGN_CENTER) {
_size.width -= text_buf->get_size().width;
}
float icon_width = _icon->get_width() * _size.height / _icon->get_height();
@@ -233,21 +260,26 @@ void Button::_notification(int p_what) {
icon_height = _icon->get_height() * icon_width / _icon->get_width();
}
- if (rtl) {
- icon_region = Rect2(Point2(size.width - (icon_ofs_region + icon_width + style->get_margin(SIDE_RIGHT)), style->get_margin(SIDE_TOP) + (_size.height - icon_height) / 2), Size2(icon_width, icon_height));
- } else {
- icon_region = Rect2(style->get_offset() + Point2(icon_ofs_region, (_size.height - icon_height) / 2), Size2(icon_width, icon_height));
- }
+ icon_size = Size2(icon_width, icon_height);
+ }
+
+ if (icon_align_rtl_checked == ALIGN_LEFT) {
+ icon_region = Rect2(style_offset + Point2(icon_ofs_region, Math::floor((valign - icon_size.y) * 0.5)), icon_size);
+ } else if (icon_align_rtl_checked == ALIGN_CENTER) {
+ icon_region = Rect2(style_offset + Point2(icon_ofs_region + Math::floor((size.x - icon_size.x) * 0.5), Math::floor((valign - icon_size.y) * 0.5)), icon_size);
} else {
- if (rtl) {
- icon_region = Rect2(Point2(size.width - (icon_ofs_region + _icon->get_size().width + style->get_margin(SIDE_RIGHT)), style->get_margin(SIDE_TOP) + Math::floor((valign - _icon->get_height()) / 2.0)), _icon->get_size());
- } else {
- icon_region = Rect2(style->get_offset() + Point2(icon_ofs_region, Math::floor((valign - _icon->get_height()) / 2.0)), _icon->get_size());
- }
+ icon_region = Rect2(style_offset + Point2(icon_ofs_region + size.x - icon_size.x, Math::floor((valign - icon_size.y) * 0.5)), icon_size);
+ }
+
+ if (icon_region.size.width > 0) {
+ draw_texture_rect_region(_icon, icon_region, Rect2(Point2(), _icon->get_size()), color_icon);
}
}
Point2 icon_ofs = !_icon.is_null() ? Point2(icon_region.size.width + get_theme_constant("hseparation"), 0) : Point2();
+ if (align_rtl_checked == ALIGN_CENTER && icon_align_rtl_checked == ALIGN_CENTER) {
+ icon_ofs.x = 0.0;
+ }
int text_clip = size.width - style->get_minimum_size().width - icon_ofs.width;
text_buf->set_width(clip_text ? text_clip : -1);
@@ -262,20 +294,15 @@ void Button::_notification(int p_what) {
Point2 text_ofs = (size - style->get_minimum_size() - icon_ofs - text_buf->get_size() - Point2(_internal_margin[SIDE_RIGHT] - _internal_margin[SIDE_LEFT], 0)) / 2.0;
- switch (align) {
+ switch (align_rtl_checked) {
case ALIGN_LEFT: {
- if (rtl) {
- if (_internal_margin[SIDE_RIGHT] > 0) {
- text_ofs.x = size.x - style->get_margin(SIDE_RIGHT) - text_width - _internal_margin[SIDE_RIGHT] - get_theme_constant("hseparation");
- } else {
- text_ofs.x = size.x - style->get_margin(SIDE_RIGHT) - text_width;
- }
+ if (icon_align_rtl_checked != ALIGN_LEFT) {
+ icon_ofs.x = 0.0;
+ }
+ if (_internal_margin[SIDE_LEFT] > 0) {
+ text_ofs.x = style->get_margin(SIDE_LEFT) + icon_ofs.x + _internal_margin[SIDE_LEFT] + get_theme_constant("hseparation");
} else {
- if (_internal_margin[SIDE_LEFT] > 0) {
- text_ofs.x = style->get_margin(SIDE_LEFT) + icon_ofs.x + _internal_margin[SIDE_LEFT] + get_theme_constant("hseparation");
- } else {
- text_ofs.x = style->get_margin(SIDE_LEFT) + icon_ofs.x;
- }
+ text_ofs.x = style->get_margin(SIDE_LEFT) + icon_ofs.x;
}
text_ofs.y += style->get_offset().y;
} break;
@@ -283,31 +310,24 @@ void Button::_notification(int p_what) {
if (text_ofs.x < 0) {
text_ofs.x = 0;
}
- text_ofs += icon_ofs;
+ if (icon_align_rtl_checked == ALIGN_LEFT) {
+ text_ofs += icon_ofs;
+ }
text_ofs += style->get_offset();
} break;
case ALIGN_RIGHT: {
- if (rtl) {
- if (_internal_margin[SIDE_LEFT] > 0) {
- text_ofs.x = style->get_margin(SIDE_LEFT) + icon_ofs.x + _internal_margin[SIDE_LEFT] + get_theme_constant("hseparation");
- } else {
- text_ofs.x = style->get_margin(SIDE_LEFT) + icon_ofs.x;
- }
+ if (_internal_margin[SIDE_RIGHT] > 0) {
+ text_ofs.x = size.x - style->get_margin(SIDE_RIGHT) - text_width - _internal_margin[SIDE_RIGHT] - get_theme_constant("hseparation");
} else {
- if (_internal_margin[SIDE_RIGHT] > 0) {
- text_ofs.x = size.x - style->get_margin(SIDE_RIGHT) - text_width - _internal_margin[SIDE_RIGHT] - get_theme_constant("hseparation");
- } else {
- text_ofs.x = size.x - style->get_margin(SIDE_RIGHT) - text_width;
- }
+ text_ofs.x = size.x - style->get_margin(SIDE_RIGHT) - text_width;
}
text_ofs.y += style->get_offset().y;
+ if (icon_align_rtl_checked == ALIGN_RIGHT) {
+ text_ofs.x -= icon_ofs.x;
+ }
} break;
}
- if (rtl) {
- text_ofs.x -= icon_ofs.x;
- }
-
Color font_outline_color = get_theme_color("font_outline_color");
int outline_size = get_theme_constant("outline_size");
if (outline_size > 0 && font_outline_color.a > 0) {
@@ -315,10 +335,6 @@ void Button::_notification(int p_what) {
}
text_buf->draw(ci, text_ofs, color);
-
- if (!_icon.is_null() && icon_region.size.width > 0) {
- draw_texture_rect_region(_icon, icon_region, Rect2(Point2(), _icon->get_size()), color_icon);
- }
} break;
}
}
@@ -457,6 +473,16 @@ Button::TextAlign Button::get_text_align() const {
return align;
}
+void Button::set_icon_align(TextAlign p_align) {
+ icon_align = p_align;
+ minimum_size_changed();
+ update();
+}
+
+Button::TextAlign Button::get_icon_align() const {
+ return icon_align;
+}
+
bool Button::_set(const StringName &p_name, const Variant &p_value) {
String str = p_name;
if (str.begins_with("opentype_features/")) {
@@ -519,31 +545,34 @@ void Button::_bind_methods() {
ClassDB::bind_method(D_METHOD("get_language"), &Button::get_language);
ClassDB::bind_method(D_METHOD("set_button_icon", "texture"), &Button::set_icon);
ClassDB::bind_method(D_METHOD("get_button_icon"), &Button::get_icon);
- ClassDB::bind_method(D_METHOD("set_expand_icon"), &Button::set_expand_icon);
- ClassDB::bind_method(D_METHOD("is_expand_icon"), &Button::is_expand_icon);
ClassDB::bind_method(D_METHOD("set_flat", "enabled"), &Button::set_flat);
+ ClassDB::bind_method(D_METHOD("is_flat"), &Button::is_flat);
ClassDB::bind_method(D_METHOD("set_clip_text", "enabled"), &Button::set_clip_text);
ClassDB::bind_method(D_METHOD("get_clip_text"), &Button::get_clip_text);
ClassDB::bind_method(D_METHOD("set_text_align", "align"), &Button::set_text_align);
ClassDB::bind_method(D_METHOD("get_text_align"), &Button::get_text_align);
- ClassDB::bind_method(D_METHOD("is_flat"), &Button::is_flat);
+ ClassDB::bind_method(D_METHOD("set_icon_align", "icon_align"), &Button::set_icon_align);
+ ClassDB::bind_method(D_METHOD("get_icon_align"), &Button::get_icon_align);
+ ClassDB::bind_method(D_METHOD("set_expand_icon"), &Button::set_expand_icon);
+ ClassDB::bind_method(D_METHOD("is_expand_icon"), &Button::is_expand_icon);
BIND_ENUM_CONSTANT(ALIGN_LEFT);
BIND_ENUM_CONSTANT(ALIGN_CENTER);
BIND_ENUM_CONSTANT(ALIGN_RIGHT);
ADD_PROPERTY(PropertyInfo(Variant::STRING, "text", PROPERTY_HINT_MULTILINE_TEXT, "", PROPERTY_USAGE_DEFAULT_INTL), "set_text", "get_text");
- ADD_PROPERTY(PropertyInfo(Variant::INT, "text_direction", PROPERTY_HINT_ENUM, "Auto,LTR,RTL,Inherited"), "set_text_direction", "get_text_direction");
+ ADD_PROPERTY(PropertyInfo(Variant::INT, "text_direction", PROPERTY_HINT_ENUM, "Auto,Left-to-Right,Right-to-Left,Inherited"), "set_text_direction", "get_text_direction");
ADD_PROPERTY(PropertyInfo(Variant::STRING, "language"), "set_language", "get_language");
ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "icon", PROPERTY_HINT_RESOURCE_TYPE, "Texture2D"), "set_button_icon", "get_button_icon");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "flat"), "set_flat", "is_flat");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "clip_text"), "set_clip_text", "get_clip_text");
ADD_PROPERTY(PropertyInfo(Variant::INT, "align", PROPERTY_HINT_ENUM, "Left,Center,Right"), "set_text_align", "get_text_align");
+ ADD_PROPERTY(PropertyInfo(Variant::INT, "icon_align", PROPERTY_HINT_ENUM, "Left,Center,Right"), "set_icon_align", "get_icon_align");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "expand_icon"), "set_expand_icon", "is_expand_icon");
}
Button::Button(const String &p_text) {
- text_buf.instance();
+ text_buf.instantiate();
text_buf->set_flags(TextServer::BREAK_MANDATORY);
set_mouse_filter(MOUSE_FILTER_STOP);
diff --git a/scene/gui/button.h b/scene/gui/button.h
index d968f63f51..253d079680 100644
--- a/scene/gui/button.h
+++ b/scene/gui/button.h
@@ -58,6 +58,7 @@ private:
bool expand_icon = false;
bool clip_text = false;
TextAlign align = ALIGN_CENTER;
+ TextAlign icon_align = ALIGN_LEFT;
float _internal_margin[4] = {};
void _shape();
@@ -102,6 +103,9 @@ public:
void set_text_align(TextAlign p_align);
TextAlign get_text_align() const;
+ void set_icon_align(TextAlign p_align);
+ TextAlign get_icon_align() const;
+
Button(const String &p_text = String());
~Button();
};
diff --git a/scene/gui/code_edit.cpp b/scene/gui/code_edit.cpp
index 28a0ea0100..ba1534ed5c 100644
--- a/scene/gui/code_edit.cpp
+++ b/scene/gui/code_edit.cpp
@@ -30,6 +30,18 @@
#include "code_edit.h"
+#include "core/os/keyboard.h"
+#include "core/string/string_builder.h"
+#include "core/string/ustring.h"
+
+static bool _is_whitespace(char32_t c) {
+ return c == '\t' || c == ' ';
+}
+
+static bool _is_char(char32_t c) {
+ return !is_symbol(c);
+}
+
void CodeEdit::_notification(int p_what) {
switch (p_what) {
case NOTIFICATION_THEME_CHANGED:
@@ -52,12 +64,838 @@ void CodeEdit::_notification(int p_what) {
folding_color = get_theme_color("code_folding_color");
can_fold_icon = get_theme_icon("can_fold");
folded_icon = get_theme_icon("folded");
+
+ code_completion_max_width = get_theme_constant("completion_max_width") * cache.font->get_char_size('x').x;
+ code_completion_max_lines = get_theme_constant("completion_lines");
+ code_completion_scroll_width = get_theme_constant("completion_scroll_width");
+ code_completion_scroll_color = get_theme_color("completion_scroll_color");
+ code_completion_background_color = get_theme_color("completion_background_color");
+ code_completion_selected_color = get_theme_color("completion_selected_color");
+ code_completion_existing_color = get_theme_color("completion_existing_color");
} break;
case NOTIFICATION_DRAW: {
+ RID ci = get_canvas_item();
+ const bool caret_visible = is_caret_visible();
+ const bool rtl = is_layout_rtl();
+ const int row_height = get_row_height();
+
+ bool code_completion_below = false;
+ if (caret_visible && code_completion_active && code_completion_options.size() > 0) {
+ Ref<StyleBox> csb = get_theme_stylebox("completion");
+
+ const int code_completion_options_count = code_completion_options.size();
+ const int lines = MIN(code_completion_options_count, code_completion_max_lines);
+ const int icon_hsep = get_theme_constant("hseparation", "ItemList");
+ const Size2 icon_area_size(row_height, row_height);
+
+ code_completion_rect.size.width = code_completion_longest_line + icon_hsep + icon_area_size.width + 2;
+ code_completion_rect.size.height = lines * row_height;
+
+ const Point2 caret_pos = get_caret_draw_pos();
+ const int total_height = csb->get_minimum_size().y + code_completion_rect.size.height;
+ if (caret_pos.y + row_height + total_height > get_size().height) {
+ code_completion_rect.position.y = (caret_pos.y - total_height - row_height) + cache.line_spacing;
+ } else {
+ code_completion_rect.position.y = caret_pos.y + (cache.line_spacing / 2.0f);
+ code_completion_below = true;
+ }
+
+ const int scroll_width = code_completion_options_count > code_completion_max_lines ? code_completion_scroll_width : 0;
+ const int code_completion_base_width = cache.font->get_string_size(code_completion_base).width;
+ if (caret_pos.x - code_completion_base_width + code_completion_rect.size.width + scroll_width > get_size().width) {
+ code_completion_rect.position.x = get_size().width - code_completion_rect.size.width - scroll_width;
+ } else {
+ code_completion_rect.position.x = caret_pos.x - code_completion_base_width;
+ }
+
+ draw_style_box(csb, Rect2(code_completion_rect.position - csb->get_offset(), code_completion_rect.size + csb->get_minimum_size() + Size2(scroll_width, 0)));
+ if (code_completion_background_color.a > 0.01) {
+ RenderingServer::get_singleton()->canvas_item_add_rect(ci, Rect2(code_completion_rect.position, code_completion_rect.size + Size2(scroll_width, 0)), code_completion_background_color);
+ }
+
+ code_completion_line_ofs = CLAMP(code_completion_current_selected - lines / 2, 0, code_completion_options_count - lines);
+ RenderingServer::get_singleton()->canvas_item_add_rect(ci, Rect2(Point2(code_completion_rect.position.x, code_completion_rect.position.y + (code_completion_current_selected - code_completion_line_ofs) * row_height), Size2(code_completion_rect.size.width, row_height)), code_completion_selected_color);
+ draw_rect(Rect2(code_completion_rect.position + Vector2(icon_area_size.x + icon_hsep, 0), Size2(MIN(code_completion_base_width, code_completion_rect.size.width - (icon_area_size.x + icon_hsep)), code_completion_rect.size.height)), code_completion_existing_color);
+
+ for (int i = 0; i < lines; i++) {
+ int l = code_completion_line_ofs + i;
+ ERR_CONTINUE(l < 0 || l >= code_completion_options_count);
+
+ Ref<TextLine> tl;
+ tl.instantiate();
+ tl->add_string(code_completion_options[l].display, cache.font, cache.font_size);
+
+ int yofs = (row_height - tl->get_size().y) / 2;
+ Point2 title_pos(code_completion_rect.position.x, code_completion_rect.position.y + i * row_height + yofs);
+
+ /* Draw completion icon if it is valid. */
+ const Ref<Texture2D> &icon = code_completion_options[l].icon;
+ Rect2 icon_area(code_completion_rect.position.x, code_completion_rect.position.y + i * row_height, icon_area_size.width, icon_area_size.height);
+ if (icon.is_valid()) {
+ Size2 icon_size = icon_area.size * 0.7;
+ icon->draw_rect(ci, Rect2(icon_area.position + (icon_area.size - icon_size) / 2, icon_size));
+ }
+ title_pos.x = icon_area.position.x + icon_area.size.width + icon_hsep;
+
+ tl->set_width(code_completion_rect.size.width - (icon_area_size.x + icon_hsep));
+ if (rtl) {
+ if (code_completion_options[l].default_value.get_type() == Variant::COLOR) {
+ draw_rect(Rect2(Point2(code_completion_rect.position.x, icon_area.position.y), icon_area_size), (Color)code_completion_options[l].default_value);
+ }
+ tl->set_align(HALIGN_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);
+ }
+ tl->set_align(HALIGN_LEFT);
+ }
+ tl->draw(ci, title_pos, code_completion_options[l].font_color);
+ }
+
+ /* Draw a small scroll rectangle to show a position in the options. */
+ if (scroll_width) {
+ float r = (float)code_completion_max_lines / code_completion_options_count;
+ float o = (float)code_completion_line_ofs / code_completion_options_count;
+ draw_rect(Rect2(code_completion_rect.position.x + code_completion_rect.size.width, code_completion_rect.position.y + o * code_completion_rect.size.y, scroll_width, code_completion_rect.size.y * r), code_completion_scroll_color);
+ }
+ }
+
+ /* Code hint */
+ if (caret_visible && code_hint != "" && (!code_completion_active || (code_completion_below != code_hint_draw_below))) {
+ const Ref<Font> font = cache.font;
+ const int font_height = font->get_height(cache.font_size);
+ Ref<StyleBox> sb = get_theme_stylebox("panel", "TooltipPanel");
+ Color font_color = get_theme_color("font_color", "TooltipLabel");
+
+ Vector<String> code_hint_lines = code_hint.split("\n");
+ int line_count = code_hint_lines.size();
+
+ int max_width = 0;
+ for (int i = 0; i < line_count; i++) {
+ max_width = MAX(max_width, font->get_string_size(code_hint_lines[i], cache.font_size).x);
+ }
+ Size2 minsize = sb->get_minimum_size() + Size2(max_width, line_count * font_height + (cache.line_spacing * line_count - 1));
+
+ int offset = font->get_string_size(code_hint_lines[0].substr(0, code_hint_lines[0].find(String::chr(0xFFFF))), cache.font_size).x;
+ if (code_hint_xpos == -0xFFFF) {
+ code_hint_xpos = get_caret_draw_pos().x - offset;
+ }
+ Point2 hint_ofs = Vector2(code_hint_xpos, get_caret_draw_pos().y);
+ if (code_hint_draw_below) {
+ hint_ofs.y += cache.line_spacing / 2.0f;
+ } else {
+ hint_ofs.y -= (minsize.y + row_height) - cache.line_spacing;
+ }
+
+ draw_style_box(sb, Rect2(hint_ofs, minsize));
+
+ int line_spacing = 0;
+ for (int i = 0; i < line_count; i++) {
+ const String &line = code_hint_lines[i];
+
+ int begin = 0;
+ int end = 0;
+ if (line.find(String::chr(0xFFFF)) != -1) {
+ begin = font->get_string_size(line.substr(0, line.find(String::chr(0xFFFF))), cache.font_size).x;
+ end = font->get_string_size(line.substr(0, line.rfind(String::chr(0xFFFF))), cache.font_size).x;
+ }
+
+ Point2 round_ofs = hint_ofs + sb->get_offset() + Vector2(0, font->get_ascent() + font_height * i + line_spacing);
+ round_ofs = round_ofs.round();
+ draw_string(font, round_ofs, line.replace(String::chr(0xFFFF), ""), HALIGN_LEFT, -1, cache.font_size, font_color);
+ if (end > 0) {
+ Vector2 b = hint_ofs + sb->get_offset() + Vector2(begin, font_height + font_height * i + line_spacing - 1);
+ draw_line(b, b + Vector2(end - begin, 0), font_color);
+ }
+ line_spacing += cache.line_spacing;
+ }
+ }
} break;
}
}
+void CodeEdit::_gui_input(const Ref<InputEvent> &p_gui_input) {
+ Ref<InputEventMouseButton> mb = p_gui_input;
+
+ if (mb.is_valid()) {
+ /* Ignore mouse clicks in IME input mode. */
+ if (has_ime_text()) {
+ return;
+ }
+
+ if (code_completion_active && code_completion_rect.has_point(mb->get_position())) {
+ if (!mb->is_pressed()) {
+ return;
+ }
+
+ switch (mb->get_button_index()) {
+ case MOUSE_BUTTON_WHEEL_UP: {
+ if (code_completion_current_selected > 0) {
+ code_completion_current_selected--;
+ update();
+ }
+ } break;
+ case MOUSE_BUTTON_WHEEL_DOWN: {
+ if (code_completion_current_selected < code_completion_options.size() - 1) {
+ code_completion_current_selected++;
+ update();
+ }
+ } break;
+ case MOUSE_BUTTON_LEFT: {
+ code_completion_current_selected = CLAMP(code_completion_line_ofs + (mb->get_position().y - code_completion_rect.position.y) / get_row_height(), 0, code_completion_options.size() - 1);
+ if (mb->is_double_click()) {
+ confirm_code_completion();
+ }
+ update();
+ } break;
+ default:
+ break;
+ }
+ return;
+ }
+ cancel_code_completion();
+ set_code_hint("");
+
+ if (mb->is_pressed()) {
+ Vector2i mpos = mb->get_position();
+ if (is_layout_rtl()) {
+ mpos.x = get_size().x - mpos.x;
+ }
+
+ int line, col;
+ _get_mouse_pos(Point2i(mpos.x, mpos.y), line, col);
+
+ if (mb->get_button_index() == MOUSE_BUTTON_LEFT) {
+ if (is_line_folded(line)) {
+ int wrap_index = get_line_wrap_index_at_col(line, col);
+ if (wrap_index == times_line_wraps(line)) {
+ int eol_icon_width = cache.folded_eol_icon->get_width();
+ int left_margin = get_total_gutter_width() + eol_icon_width + get_line_width(line, wrap_index) - get_h_scroll();
+ if (mpos.x > left_margin && mpos.x <= left_margin + eol_icon_width + 3) {
+ unfold_line(line);
+ return;
+ }
+ }
+ }
+ }
+ }
+ }
+
+ Ref<InputEventKey> k = p_gui_input;
+ bool update_code_completion = false;
+ if (!k.is_valid()) {
+ TextEdit::_gui_input(p_gui_input);
+ return;
+ }
+
+ /* If a modifier has been pressed, and nothing else, return. */
+ if (!k->is_pressed() || k->get_keycode() == KEY_CTRL || k->get_keycode() == KEY_ALT || k->get_keycode() == KEY_SHIFT || k->get_keycode() == KEY_META) {
+ return;
+ }
+
+ /* Allow unicode handling if: */
+ /* No Modifiers are pressed (except shift) */
+ bool allow_unicode_handling = !(k->is_command_pressed() || k->is_ctrl_pressed() || k->is_alt_pressed() || k->is_meta_pressed());
+
+ /* AUTO-COMPLETE */
+ if (code_completion_enabled && k->is_action("ui_text_completion_query", true)) {
+ request_code_completion(true);
+ accept_event();
+ return;
+ }
+
+ if (code_completion_active) {
+ if (k->is_action("ui_up", true)) {
+ if (code_completion_current_selected > 0) {
+ code_completion_current_selected--;
+ } else {
+ code_completion_current_selected = code_completion_options.size() - 1;
+ }
+ update();
+ accept_event();
+ return;
+ }
+ if (k->is_action("ui_down", true)) {
+ if (code_completion_current_selected < code_completion_options.size() - 1) {
+ code_completion_current_selected++;
+ } else {
+ code_completion_current_selected = 0;
+ }
+ update();
+ accept_event();
+ return;
+ }
+ if (k->is_action("ui_page_up", true)) {
+ code_completion_current_selected = MAX(0, code_completion_current_selected - code_completion_max_lines);
+ update();
+ accept_event();
+ return;
+ }
+ if (k->is_action("ui_page_down", true)) {
+ code_completion_current_selected = MIN(code_completion_options.size() - 1, code_completion_current_selected + code_completion_max_lines);
+ update();
+ accept_event();
+ return;
+ }
+ if (k->is_action("ui_home", true)) {
+ code_completion_current_selected = 0;
+ update();
+ accept_event();
+ return;
+ }
+ if (k->is_action("ui_end", true)) {
+ code_completion_current_selected = MIN(code_completion_options.size() - 1, code_completion_current_selected + code_completion_max_lines);
+ update();
+ accept_event();
+ return;
+ }
+ if (k->is_action("ui_text_completion_replace", true) || k->is_action("ui_text_completion_accept", true)) {
+ confirm_code_completion(k->is_action("ui_text_completion_replace", true));
+ accept_event();
+ return;
+ }
+ if (k->is_action("ui_cancel", true)) {
+ cancel_code_completion();
+ accept_event();
+ return;
+ }
+ if (k->is_action("ui_text_backspace", true)) {
+ backspace();
+ _filter_code_completion_candidates();
+ accept_event();
+ return;
+ }
+
+ if (k->is_action("ui_left", true) || k->is_action("ui_right", true)) {
+ update_code_completion = true;
+ } else {
+ update_code_completion = (allow_unicode_handling && k->get_unicode() >= 32);
+ }
+
+ if (!update_code_completion) {
+ cancel_code_completion();
+ }
+ }
+
+ /* MISC */
+ if (k->is_action("ui_cancel", true)) {
+ set_code_hint("");
+ accept_event();
+ return;
+ }
+ if (allow_unicode_handling && k->get_unicode() == ')') {
+ set_code_hint("");
+ }
+
+ /* Indentation */
+ if (k->is_action("ui_text_indent", true)) {
+ do_indent();
+ accept_event();
+ return;
+ }
+
+ if (k->is_action("ui_text_dedent", true)) {
+ do_unindent();
+ accept_event();
+ return;
+ }
+
+ // Override new line actions, for auto indent
+ if (k->is_action("ui_text_newline_above", true)) {
+ _new_line(false, true);
+ accept_event();
+ return;
+ }
+ if (k->is_action("ui_text_newline_blank", true)) {
+ _new_line(false);
+ accept_event();
+ return;
+ }
+ if (k->is_action("ui_text_newline", true)) {
+ _new_line();
+ accept_event();
+ return;
+ }
+
+ /* Remove shift otherwise actions will not match. */
+ k = k->duplicate();
+ k->set_shift_pressed(false);
+
+ if (k->is_action("ui_text_caret_up", true) ||
+ k->is_action("ui_text_caret_down", true) ||
+ k->is_action("ui_text_caret_line_start", true) ||
+ k->is_action("ui_text_caret_line_end", true) ||
+ k->is_action("ui_text_caret_page_up", true) ||
+ k->is_action("ui_text_caret_page_down", true)) {
+ set_code_hint("");
+ }
+
+ TextEdit::_gui_input(p_gui_input);
+
+ if (update_code_completion) {
+ _filter_code_completion_candidates();
+ }
+}
+
+Control::CursorShape CodeEdit::get_cursor_shape(const Point2 &p_pos) const {
+ if ((code_completion_active && code_completion_rect.has_point(p_pos)) || (is_readonly() && (!is_selecting_enabled() || get_line_count() == 0))) {
+ return CURSOR_ARROW;
+ }
+
+ int line, col;
+ _get_mouse_pos(p_pos, line, col);
+
+ if (is_line_folded(line)) {
+ int wrap_index = get_line_wrap_index_at_col(line, col);
+ if (wrap_index == times_line_wraps(line)) {
+ int eol_icon_width = cache.folded_eol_icon->get_width();
+ int left_margin = get_total_gutter_width() + eol_icon_width + get_line_width(line, wrap_index) - get_h_scroll();
+ if (p_pos.x > left_margin && p_pos.x <= left_margin + eol_icon_width + 3) {
+ return CURSOR_POINTING_HAND;
+ }
+ }
+ }
+
+ return TextEdit::get_cursor_shape(p_pos);
+}
+
+/* Indent management */
+void CodeEdit::set_indent_size(const int p_size) {
+ ERR_FAIL_COND_MSG(p_size <= 0, "Indend size must be greater than 0.");
+ if (indent_size == p_size) {
+ return;
+ }
+
+ indent_size = p_size;
+ if (indent_using_spaces) {
+ indent_text = String(" ").repeat(p_size);
+ } else {
+ indent_text = "\t";
+ }
+ set_tab_size(p_size);
+}
+
+int CodeEdit::get_indent_size() const {
+ return indent_size;
+}
+
+void CodeEdit::set_indent_using_spaces(const bool p_use_spaces) {
+ indent_using_spaces = p_use_spaces;
+ if (indent_using_spaces) {
+ indent_text = String(" ").repeat(indent_size);
+ } else {
+ indent_text = "\t";
+ }
+}
+
+bool CodeEdit::is_indent_using_spaces() const {
+ return indent_using_spaces;
+}
+
+void CodeEdit::set_auto_indent_enabled(bool p_enabled) {
+ auto_indent = p_enabled;
+}
+
+bool CodeEdit::is_auto_indent_enabled() const {
+ return auto_indent;
+}
+
+void CodeEdit::set_auto_indent_prefixes(const TypedArray<String> &p_prefixes) {
+ auto_indent_prefixes.clear();
+ for (int i = 0; i < p_prefixes.size(); i++) {
+ const String prefix = p_prefixes[i];
+ auto_indent_prefixes.insert(prefix[0]);
+ }
+}
+
+TypedArray<String> CodeEdit::get_auto_indent_prefixes() const {
+ TypedArray<String> prefixes;
+ for (const Set<char32_t>::Element *E = auto_indent_prefixes.front(); E; E = E->next()) {
+ prefixes.push_back(String::chr(E->get()));
+ }
+ return prefixes;
+}
+
+void CodeEdit::do_indent() {
+ if (is_readonly()) {
+ return;
+ }
+
+ if (is_selection_active()) {
+ indent_lines();
+ return;
+ }
+
+ if (!indent_using_spaces) {
+ _insert_text_at_cursor("\t");
+ return;
+ }
+
+ int spaces_to_add = _calculate_spaces_till_next_right_indent(cursor_get_column());
+ if (spaces_to_add > 0) {
+ _insert_text_at_cursor(String(" ").repeat(spaces_to_add));
+ }
+}
+
+void CodeEdit::indent_lines() {
+ if (is_readonly()) {
+ return;
+ }
+
+ begin_complex_operation();
+
+ /* This value informs us by how much we changed selection position by indenting right. */
+ /* Default is 1 for tab indentation. */
+ int selection_offset = 1;
+
+ int start_line = cursor_get_line();
+ int end_line = start_line;
+ if (is_selection_active()) {
+ start_line = get_selection_from_line();
+ end_line = get_selection_to_line();
+
+ /* Ignore the last line if the selection is not past the first column. */
+ if (get_selection_to_column() == 0) {
+ selection_offset = 0;
+ end_line--;
+ }
+ }
+
+ for (int i = start_line; i <= end_line; i++) {
+ const String line_text = get_line(i);
+ if (line_text.size() == 0 && is_selection_active()) {
+ continue;
+ }
+
+ if (!indent_using_spaces) {
+ set_line(i, '\t' + line_text);
+ continue;
+ }
+
+ /* We don't really care where selection is - we just need to know indentation level at the beginning of the line. */
+ /* Since we will add this many spaces, we want to move the whole selection and caret by this much. */
+ int spaces_to_add = _calculate_spaces_till_next_right_indent(get_first_non_whitespace_column(i));
+ set_line(i, String(" ").repeat(spaces_to_add) + line_text);
+ selection_offset = spaces_to_add;
+ }
+
+ /* Fix selection and caret being off after shifting selection right.*/
+ if (is_selection_active()) {
+ select(start_line, get_selection_from_column() + selection_offset, get_selection_to_line(), get_selection_to_column() + selection_offset);
+ }
+ cursor_set_column(cursor_get_column() + selection_offset, false);
+
+ end_complex_operation();
+}
+
+void CodeEdit::do_unindent() {
+ if (is_readonly()) {
+ return;
+ }
+
+ int cc = cursor_get_column();
+
+ if (is_selection_active() || cc <= 0) {
+ unindent_lines();
+ return;
+ }
+
+ int cl = cursor_get_line();
+ const String &line = get_line(cl);
+
+ if (line[cc - 1] == '\t') {
+ _remove_text(cl, cc - 1, cl, cc);
+ cursor_set_column(MAX(0, cc - 1));
+ return;
+ }
+
+ if (line[cc - 1] != ' ') {
+ return;
+ }
+
+ int spaces_to_remove = _calculate_spaces_till_next_left_indent(cc);
+ if (spaces_to_remove > 0) {
+ for (int i = 1; i <= spaces_to_remove; i++) {
+ if (line[cc - i] != ' ') {
+ spaces_to_remove = i - 1;
+ break;
+ }
+ }
+ _remove_text(cl, cc - spaces_to_remove, cl, cc);
+ cursor_set_column(MAX(0, cc - spaces_to_remove));
+ }
+}
+
+void CodeEdit::unindent_lines() {
+ if (is_readonly()) {
+ return;
+ }
+
+ begin_complex_operation();
+
+ /* Moving caret and selection after unindenting can get tricky because */
+ /* changing content of line can move caret and selection on its own (if new line ends before previous position of either), */
+ /* therefore we just remember initial values and at the end of the operation offset them by number of removed characters. */
+ int removed_characters = 0;
+ int initial_selection_end_column = 0;
+ int initial_cursor_column = cursor_get_column();
+
+ int start_line = cursor_get_line();
+ int end_line = start_line;
+ if (is_selection_active()) {
+ start_line = get_selection_from_line();
+ end_line = get_selection_to_line();
+
+ /* Ignore the last line if the selection is not past the first column. */
+ initial_selection_end_column = get_selection_to_column();
+ if (initial_selection_end_column == 0) {
+ end_line--;
+ }
+ }
+
+ bool first_line_edited = false;
+ bool last_line_edited = false;
+
+ for (int i = start_line; i <= end_line; i++) {
+ String line_text = get_line(i);
+
+ if (line_text.begins_with("\t")) {
+ line_text = line_text.substr(1, line_text.length());
+
+ set_line(i, line_text);
+ removed_characters = 1;
+
+ first_line_edited = (i == start_line) ? true : first_line_edited;
+ last_line_edited = (i == end_line) ? true : last_line_edited;
+ continue;
+ }
+
+ if (line_text.begins_with(" ")) {
+ /* When unindenting we aim to remove spaces before line that has selection no matter what is selected, */
+ /* Here we remove only enough spaces to align text to nearest full multiple of indentation_size. */
+ /* In case where selection begins at the start of indentation_size multiple we remove whole indentation level. */
+ int spaces_to_remove = _calculate_spaces_till_next_left_indent(get_first_non_whitespace_column(i));
+ line_text = line_text.substr(spaces_to_remove, line_text.length());
+
+ set_line(i, line_text);
+ removed_characters = spaces_to_remove;
+
+ first_line_edited = (i == start_line) ? true : first_line_edited;
+ last_line_edited = (i == end_line) ? true : last_line_edited;
+ }
+ }
+
+ if (is_selection_active()) {
+ /* Fix selection being off by one on the first line. */
+ if (first_line_edited) {
+ select(get_selection_from_line(), get_selection_from_column() - removed_characters, get_selection_to_line(), initial_selection_end_column);
+ }
+
+ /* Fix selection being off by one on the last line. */
+ if (last_line_edited) {
+ select(get_selection_from_line(), get_selection_from_column(), get_selection_to_line(), initial_selection_end_column - removed_characters);
+ }
+ }
+ cursor_set_column(initial_cursor_column - removed_characters, false);
+
+ end_complex_operation();
+}
+
+int CodeEdit::_calculate_spaces_till_next_left_indent(int p_column) const {
+ int spaces_till_indent = p_column % indent_size;
+ if (spaces_till_indent == 0) {
+ spaces_till_indent = indent_size;
+ }
+ return spaces_till_indent;
+}
+
+int CodeEdit::_calculate_spaces_till_next_right_indent(int p_column) const {
+ return indent_size - p_column % indent_size;
+}
+
+/* TODO: remove once brace completion is refactored. */
+static char32_t _get_right_pair_symbol(char32_t c) {
+ if (c == '"') {
+ return '"';
+ }
+ if (c == '\'') {
+ return '\'';
+ }
+ if (c == '(') {
+ return ')';
+ }
+ if (c == '[') {
+ return ']';
+ }
+ if (c == '{') {
+ return '}';
+ }
+ return 0;
+}
+
+static bool _is_pair_left_symbol(char32_t c) {
+ return c == '"' ||
+ c == '\'' ||
+ c == '(' ||
+ c == '[' ||
+ c == '{';
+}
+
+void CodeEdit::_new_line(bool p_split_current_line, bool p_above) {
+ if (is_readonly()) {
+ return;
+ }
+
+ const int cc = cursor_get_column();
+ const int cl = cursor_get_line();
+ const String line = get_line(cl);
+
+ String ins = "\n";
+
+ /* Append current indentation. */
+ int space_count = 0;
+ int line_col = 0;
+ for (; line_col < cc; line_col++) {
+ if (line[line_col] == '\t') {
+ ins += indent_text;
+ space_count = 0;
+ continue;
+ }
+
+ if (line[line_col] == ' ') {
+ space_count++;
+
+ if (space_count == indent_size) {
+ ins += indent_text;
+ space_count = 0;
+ }
+ continue;
+ }
+ break;
+ }
+
+ if (is_line_folded(cl)) {
+ unfold_line(cl);
+ }
+
+ /* Indent once again if the previous line needs it, ie ':'. */
+ /* Then add an addition new line for any closing pairs aka '()'. */
+ /* Skip this in comments or if we are going above. */
+ bool brace_indent = false;
+ if (auto_indent && !p_above && cc > 0 && is_in_comment(cl) == -1) {
+ bool should_indent = false;
+ char32_t indent_char = ' ';
+
+ for (; line_col < cc; line_col++) {
+ char32_t c = line[line_col];
+ if (auto_indent_prefixes.has(c)) {
+ should_indent = true;
+ indent_char = c;
+ continue;
+ }
+
+ /* Make sure this is the last char, trailing whitespace or comments are okay. */
+ if (should_indent && (!_is_whitespace(c) && is_in_comment(cl, cc) == -1)) {
+ should_indent = false;
+ }
+ }
+
+ if (should_indent) {
+ ins += indent_text;
+
+ /* TODO: Change when brace completion is refactored. */
+ char32_t closing_char = _get_right_pair_symbol(indent_char);
+ if (closing_char != 0 && closing_char == line[cc]) {
+ /* No need to move the brace below if we are not taking the text with us. */
+ if (p_split_current_line) {
+ brace_indent = true;
+ ins += "\n" + ins.substr(1, ins.length() - 2);
+ } else {
+ brace_indent = false;
+ ins = "\n" + ins.substr(1, ins.length() - 2);
+ }
+ }
+ }
+ }
+
+ begin_complex_operation();
+
+ bool first_line = false;
+ if (!p_split_current_line) {
+ if (p_above) {
+ if (cl > 0) {
+ cursor_set_line(cl - 1, false);
+ cursor_set_column(get_line(cursor_get_line()).length());
+ } else {
+ cursor_set_column(0);
+ first_line = true;
+ }
+ } else {
+ cursor_set_column(line.length());
+ }
+ }
+
+ insert_text_at_cursor(ins);
+
+ if (first_line) {
+ cursor_set_line(0);
+ } else if (brace_indent) {
+ cursor_set_line(cursor_get_line() - 1, false);
+ cursor_set_column(get_line(cursor_get_line()).length());
+ }
+
+ end_complex_operation();
+}
+
+void CodeEdit::backspace() {
+ if (is_readonly()) {
+ return;
+ }
+
+ int cc = cursor_get_column();
+ int cl = cursor_get_line();
+
+ if (cc == 0 && cl == 0) {
+ return;
+ }
+
+ if (is_selection_active()) {
+ delete_selection();
+ return;
+ }
+
+ if (cl > 0 && is_line_hidden(cl - 1)) {
+ unfold_line(cursor_get_line() - 1);
+ }
+
+ int prev_line = cc ? cl : cl - 1;
+ int prev_column = cc ? (cc - 1) : (get_line(cl - 1).length());
+
+ merge_gutters(cl, prev_line);
+
+ /* TODO: Change when brace completion is refactored. */
+ if (auto_brace_completion_enabled && cc > 0 && _is_pair_left_symbol(get_line(cl)[cc - 1])) {
+ _consume_backspace_for_pair_symbol(prev_line, prev_column);
+ cursor_set_line(prev_line, false, true);
+ cursor_set_column(prev_column);
+ return;
+ }
+
+ /* For space indentation we need to do a simple unindent if there are no chars to the left, acting in the */
+ /* same way as tabs. */
+ if (indent_using_spaces && cc != 0) {
+ if (get_first_non_whitespace_column(cl) > cc) {
+ prev_column = cc - _calculate_spaces_till_next_left_indent(cc);
+ prev_line = cl;
+ }
+ }
+
+ _remove_text(prev_line, prev_column, cl, cc);
+
+ cursor_set_line(prev_line, false, true);
+ cursor_set_column(prev_column);
+}
+
/* Main Gutter */
void CodeEdit::_update_draw_main_gutter() {
set_gutter_draw(main_gutter, draw_breakpoints || draw_bookmarks || draw_executing_lines);
@@ -236,7 +1074,7 @@ 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 = TS->format_number(String::num(p_line + 1).lpad(line_number_digits, line_number_padding));
Ref<TextLine> tl;
- tl.instance();
+ tl.instantiate();
tl->add_string(fc, cache.font, cache.font_size);
int yofs = p_region.position.y + (get_row_height() - tl->get_size().y) / 2;
Color number_color = get_line_gutter_item_color(p_line, line_number_gutter);
@@ -256,7 +1094,7 @@ bool CodeEdit::is_drawing_fold_gutter() const {
}
void CodeEdit::_fold_gutter_draw_callback(int p_line, int p_gutter, Rect2 p_region) {
- if (!can_fold(p_line) && !is_folded(p_line)) {
+ if (!can_fold_line(p_line) && !is_line_folded(p_line)) {
set_line_gutter_clickable(p_line, fold_gutter, false);
return;
}
@@ -268,14 +1106,662 @@ void CodeEdit::_fold_gutter_draw_callback(int p_line, int p_gutter, Rect2 p_regi
p_region.position += Point2(horizontal_padding, vertical_padding);
p_region.size -= Point2(horizontal_padding, vertical_padding) * 2;
- if (can_fold(p_line)) {
+ if (can_fold_line(p_line)) {
can_fold_icon->draw_rect(get_canvas_item(), p_region, false, folding_color);
return;
}
folded_icon->draw_rect(get_canvas_item(), p_region, false, folding_color);
}
+/* Line Folding */
+void CodeEdit::set_line_folding_enabled(bool p_enabled) {
+ line_folding_enabled = p_enabled;
+ set_hiding_enabled(p_enabled);
+}
+
+bool CodeEdit::is_line_folding_enabled() const {
+ return line_folding_enabled;
+}
+
+bool CodeEdit::can_fold_line(int p_line) const {
+ ERR_FAIL_INDEX_V(p_line, get_line_count(), false);
+ if (!line_folding_enabled) {
+ return false;
+ }
+
+ if (p_line + 1 >= get_line_count() || get_line(p_line).strip_edges().size() == 0) {
+ return false;
+ }
+
+ if (is_line_hidden(p_line) || is_line_folded(p_line)) {
+ return false;
+ }
+
+ /* Check for full multiline line or block strings / comments. */
+ int in_comment = is_in_comment(p_line);
+ int in_string = (in_comment == -1) ? is_in_string(p_line) : -1;
+ if (in_string != -1 || in_comment != -1) {
+ if (get_delimiter_start_position(p_line, get_line(p_line).size() - 1).y != p_line) {
+ return false;
+ }
+
+ int delimter_end_line = get_delimiter_end_position(p_line, get_line(p_line).size() - 1).y;
+ /* No end line, therefore we have a multiline region over the rest of the file. */
+ if (delimter_end_line == -1) {
+ return true;
+ }
+ /* End line is the same therefore we have a block. */
+ if (delimter_end_line == p_line) {
+ /* Check we are the start of the block. */
+ if (p_line - 1 >= 0) {
+ if ((in_string != -1 && is_in_string(p_line - 1) != -1) || (in_comment != -1 && is_in_comment(p_line - 1) != -1)) {
+ return false;
+ }
+ }
+ /* Check it continues for at least one line. */
+ return ((in_string != -1 && is_in_string(p_line + 1) != -1) || (in_comment != -1 && is_in_comment(p_line + 1) != -1));
+ }
+ return ((in_string != -1 && is_in_string(delimter_end_line) != -1) || (in_comment != -1 && is_in_comment(delimter_end_line) != -1));
+ }
+
+ /* Otherwise check indent levels. */
+ int start_indent = get_indent_level(p_line);
+ for (int i = p_line + 1; i < get_line_count(); i++) {
+ if (is_in_string(i) != -1 || is_in_comment(i) != -1 || get_line(i).strip_edges().size() == 0) {
+ continue;
+ }
+ return (get_indent_level(i) > start_indent);
+ }
+ return false;
+}
+
+void CodeEdit::fold_line(int p_line) {
+ ERR_FAIL_INDEX(p_line, get_line_count());
+ if (!is_line_folding_enabled() || !can_fold_line(p_line)) {
+ return;
+ }
+
+ /* Find the last line to be hidden. */
+ int end_line = get_line_count();
+
+ int in_comment = is_in_comment(p_line);
+ int in_string = (in_comment == -1) ? is_in_string(p_line) : -1;
+ if (in_string != -1 || in_comment != -1) {
+ end_line = get_delimiter_end_position(p_line, get_line(p_line).size() - 1).y;
+ /* End line is the same therefore we have a block. */
+ if (end_line == p_line) {
+ for (int i = p_line + 1; i < get_line_count(); i++) {
+ if ((in_string != -1 && is_in_string(i) == -1) || (in_comment != -1 && is_in_comment(i) == -1)) {
+ end_line = i - 1;
+ break;
+ }
+ }
+ }
+ } else {
+ int start_indent = get_indent_level(p_line);
+ for (int i = p_line + 1; i < get_line_count(); i++) {
+ if (get_line(p_line).strip_edges().size() == 0 || is_in_string(i) != -1 || is_in_comment(i) != -1) {
+ end_line = i;
+ continue;
+ }
+
+ if (get_indent_level(i) <= start_indent && get_line(i).strip_edges().size() != 0) {
+ end_line = i - 1;
+ break;
+ }
+ }
+ }
+
+ for (int i = p_line + 1; i <= end_line; i++) {
+ set_line_as_hidden(i, true);
+ }
+
+ /* Fix selection. */
+ if (is_selection_active()) {
+ if (is_line_hidden(get_selection_from_line()) && is_line_hidden(get_selection_to_line())) {
+ deselect();
+ } else if (is_line_hidden(get_selection_from_line())) {
+ select(p_line, 9999, get_selection_to_line(), get_selection_to_column());
+ } else if (is_line_hidden(get_selection_to_line())) {
+ select(get_selection_from_line(), get_selection_from_column(), p_line, 9999);
+ }
+ }
+
+ /* Reset caret. */
+ if (is_line_hidden(cursor_get_line())) {
+ cursor_set_line(p_line, false, false);
+ cursor_set_column(get_line(p_line).length(), false);
+ }
+ update();
+}
+
+void CodeEdit::unfold_line(int p_line) {
+ ERR_FAIL_INDEX(p_line, get_line_count());
+ if (!is_line_folded(p_line) && !is_line_hidden(p_line)) {
+ return;
+ }
+
+ int fold_start = p_line;
+ for (; fold_start > 0; fold_start--) {
+ if (is_line_folded(fold_start)) {
+ break;
+ }
+ }
+ fold_start = is_line_folded(fold_start) ? fold_start : p_line;
+
+ for (int i = fold_start + 1; i < get_line_count(); i++) {
+ if (!is_line_hidden(i)) {
+ break;
+ }
+ set_line_as_hidden(i, false);
+ }
+ update();
+}
+
+void CodeEdit::fold_all_lines() {
+ for (int i = 0; i < get_line_count(); i++) {
+ fold_line(i);
+ }
+ update();
+}
+
+void CodeEdit::unfold_all_lines() {
+ unhide_all_lines();
+}
+
+void CodeEdit::toggle_foldable_line(int p_line) {
+ ERR_FAIL_INDEX(p_line, get_line_count());
+ if (is_line_folded(p_line)) {
+ unfold_line(p_line);
+ return;
+ }
+ fold_line(p_line);
+}
+
+bool CodeEdit::is_line_folded(int p_line) const {
+ ERR_FAIL_INDEX_V(p_line, get_line_count(), false);
+ return p_line + 1 < get_line_count() && !is_line_hidden(p_line) && is_line_hidden(p_line + 1);
+}
+
+TypedArray<int> CodeEdit::get_folded_lines() const {
+ TypedArray<int> folded_lines;
+ for (int i = 0; i < get_line_count(); i++) {
+ if (is_line_folded(i)) {
+ folded_lines.push_back(i);
+ }
+ }
+ return folded_lines;
+}
+
+/* Delimiters */
+// Strings
+void CodeEdit::add_string_delimiter(const String &p_start_key, const String &p_end_key, bool p_line_only) {
+ _add_delimiter(p_start_key, p_end_key, p_line_only, TYPE_STRING);
+}
+
+void CodeEdit::remove_string_delimiter(const String &p_start_key) {
+ _remove_delimiter(p_start_key, TYPE_STRING);
+}
+
+bool CodeEdit::has_string_delimiter(const String &p_start_key) const {
+ return _has_delimiter(p_start_key, TYPE_STRING);
+}
+
+void CodeEdit::set_string_delimiters(const TypedArray<String> &p_string_delimiters) {
+ _set_delimiters(p_string_delimiters, TYPE_STRING);
+}
+
+void CodeEdit::clear_string_delimiters() {
+ _clear_delimiters(TYPE_STRING);
+}
+
+TypedArray<String> CodeEdit::get_string_delimiters() const {
+ return _get_delimiters(TYPE_STRING);
+}
+
+int CodeEdit::is_in_string(int p_line, int p_column) const {
+ return _is_in_delimiter(p_line, p_column, TYPE_STRING);
+}
+
+// Comments
+void CodeEdit::add_comment_delimiter(const String &p_start_key, const String &p_end_key, bool p_line_only) {
+ _add_delimiter(p_start_key, p_end_key, p_line_only, TYPE_COMMENT);
+}
+
+void CodeEdit::remove_comment_delimiter(const String &p_start_key) {
+ _remove_delimiter(p_start_key, TYPE_COMMENT);
+}
+
+bool CodeEdit::has_comment_delimiter(const String &p_start_key) const {
+ return _has_delimiter(p_start_key, TYPE_COMMENT);
+}
+
+void CodeEdit::set_comment_delimiters(const TypedArray<String> &p_comment_delimiters) {
+ _set_delimiters(p_comment_delimiters, TYPE_COMMENT);
+}
+
+void CodeEdit::clear_comment_delimiters() {
+ _clear_delimiters(TYPE_COMMENT);
+}
+
+TypedArray<String> CodeEdit::get_comment_delimiters() const {
+ return _get_delimiters(TYPE_COMMENT);
+}
+
+int CodeEdit::is_in_comment(int p_line, int p_column) const {
+ return _is_in_delimiter(p_line, p_column, TYPE_COMMENT);
+}
+
+String CodeEdit::get_delimiter_start_key(int p_delimiter_idx) const {
+ ERR_FAIL_INDEX_V(p_delimiter_idx, delimiters.size(), "");
+ return delimiters[p_delimiter_idx].start_key;
+}
+
+String CodeEdit::get_delimiter_end_key(int p_delimiter_idx) const {
+ ERR_FAIL_INDEX_V(p_delimiter_idx, delimiters.size(), "");
+ return delimiters[p_delimiter_idx].end_key;
+}
+
+Point2 CodeEdit::get_delimiter_start_position(int p_line, int p_column) const {
+ if (delimiters.size() == 0) {
+ return Point2(-1, -1);
+ }
+ ERR_FAIL_INDEX_V(p_line, get_line_count(), Point2(-1, -1));
+ ERR_FAIL_COND_V(p_column - 1 > get_line(p_line).size(), Point2(-1, -1));
+
+ Point2 start_position;
+ start_position.y = -1;
+ start_position.x = -1;
+
+ bool in_region = ((p_line <= 0 || delimiter_cache[p_line - 1].size() < 1) ? -1 : delimiter_cache[p_line - 1].back()->value()) != -1;
+
+ /* Check the keys for this line. */
+ for (Map<int, int>::Element *E = delimiter_cache[p_line].front(); E; E = E->next()) {
+ if (E->key() > p_column) {
+ break;
+ }
+ in_region = E->value() != -1;
+ start_position.x = in_region ? E->key() : -1;
+ }
+
+ /* Region was found on this line and is not a multiline continuation. */
+ if (start_position.x != -1 && start_position.x != get_line(p_line).length() + 1) {
+ start_position.y = p_line;
+ return start_position;
+ }
+
+ /* Not in a region */
+ if (!in_region) {
+ return start_position;
+ }
+
+ /* Region starts on a previous line */
+ for (int i = p_line - 1; i >= 0; i--) {
+ if (delimiter_cache[i].size() < 1) {
+ continue;
+ }
+ start_position.y = i;
+ start_position.x = delimiter_cache[i].back()->key();
+
+ /* Make sure it's not a multiline continuation. */
+ if (start_position.x != get_line(i).length() + 1) {
+ break;
+ }
+ }
+ return start_position;
+}
+
+Point2 CodeEdit::get_delimiter_end_position(int p_line, int p_column) const {
+ if (delimiters.size() == 0) {
+ return Point2(-1, -1);
+ }
+ ERR_FAIL_INDEX_V(p_line, get_line_count(), Point2(-1, -1));
+ ERR_FAIL_COND_V(p_column - 1 > get_line(p_line).size(), Point2(-1, -1));
+
+ Point2 end_position;
+ end_position.y = -1;
+ end_position.x = -1;
+
+ int region = (p_line <= 0 || delimiter_cache[p_line - 1].size() < 1) ? -1 : delimiter_cache[p_line - 1].back()->value();
+
+ /* Check the keys for this line. */
+ for (Map<int, int>::Element *E = delimiter_cache[p_line].front(); E; E = E->next()) {
+ end_position.x = (E->value() == -1) ? E->key() : -1;
+ if (E->key() > p_column) {
+ break;
+ }
+ region = E->value();
+ }
+
+ /* Region was found on this line and is not a multiline continuation. */
+ if (region != -1 && end_position.x != -1 && (delimiters[region].line_only || end_position.x != get_line(p_line).length() + 1)) {
+ end_position.y = p_line;
+ return end_position;
+ }
+
+ /* Not in a region */
+ if (region == -1) {
+ end_position.x = -1;
+ return end_position;
+ }
+
+ /* Region ends on a later line */
+ for (int i = p_line + 1; i < get_line_count(); i++) {
+ if (delimiter_cache[i].size() < 1 || delimiter_cache[i].front()->value() != -1) {
+ continue;
+ }
+ end_position.x = delimiter_cache[i].front()->key();
+
+ /* Make sure it's not a multiline continuation. */
+ if (get_line(i).length() > 0 && end_position.x != get_line(i).length() + 1) {
+ end_position.y = i;
+ break;
+ }
+ end_position.x = -1;
+ }
+ return end_position;
+}
+
+/* Code hint */
+void CodeEdit::set_code_hint(const String &p_hint) {
+ code_hint = p_hint;
+ code_hint_xpos = -0xFFFF;
+ update();
+}
+
+void CodeEdit::set_code_hint_draw_below(bool p_below) {
+ code_hint_draw_below = p_below;
+ update();
+}
+
+/* Code Completion */
+void CodeEdit::set_code_completion_enabled(bool p_enable) {
+ code_completion_enabled = p_enable;
+}
+
+bool CodeEdit::is_code_completion_enabled() const {
+ return code_completion_enabled;
+}
+
+void CodeEdit::set_code_completion_prefixes(const TypedArray<String> &p_prefixes) {
+ code_completion_prefixes.clear();
+ for (int i = 0; i < p_prefixes.size(); i++) {
+ code_completion_prefixes.insert(p_prefixes[i]);
+ }
+}
+
+TypedArray<String> CodeEdit::get_code_completion_prefixes() const {
+ TypedArray<String> prefixes;
+ for (Set<String>::Element *E = code_completion_prefixes.front(); E; E = E->next()) {
+ prefixes.push_back(E->get());
+ }
+ return prefixes;
+}
+
+String CodeEdit::get_text_for_code_completion() const {
+ StringBuilder completion_text;
+ const int text_size = get_line_count();
+ for (int i = 0; i < text_size; i++) {
+ String line = get_line(i);
+
+ if (i == cursor_get_line()) {
+ completion_text += line.substr(0, cursor_get_column());
+ /* Not unicode, represents the caret. */
+ completion_text += String::chr(0xFFFF);
+ completion_text += line.substr(cursor_get_column(), line.size());
+ } else {
+ completion_text += line;
+ }
+
+ if (i != text_size - 1) {
+ completion_text += "\n";
+ }
+ }
+ return completion_text.as_string();
+}
+
+void CodeEdit::request_code_completion(bool p_force) {
+ ScriptInstance *si = get_script_instance();
+ if (si && si->has_method("_request_code_completion")) {
+ si->call("_request_code_completion", p_force);
+ return;
+ }
+
+ /* Don't re-query if all existing options are quoted types, eg path, signal. */
+ bool ignored = code_completion_active && !code_completion_options.is_empty();
+ if (ignored) {
+ ScriptCodeCompletionOption::Kind kind = ScriptCodeCompletionOption::KIND_PLAIN_TEXT;
+ const ScriptCodeCompletionOption *previous_option = nullptr;
+ for (int i = 0; i < code_completion_options.size(); i++) {
+ const ScriptCodeCompletionOption &current_option = code_completion_options[i];
+ if (!previous_option) {
+ previous_option = &current_option;
+ kind = current_option.kind;
+ }
+ if (previous_option->kind != current_option.kind) {
+ ignored = false;
+ break;
+ }
+ }
+ ignored = ignored && (kind == ScriptCodeCompletionOption::KIND_FILE_PATH || kind == ScriptCodeCompletionOption::KIND_NODE_PATH || kind == ScriptCodeCompletionOption::KIND_SIGNAL);
+ }
+
+ if (ignored) {
+ return;
+ }
+
+ if (p_force) {
+ emit_signal("request_code_completion");
+ return;
+ }
+
+ String line = get_line(cursor_get_line());
+ int ofs = CLAMP(cursor_get_column(), 0, line.length());
+
+ if (ofs > 0 && (is_in_string(cursor_get_line(), ofs) != -1 || _is_char(line[ofs - 1]) || code_completion_prefixes.has(String::chr(line[ofs - 1])))) {
+ emit_signal("request_code_completion");
+ } else if (ofs > 1 && line[ofs - 1] == ' ' && code_completion_prefixes.has(String::chr(line[ofs - 2]))) {
+ emit_signal("request_code_completion");
+ }
+}
+
+void CodeEdit::add_code_completion_option(CodeCompletionKind p_type, const String &p_display_text, const String &p_insert_text, const Color &p_text_color, const RES &p_icon, const Variant &p_value) {
+ ScriptCodeCompletionOption completion_option;
+ completion_option.kind = (ScriptCodeCompletionOption::Kind)p_type;
+ completion_option.display = p_display_text;
+ completion_option.insert_text = p_insert_text;
+ completion_option.font_color = p_text_color;
+ completion_option.icon = p_icon;
+ completion_option.default_value = p_value;
+ code_completion_option_submitted.push_back(completion_option);
+}
+
+void CodeEdit::update_code_completion_options(bool p_forced) {
+ code_completion_forced = p_forced;
+ code_completion_option_sources = code_completion_option_submitted;
+ code_completion_option_submitted.clear();
+ _filter_code_completion_candidates();
+}
+
+TypedArray<Dictionary> CodeEdit::get_code_completion_options() const {
+ if (!code_completion_active) {
+ return TypedArray<Dictionary>();
+ }
+
+ TypedArray<Dictionary> completion_options;
+ completion_options.resize(code_completion_options.size());
+ for (int i = 0; i < code_completion_options.size(); i++) {
+ Dictionary option;
+ option["kind"] = code_completion_options[i].kind;
+ option["display_text"] = code_completion_options[i].display;
+ option["insert_text"] = code_completion_options[i].insert_text;
+ option["font_color"] = code_completion_options[i].font_color;
+ option["icon"] = code_completion_options[i].icon;
+ option["default_value"] = code_completion_options[i].default_value;
+ completion_options[i] = option;
+ }
+ return completion_options;
+}
+
+Dictionary CodeEdit::get_code_completion_option(int p_index) const {
+ if (!code_completion_active) {
+ return Dictionary();
+ }
+ ERR_FAIL_INDEX_V(p_index, code_completion_options.size(), Dictionary());
+
+ Dictionary option;
+ option["kind"] = code_completion_options[p_index].kind;
+ option["display_text"] = code_completion_options[p_index].display;
+ option["insert_text"] = code_completion_options[p_index].insert_text;
+ option["font_color"] = code_completion_options[p_index].font_color;
+ option["icon"] = code_completion_options[p_index].icon;
+ option["default_value"] = code_completion_options[p_index].default_value;
+ return option;
+}
+
+int CodeEdit::get_code_completion_selected_index() const {
+ return (code_completion_active) ? code_completion_current_selected : -1;
+}
+
+void CodeEdit::set_code_completion_selected_index(int p_index) {
+ if (!code_completion_active) {
+ return;
+ }
+ ERR_FAIL_INDEX(p_index, code_completion_options.size());
+ code_completion_current_selected = p_index;
+ update();
+}
+
+void CodeEdit::confirm_code_completion(bool p_replace) {
+ if (is_readonly() || !code_completion_active) {
+ return;
+ }
+
+ ScriptInstance *si = get_script_instance();
+ if (si && si->has_method("_confirm_code_completion")) {
+ si->call("_confirm_code_completion", p_replace);
+ return;
+ }
+ begin_complex_operation();
+
+ int caret_line = cursor_get_line();
+
+ const String &insert_text = code_completion_options[code_completion_current_selected].insert_text;
+ const String &display_text = code_completion_options[code_completion_current_selected].display;
+
+ if (p_replace) {
+ /* Find end of current section */
+ const String line = get_line(caret_line);
+ int caret_col = cursor_get_column();
+ int caret_remove_line = caret_line;
+
+ bool merge_text = true;
+ int in_string = is_in_string(caret_line, caret_col);
+ if (in_string != -1) {
+ Point2 string_end = get_delimiter_end_position(caret_line, caret_col);
+ if (string_end.x != -1) {
+ merge_text = false;
+ caret_remove_line = string_end.y;
+ caret_col = string_end.x - 1;
+ }
+ }
+
+ if (merge_text) {
+ for (; caret_col < line.length(); caret_col++) {
+ if (!_is_char(line[caret_col])) {
+ break;
+ }
+ }
+ }
+
+ /* Replace. */
+ _remove_text(caret_line, cursor_get_column() - code_completion_base.length(), caret_remove_line, caret_col);
+ cursor_set_column(cursor_get_column() - code_completion_base.length(), false);
+ insert_text_at_cursor(insert_text);
+ } else {
+ /* Get first non-matching char. */
+ const String line = get_line(caret_line);
+ int caret_col = cursor_get_column();
+ int matching_chars = code_completion_base.length();
+ for (; matching_chars <= insert_text.length(); matching_chars++) {
+ if (caret_col >= line.length() || line[caret_col] != insert_text[matching_chars]) {
+ break;
+ }
+ caret_col++;
+ }
+
+ /* Remove base completion text. */
+ _remove_text(caret_line, cursor_get_column() - code_completion_base.length(), caret_line, cursor_get_column());
+ cursor_set_column(cursor_get_column() - code_completion_base.length(), false);
+
+ /* Merge with text. */
+ insert_text_at_cursor(insert_text.substr(0, code_completion_base.length()));
+ cursor_set_column(caret_col, false);
+ insert_text_at_cursor(insert_text.substr(matching_chars));
+ }
+
+ /* TODO: merge with autobrace completion, when in CodeEdit. */
+ /* Handle merging of symbols eg strings, brackets. */
+ const String line = get_line(caret_line);
+ char32_t next_char = line[cursor_get_column()];
+ char32_t last_completion_char = insert_text[insert_text.length() - 1];
+ char32_t last_completion_char_display = display_text[display_text.length() - 1];
+
+ if ((last_completion_char == '"' || last_completion_char == '\'') && (last_completion_char == next_char || last_completion_char_display == next_char)) {
+ _remove_text(caret_line, cursor_get_column(), caret_line, cursor_get_column() + 1);
+ }
+
+ if (last_completion_char == '(') {
+ if (next_char == last_completion_char) {
+ _remove_text(caret_line, cursor_get_column() - 1, caret_line, cursor_get_column());
+ } else if (auto_brace_completion_enabled) {
+ insert_text_at_cursor(")");
+ cursor_set_column(cursor_get_column() - 1);
+ }
+ } else if (last_completion_char == ')' && next_char == '(') {
+ _remove_text(caret_line, cursor_get_column() - 2, caret_line, cursor_get_column());
+ if (line[cursor_get_column() + 1] != ')') {
+ cursor_set_column(cursor_get_column() - 1);
+ }
+ }
+
+ end_complex_operation();
+
+ cancel_code_completion();
+ if (last_completion_char == '(') {
+ request_code_completion();
+ }
+}
+
+void CodeEdit::cancel_code_completion() {
+ if (!code_completion_active) {
+ return;
+ }
+ code_completion_forced = false;
+ code_completion_active = false;
+ update();
+}
+
void CodeEdit::_bind_methods() {
+ /* Indent management */
+ ClassDB::bind_method(D_METHOD("set_indent_size", "size"), &CodeEdit::set_indent_size);
+ ClassDB::bind_method(D_METHOD("get_indent_size"), &CodeEdit::get_indent_size);
+
+ ClassDB::bind_method(D_METHOD("set_indent_using_spaces", "use_spaces"), &CodeEdit::set_indent_using_spaces);
+ ClassDB::bind_method(D_METHOD("is_indent_using_spaces"), &CodeEdit::is_indent_using_spaces);
+
+ ClassDB::bind_method(D_METHOD("set_auto_indent_enabled", "enable"), &CodeEdit::set_auto_indent_enabled);
+ ClassDB::bind_method(D_METHOD("is_auto_indent_enabled"), &CodeEdit::is_auto_indent_enabled);
+
+ ClassDB::bind_method(D_METHOD("set_auto_indent_prefixes", "prefixes"), &CodeEdit::set_auto_indent_prefixes);
+ ClassDB::bind_method(D_METHOD("get_auto_indent_prefixes"), &CodeEdit::get_auto_indent_prefixes);
+
+ ClassDB::bind_method(D_METHOD("do_indent"), &CodeEdit::do_indent);
+ ClassDB::bind_method(D_METHOD("do_unindent"), &CodeEdit::do_unindent);
+
+ ClassDB::bind_method(D_METHOD("indent_lines"), &CodeEdit::indent_lines);
+ ClassDB::bind_method(D_METHOD("unindent_lines"), &CodeEdit::unindent_lines);
+
/* Main Gutter */
ClassDB::bind_method(D_METHOD("_main_gutter_draw_callback"), &CodeEdit::_main_gutter_draw_callback);
@@ -320,6 +1806,91 @@ void CodeEdit::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_draw_fold_gutter", "enable"), &CodeEdit::set_draw_fold_gutter);
ClassDB::bind_method(D_METHOD("is_drawing_fold_gutter"), &CodeEdit::is_drawing_fold_gutter);
+ /* Line folding */
+ ClassDB::bind_method(D_METHOD("set_line_folding_enabled", "enabled"), &CodeEdit::set_line_folding_enabled);
+ ClassDB::bind_method(D_METHOD("is_line_folding_enabled"), &CodeEdit::is_line_folding_enabled);
+
+ ClassDB::bind_method(D_METHOD("can_fold_line", "line"), &CodeEdit::can_fold_line);
+
+ ClassDB::bind_method(D_METHOD("fold_line", "line"), &CodeEdit::fold_line);
+ ClassDB::bind_method(D_METHOD("unfold_line", "line"), &CodeEdit::unfold_line);
+ ClassDB::bind_method(D_METHOD("fold_all_lines"), &CodeEdit::fold_all_lines);
+ ClassDB::bind_method(D_METHOD("unfold_all_lines"), &CodeEdit::unfold_all_lines);
+ ClassDB::bind_method(D_METHOD("toggle_foldable_line", "line"), &CodeEdit::toggle_foldable_line);
+
+ ClassDB::bind_method(D_METHOD("is_line_folded", "line"), &CodeEdit::is_line_folded);
+ ClassDB::bind_method(D_METHOD("get_folded_lines"), &CodeEdit::get_folded_lines);
+
+ /* Delimiters */
+ // Strings
+ ClassDB::bind_method(D_METHOD("add_string_delimiter", "start_key", "end_key", "line_only"), &CodeEdit::add_string_delimiter, DEFVAL(false));
+ ClassDB::bind_method(D_METHOD("remove_string_delimiter", "start_key"), &CodeEdit::remove_string_delimiter);
+ ClassDB::bind_method(D_METHOD("has_string_delimiter", "start_key"), &CodeEdit::has_string_delimiter);
+
+ ClassDB::bind_method(D_METHOD("set_string_delimiters", "string_delimiters"), &CodeEdit::set_string_delimiters);
+ ClassDB::bind_method(D_METHOD("clear_string_delimiters"), &CodeEdit::clear_string_delimiters);
+ ClassDB::bind_method(D_METHOD("get_string_delimiters"), &CodeEdit::get_string_delimiters);
+
+ ClassDB::bind_method(D_METHOD("is_in_string", "line", "column"), &CodeEdit::is_in_string, DEFVAL(-1));
+
+ // Comments
+ ClassDB::bind_method(D_METHOD("add_comment_delimiter", "start_key", "end_key", "line_only"), &CodeEdit::add_comment_delimiter, DEFVAL(false));
+ ClassDB::bind_method(D_METHOD("remove_comment_delimiter", "start_key"), &CodeEdit::remove_comment_delimiter);
+ ClassDB::bind_method(D_METHOD("has_comment_delimiter", "start_key"), &CodeEdit::has_comment_delimiter);
+
+ ClassDB::bind_method(D_METHOD("set_comment_delimiters", "comment_delimiters"), &CodeEdit::set_comment_delimiters);
+ ClassDB::bind_method(D_METHOD("clear_comment_delimiters"), &CodeEdit::clear_comment_delimiters);
+ ClassDB::bind_method(D_METHOD("get_comment_delimiters"), &CodeEdit::get_comment_delimiters);
+
+ ClassDB::bind_method(D_METHOD("is_in_comment", "line", "column"), &CodeEdit::is_in_comment, DEFVAL(-1));
+
+ // Util
+ ClassDB::bind_method(D_METHOD("get_delimiter_start_key", "delimiter_index"), &CodeEdit::get_delimiter_start_key);
+ ClassDB::bind_method(D_METHOD("get_delimiter_end_key", "delimiter_index"), &CodeEdit::get_delimiter_end_key);
+
+ ClassDB::bind_method(D_METHOD("get_delimiter_start_position", "line", "column"), &CodeEdit::get_delimiter_start_position);
+ ClassDB::bind_method(D_METHOD("get_delimiter_end_position", "line", "column"), &CodeEdit::get_delimiter_end_position);
+
+ /* Code hint */
+ ClassDB::bind_method(D_METHOD("set_code_hint", "code_hint"), &CodeEdit::set_code_hint);
+ ClassDB::bind_method(D_METHOD("set_code_hint_draw_below", "draw_below"), &CodeEdit::set_code_hint_draw_below);
+
+ /* Code Completion */
+ BIND_ENUM_CONSTANT(KIND_CLASS);
+ BIND_ENUM_CONSTANT(KIND_FUNCTION);
+ BIND_ENUM_CONSTANT(KIND_SIGNAL);
+ BIND_ENUM_CONSTANT(KIND_VARIABLE);
+ BIND_ENUM_CONSTANT(KIND_MEMBER);
+ BIND_ENUM_CONSTANT(KIND_ENUM);
+ BIND_ENUM_CONSTANT(KIND_CONSTANT);
+ BIND_ENUM_CONSTANT(KIND_NODE_PATH);
+ BIND_ENUM_CONSTANT(KIND_FILE_PATH);
+ BIND_ENUM_CONSTANT(KIND_PLAIN_TEXT);
+
+ ClassDB::bind_method(D_METHOD("get_text_for_code_completion"), &CodeEdit::get_text_for_code_completion);
+ ClassDB::bind_method(D_METHOD("request_code_completion", "force"), &CodeEdit::request_code_completion, DEFVAL(false));
+ ClassDB::bind_method(D_METHOD("add_code_completion_option", "type", "display_text", "insert_text", "text_color", "icon", "value"), &CodeEdit::add_code_completion_option, DEFVAL(Color(1, 1, 1)), DEFVAL(RES()), DEFVAL(Variant::NIL));
+ ClassDB::bind_method(D_METHOD("update_code_completion_options", "force"), &CodeEdit::update_code_completion_options);
+ ClassDB::bind_method(D_METHOD("get_code_completion_options"), &CodeEdit::get_code_completion_options);
+ ClassDB::bind_method(D_METHOD("get_code_completion_option", "index"), &CodeEdit::get_code_completion_option);
+ ClassDB::bind_method(D_METHOD("get_code_completion_selected_index"), &CodeEdit::get_code_completion_selected_index);
+ ClassDB::bind_method(D_METHOD("set_code_completion_selected_index", "index"), &CodeEdit::set_code_completion_selected_index);
+
+ ClassDB::bind_method(D_METHOD("confirm_code_completion", "replace"), &CodeEdit::confirm_code_completion, DEFVAL(false));
+ ClassDB::bind_method(D_METHOD("cancel_code_completion"), &CodeEdit::cancel_code_completion);
+
+ ClassDB::bind_method(D_METHOD("set_code_completion_enabled", "enable"), &CodeEdit::set_code_completion_enabled);
+ ClassDB::bind_method(D_METHOD("is_code_completion_enabled"), &CodeEdit::is_code_completion_enabled);
+
+ ClassDB::bind_method(D_METHOD("set_code_completion_prefixes", "prefixes"), &CodeEdit::set_code_completion_prefixes);
+ ClassDB::bind_method(D_METHOD("get_code_comletion_prefixes"), &CodeEdit::get_code_completion_prefixes);
+
+ // Overridable
+ BIND_VMETHOD(MethodInfo("_confirm_code_completion", PropertyInfo(Variant::BOOL, "replace")));
+ BIND_VMETHOD(MethodInfo("_request_code_completion", PropertyInfo(Variant::BOOL, "force")));
+ BIND_VMETHOD(MethodInfo(Variant::ARRAY, "_filter_code_completion_candidates", PropertyInfo(Variant::ARRAY, "candidates")));
+
+ /* Inspector */
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "draw_breakpoints_gutter"), "set_draw_breakpoints_gutter", "is_drawing_breakpoints_gutter");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "draw_bookmarks"), "set_draw_bookmarks_gutter", "is_drawing_bookmarks_gutter");
@@ -331,7 +1902,25 @@ void CodeEdit::_bind_methods() {
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "draw_fold_gutter"), "set_draw_fold_gutter", "is_drawing_fold_gutter");
+ ADD_PROPERTY(PropertyInfo(Variant::BOOL, "line_folding"), "set_line_folding_enabled", "is_line_folding_enabled");
+
+ ADD_GROUP("Delimiters", "delimiter_");
+ ADD_PROPERTY(PropertyInfo(Variant::PACKED_STRING_ARRAY, "delimiter_strings"), "set_string_delimiters", "get_string_delimiters");
+ ADD_PROPERTY(PropertyInfo(Variant::PACKED_STRING_ARRAY, "delimiter_comments"), "set_comment_delimiters", "get_comment_delimiters");
+
+ ADD_GROUP("Code Completion", "code_completion_");
+ ADD_PROPERTY(PropertyInfo(Variant::BOOL, "code_completion_enabled"), "set_code_completion_enabled", "is_code_completion_enabled");
+ ADD_PROPERTY(PropertyInfo(Variant::PACKED_STRING_ARRAY, "code_completion_prefixes"), "set_code_completion_prefixes", "get_code_comletion_prefixes");
+
+ ADD_GROUP("Indentation", "indent_");
+ ADD_PROPERTY(PropertyInfo(Variant::INT, "indent_size"), "set_indent_size", "get_indent_size");
+ ADD_PROPERTY(PropertyInfo(Variant::BOOL, "indent_use_spaces"), "set_indent_using_spaces", "is_indent_using_spaces");
+ ADD_PROPERTY(PropertyInfo(Variant::BOOL, "indent_automatic"), "set_auto_indent_enabled", "is_auto_indent_enabled");
+ ADD_PROPERTY(PropertyInfo(Variant::PACKED_STRING_ARRAY, "indent_automatic_prefixes"), "set_auto_indent_prefixes", "get_auto_indent_prefixes");
+
+ /* Signals */
ADD_SIGNAL(MethodInfo("breakpoint_toggled", PropertyInfo(Variant::INT, "line")));
+ ADD_SIGNAL(MethodInfo("request_code_completion"));
}
void CodeEdit::_gutter_clicked(int p_line, int p_gutter) {
@@ -351,16 +1940,578 @@ void CodeEdit::_gutter_clicked(int p_line, int p_gutter) {
}
if (p_gutter == fold_gutter) {
- if (is_folded(p_line)) {
+ if (is_line_folded(p_line)) {
unfold_line(p_line);
- } else if (can_fold(p_line)) {
+ } else if (can_fold_line(p_line)) {
fold_line(p_line);
}
return;
}
}
+void CodeEdit::_update_gutter_indexes() {
+ for (int i = 0; i < get_gutter_count(); i++) {
+ if (get_gutter_name(i) == "main_gutter") {
+ main_gutter = i;
+ continue;
+ }
+
+ if (get_gutter_name(i) == "line_numbers") {
+ line_number_gutter = i;
+ continue;
+ }
+
+ if (get_gutter_name(i) == "fold_gutter") {
+ fold_gutter = i;
+ continue;
+ }
+ }
+}
+
+/* Delimiters */
+void CodeEdit::_update_delimiter_cache(int p_from_line, int p_to_line) {
+ if (delimiters.size() == 0) {
+ return;
+ }
+
+ int line_count = get_line_count();
+ if (p_to_line == -1) {
+ p_to_line = line_count;
+ }
+
+ int start_line = MIN(p_from_line, p_to_line);
+ int end_line = MAX(p_from_line, p_to_line);
+
+ /* Make sure delimiter_cache has all the lines. */
+ if (start_line != end_line) {
+ if (p_to_line < p_from_line) {
+ for (int i = end_line; i > start_line; i--) {
+ delimiter_cache.remove(i);
+ }
+ } else {
+ for (int i = start_line; i < end_line; i++) {
+ delimiter_cache.insert(i, Map<int, int>());
+ }
+ }
+ }
+
+ int in_region = -1;
+ for (int i = start_line; i < MIN(end_line + 1, line_count); i++) {
+ int current_end_region = (i <= 0 || delimiter_cache[i].size() < 1) ? -1 : delimiter_cache[i].back()->value();
+ in_region = (i <= 0 || delimiter_cache[i - 1].size() < 1) ? -1 : delimiter_cache[i - 1].back()->value();
+
+ const String &str = get_line(i);
+ const int line_length = str.length();
+ delimiter_cache.write[i].clear();
+
+ if (str.length() == 0) {
+ if (in_region != -1) {
+ delimiter_cache.write[i][0] = in_region;
+ }
+ if (i == end_line && current_end_region != in_region) {
+ end_line++;
+ end_line = MIN(end_line, line_count);
+ }
+ continue;
+ }
+
+ int end_region = -1;
+ for (int j = 0; j < line_length; j++) {
+ int from = j;
+ for (; from < line_length; from++) {
+ if (str[from] == '\\') {
+ from++;
+ continue;
+ }
+ break;
+ }
+
+ /* check if we are in entering a region */
+ bool same_line = false;
+ if (in_region == -1) {
+ for (int d = 0; d < delimiters.size(); d++) {
+ /* check there is enough room */
+ int chars_left = line_length - from;
+ int start_key_length = delimiters[d].start_key.length();
+ int end_key_length = delimiters[d].end_key.length();
+ if (chars_left < start_key_length) {
+ continue;
+ }
+
+ /* search the line */
+ bool match = true;
+ const char32_t *start_key = delimiters[d].start_key.get_data();
+ for (int k = 0; k < start_key_length; k++) {
+ if (start_key[k] != str[from + k]) {
+ match = false;
+ break;
+ }
+ }
+ if (!match) {
+ continue;
+ }
+ same_line = true;
+ in_region = d;
+ delimiter_cache.write[i][from + 1] = d;
+ from += start_key_length;
+
+ /* check if it's the whole line */
+ if (end_key_length == 0 || delimiters[d].line_only || from + end_key_length > line_length) {
+ j = line_length;
+ if (delimiters[d].line_only) {
+ delimiter_cache.write[i][line_length + 1] = -1;
+ } else {
+ end_region = in_region;
+ }
+ }
+ break;
+ }
+
+ if (j == line_length || in_region == -1) {
+ continue;
+ }
+ }
+
+ /* if we are in one find the end key */
+ /* search the line */
+ int region_end_index = -1;
+ int end_key_length = delimiters[in_region].end_key.length();
+ const char32_t *end_key = delimiters[in_region].end_key.get_data();
+ for (; from < line_length; from++) {
+ if (line_length - from < end_key_length) {
+ break;
+ }
+
+ if (!is_symbol(str[from])) {
+ continue;
+ }
+
+ if (str[from] == '\\') {
+ from++;
+ continue;
+ }
+
+ region_end_index = from;
+ for (int k = 0; k < end_key_length; k++) {
+ if (end_key[k] != str[from + k]) {
+ region_end_index = -1;
+ break;
+ }
+ }
+
+ if (region_end_index != -1) {
+ break;
+ }
+ }
+
+ j = from + (end_key_length - 1);
+ end_region = (region_end_index == -1) ? in_region : -1;
+ if (!same_line || region_end_index != -1) {
+ delimiter_cache.write[i][j + 1] = end_region;
+ }
+ in_region = -1;
+ }
+
+ if (i == end_line && current_end_region != end_region) {
+ end_line++;
+ end_line = MIN(end_line, line_count);
+ }
+ }
+}
+
+int CodeEdit::_is_in_delimiter(int p_line, int p_column, DelimiterType p_type) const {
+ if (delimiters.size() == 0) {
+ return -1;
+ }
+ ERR_FAIL_INDEX_V(p_line, get_line_count(), 0);
+
+ int region = (p_line <= 0 || delimiter_cache[p_line - 1].size() < 1) ? -1 : delimiter_cache[p_line - 1].back()->value();
+ bool in_region = region != -1 && delimiters[region].type == p_type;
+ for (Map<int, int>::Element *E = delimiter_cache[p_line].front(); E; E = E->next()) {
+ /* If column is specified, loop untill the key is larger then the column. */
+ if (p_column != -1) {
+ if (E->key() > p_column) {
+ break;
+ }
+ in_region = E->value() != -1 && delimiters[E->value()].type == p_type;
+ region = in_region ? E->value() : -1;
+ continue;
+ }
+
+ /* If no column, calulate if the entire line is a region */
+ /* excluding whitespace. */
+ const String line = get_line(p_line);
+ if (!in_region) {
+ if (E->value() == -1 || delimiters[E->value()].type != p_type) {
+ break;
+ }
+
+ region = E->value();
+ in_region = true;
+ for (int i = E->key() - 2; i >= 0; i--) {
+ if (!_is_whitespace(line[i])) {
+ return -1;
+ }
+ }
+ }
+
+ if (delimiters[region].line_only) {
+ return region;
+ }
+
+ int end_col = E->key();
+ if (E->value() != -1) {
+ if (!E->next()) {
+ return region;
+ }
+ end_col = E->next()->key();
+ }
+
+ for (int i = end_col; i < line.length(); i++) {
+ if (!_is_whitespace(line[i])) {
+ return -1;
+ }
+ }
+ return region;
+ }
+ return in_region ? region : -1;
+}
+
+void CodeEdit::_add_delimiter(const String &p_start_key, const String &p_end_key, bool p_line_only, DelimiterType p_type) {
+ if (p_start_key.length() > 0) {
+ for (int i = 0; i < p_start_key.length(); i++) {
+ ERR_FAIL_COND_MSG(!is_symbol(p_start_key[i]), "delimiter must start with a symbol");
+ }
+ }
+
+ if (p_end_key.length() > 0) {
+ for (int i = 0; i < p_end_key.length(); i++) {
+ ERR_FAIL_COND_MSG(!is_symbol(p_end_key[i]), "delimiter must end with a symbol");
+ }
+ }
+
+ int at = 0;
+ for (int i = 0; i < delimiters.size(); i++) {
+ ERR_FAIL_COND_MSG(delimiters[i].start_key == p_start_key, "delimiter with start key '" + p_start_key + "' already exists.");
+ if (p_start_key.length() < delimiters[i].start_key.length()) {
+ at++;
+ }
+ }
+
+ Delimiter delimiter;
+ delimiter.type = p_type;
+ delimiter.start_key = p_start_key;
+ delimiter.end_key = p_end_key;
+ delimiter.line_only = p_line_only || p_end_key == "";
+ delimiters.insert(at, delimiter);
+ if (!setting_delimiters) {
+ delimiter_cache.clear();
+ _update_delimiter_cache();
+ }
+}
+
+void CodeEdit::_remove_delimiter(const String &p_start_key, DelimiterType p_type) {
+ for (int i = 0; i < delimiters.size(); i++) {
+ if (delimiters[i].start_key != p_start_key) {
+ continue;
+ }
+
+ if (delimiters[i].type != p_type) {
+ break;
+ }
+
+ delimiters.remove(i);
+ if (!setting_delimiters) {
+ delimiter_cache.clear();
+ _update_delimiter_cache();
+ }
+ break;
+ }
+}
+
+bool CodeEdit::_has_delimiter(const String &p_start_key, DelimiterType p_type) const {
+ for (int i = 0; i < delimiters.size(); i++) {
+ if (delimiters[i].start_key == p_start_key) {
+ return delimiters[i].type == p_type;
+ }
+ }
+ return false;
+}
+
+void CodeEdit::_set_delimiters(const TypedArray<String> &p_delimiters, DelimiterType p_type) {
+ setting_delimiters = true;
+ _clear_delimiters(p_type);
+
+ for (int i = 0; i < p_delimiters.size(); i++) {
+ String key = p_delimiters[i].is_null() ? "" : p_delimiters[i];
+
+ const String start_key = key.get_slice(" ", 0);
+ const String end_key = key.get_slice_count(" ") > 1 ? key.get_slice(" ", 1) : String();
+
+ _add_delimiter(start_key, end_key, end_key == "", p_type);
+ }
+ setting_delimiters = false;
+ _update_delimiter_cache();
+}
+
+void CodeEdit::_clear_delimiters(DelimiterType p_type) {
+ for (int i = delimiters.size() - 1; i >= 0; i--) {
+ if (delimiters[i].type == p_type) {
+ delimiters.remove(i);
+ }
+ }
+ delimiter_cache.clear();
+ if (!setting_delimiters) {
+ _update_delimiter_cache();
+ }
+}
+
+TypedArray<String> CodeEdit::_get_delimiters(DelimiterType p_type) const {
+ TypedArray<String> r_delimiters;
+ for (int i = 0; i < delimiters.size(); i++) {
+ if (delimiters[i].type != p_type) {
+ continue;
+ }
+ r_delimiters.push_back(delimiters[i].start_key + (delimiters[i].end_key.is_empty() ? "" : " " + delimiters[i].end_key));
+ }
+ return r_delimiters;
+}
+
+/* Code Completion */
+void CodeEdit::_filter_code_completion_candidates() {
+ ScriptInstance *si = get_script_instance();
+ if (si && si->has_method("_filter_code_completion_candidates")) {
+ code_completion_options.clear();
+ code_completion_base = "";
+
+ /* Build options argument. */
+ TypedArray<Dictionary> completion_options_sources;
+ completion_options_sources.resize(code_completion_option_sources.size());
+ int i = 0;
+ for (List<ScriptCodeCompletionOption>::Element *E = code_completion_option_sources.front(); E; E = E->next()) {
+ Dictionary option;
+ option["kind"] = E->get().kind;
+ option["display_text"] = E->get().display;
+ option["insert_text"] = E->get().insert_text;
+ option["font_color"] = E->get().font_color;
+ option["icon"] = E->get().icon;
+ option["default_value"] = E->get().default_value;
+ completion_options_sources[i] = option;
+ i++;
+ }
+
+ TypedArray<Dictionary> completion_options = si->call("_filter_code_completion_candidates", completion_options_sources);
+
+ /* No options to complete, cancel. */
+ if (completion_options.size() == 0) {
+ cancel_code_completion();
+ return;
+ }
+
+ /* Convert back into options. */
+ int max_width = 0;
+ for (i = 0; i < completion_options.size(); i++) {
+ ScriptCodeCompletionOption option;
+ option.kind = (ScriptCodeCompletionOption::Kind)(int)completion_options[i].get("kind");
+ option.display = completion_options[i].get("display_text");
+ option.insert_text = completion_options[i].get("insert_text");
+ option.font_color = completion_options[i].get("font_color");
+ option.icon = completion_options[i].get("icon");
+ option.default_value = completion_options[i].get("default_value");
+
+ max_width = MAX(max_width, cache.font->get_string_size(option.display).width);
+ code_completion_options.push_back(option);
+ }
+
+ code_completion_longest_line = MIN(max_width, code_completion_max_width);
+ code_completion_current_selected = 0;
+ code_completion_active = true;
+ update();
+ return;
+ }
+
+ const int caret_line = cursor_get_line();
+ const int caret_column = cursor_get_column();
+ const String line = get_line(caret_line);
+
+ if (caret_column > 0 && line[caret_column - 1] == '(' && !code_completion_forced) {
+ cancel_code_completion();
+ return;
+ }
+
+ /* Get string status, are we in one or at the close. */
+ int in_string = is_in_string(caret_line, caret_column);
+ int first_quote_col = -1;
+ if (in_string != -1) {
+ Point2 string_start_pos = get_delimiter_start_position(caret_line, caret_column);
+ first_quote_col = (string_start_pos.y == caret_line) ? string_start_pos.x : -1;
+ } else if (caret_column > 0) {
+ if (is_in_string(caret_line, caret_column - 1) != -1) {
+ first_quote_col = caret_column - 1;
+ }
+ }
+
+ int cofs = caret_column;
+ String string_to_complete;
+ bool prev_is_word = false;
+
+ /* Cancel if we are at the close of a string. */
+ if (in_string == -1 && first_quote_col == cofs - 1) {
+ cancel_code_completion();
+ return;
+ /* In a string, therefore we are trying to complete the string text. */
+ } else if (in_string != -1 && first_quote_col != -1) {
+ int key_length = delimiters[in_string].start_key.length();
+ string_to_complete = line.substr(first_quote_col - key_length, (cofs - first_quote_col) + key_length);
+ /* If we have a space, previous word might be a keyword. eg "func |". */
+ } else if (cofs > 0 && line[cofs - 1] == ' ') {
+ int ofs = cofs - 1;
+ while (ofs >= 0 && line[ofs] == ' ') {
+ ofs--;
+ }
+ prev_is_word = _is_char(line[ofs]);
+ /* Otherwise get current word and set cofs to the start. */
+ } else {
+ int start_cofs = cofs;
+ while (cofs > 0 && line[cofs - 1] > 32 && (line[cofs - 1] == '/' || _is_char(line[cofs - 1]))) {
+ cofs--;
+ }
+ string_to_complete = line.substr(cofs, start_cofs - cofs);
+ }
+
+ /* If all else fails, check for a prefix. */
+ /* Single space between caret and prefix is okay. */
+ bool prev_is_prefix = false;
+ if (cofs > 0 && code_completion_prefixes.has(String::chr(line[cofs - 1]))) {
+ prev_is_prefix = true;
+ } else if (cofs > 1 && line[cofs - 1] == ' ' && code_completion_prefixes.has(String::chr(line[cofs - 2]))) {
+ prev_is_prefix = true;
+ }
+
+ if (!prev_is_word && string_to_complete.is_empty() && (cofs == 0 || !prev_is_prefix)) {
+ cancel_code_completion();
+ return;
+ }
+
+ /* Filter Options. */
+ /* For now handle only tradional quoted strings. */
+ bool single_quote = in_string != -1 && first_quote_col > 0 && delimiters[in_string].start_key == "'";
+
+ code_completion_options.clear();
+ code_completion_base = string_to_complete;
+
+ Vector<ScriptCodeCompletionOption> completion_options_casei;
+ Vector<ScriptCodeCompletionOption> completion_options_subseq;
+ Vector<ScriptCodeCompletionOption> completion_options_subseq_casei;
+
+ int max_width = 0;
+ String string_to_complete_lower = string_to_complete.to_lower();
+ for (List<ScriptCodeCompletionOption>::Element *E = code_completion_option_sources.front(); E; E = E->next()) {
+ ScriptCodeCompletionOption &option = E->get();
+
+ if (single_quote && option.display.is_quoted()) {
+ option.display = option.display.unquote().quote("'");
+ }
+
+ if (in_string != -1) {
+ String quote = single_quote ? "'" : "\"";
+ option.display = option.display.unquote().quote(quote);
+ option.insert_text = option.insert_text.unquote().quote(quote);
+ }
+
+ if (option.display.length() == 0) {
+ continue;
+ }
+
+ if (string_to_complete.length() == 0) {
+ code_completion_options.push_back(option);
+ max_width = MAX(max_width, cache.font->get_string_size(option.display).width);
+ continue;
+ }
+
+ /* This code works the same as:
+
+ if (option.display.begins_with(s)) {
+ completion_options.push_back(option);
+ } else if (option.display.to_lower().begins_with(s.to_lower())) {
+ completion_options_casei.push_back(option);
+ } else if (s.is_subsequence_of(option.display)) {
+ completion_options_subseq.push_back(option);
+ } else if (s.is_subsequence_ofi(option.display)) {
+ completion_options_subseq_casei.push_back(option);
+ }
+
+ But is more performant due to being inlined and looping over the characters only once
+ */
+
+ String display_lower = option.display.to_lower();
+
+ const char32_t *ssq = &string_to_complete[0];
+ const char32_t *ssq_lower = &string_to_complete_lower[0];
+
+ const char32_t *tgt = &option.display[0];
+ const char32_t *tgt_lower = &display_lower[0];
+
+ const char32_t *ssq_last_tgt = nullptr;
+ const char32_t *ssq_lower_last_tgt = nullptr;
+
+ for (; *tgt; tgt++, tgt_lower++) {
+ if (*ssq == *tgt) {
+ ssq++;
+ ssq_last_tgt = tgt;
+ }
+ if (*ssq_lower == *tgt_lower) {
+ ssq_lower++;
+ ssq_lower_last_tgt = tgt;
+ }
+ }
+
+ /* Matched the whole subsequence in s. */
+ if (!*ssq) {
+ /* Finished matching in the first s.length() characters. */
+ if (ssq_last_tgt == &option.display[string_to_complete.length() - 1]) {
+ code_completion_options.push_back(option);
+ } else {
+ completion_options_subseq.push_back(option);
+ }
+ max_width = MAX(max_width, cache.font->get_string_size(option.display).width);
+ /* Matched the whole subsequence in s_lower. */
+ } else if (!*ssq_lower) {
+ /* Finished matching in the first s.length() characters. */
+ if (ssq_lower_last_tgt == &option.display[string_to_complete.length() - 1]) {
+ completion_options_casei.push_back(option);
+ } else {
+ completion_options_subseq_casei.push_back(option);
+ }
+ max_width = MAX(max_width, cache.font->get_string_size(option.display).width);
+ }
+ }
+
+ code_completion_options.append_array(completion_options_casei);
+ code_completion_options.append_array(completion_options_subseq);
+ code_completion_options.append_array(completion_options_subseq_casei);
+
+ /* No options to complete, cancel. */
+ if (code_completion_options.size() == 0) {
+ cancel_code_completion();
+ return;
+ }
+
+ /* A perfect match, stop completion. */
+ if (code_completion_options.size() == 1 && string_to_complete == code_completion_options[0].display) {
+ cancel_code_completion();
+ return;
+ }
+
+ code_completion_longest_line = MIN(max_width, code_completion_max_width);
+ code_completion_current_selected = 0;
+ code_completion_active = true;
+ update();
+}
+
void CodeEdit::_lines_edited_from(int p_from_line, int p_to_line) {
+ _update_delimiter_cache(p_from_line, p_to_line);
+
if (p_from_line == p_to_line) {
return;
}
@@ -392,26 +2543,13 @@ void CodeEdit::_lines_edited_from(int p_from_line, int p_to_line) {
}
}
-void CodeEdit::_update_gutter_indexes() {
- for (int i = 0; i < get_gutter_count(); i++) {
- if (get_gutter_name(i) == "main_gutter") {
- main_gutter = i;
- continue;
- }
-
- if (get_gutter_name(i) == "line_numbers") {
- line_number_gutter = i;
- continue;
- }
-
- if (get_gutter_name(i) == "fold_gutter") {
- fold_gutter = i;
- continue;
- }
- }
-}
-
CodeEdit::CodeEdit() {
+ /* Indent management */
+ auto_indent_prefixes.insert(':');
+ auto_indent_prefixes.insert('{');
+ auto_indent_prefixes.insert('[');
+ auto_indent_prefixes.insert('(');
+
/* Text Direction */
set_layout_direction(LAYOUT_DIRECTION_LTR);
set_text_direction(TEXT_DIRECTION_LTR);
diff --git a/scene/gui/code_edit.h b/scene/gui/code_edit.h
index d0c39ec0f1..25b518402b 100644
--- a/scene/gui/code_edit.h
+++ b/scene/gui/code_edit.h
@@ -36,7 +36,36 @@
class CodeEdit : public TextEdit {
GDCLASS(CodeEdit, TextEdit)
+public:
+ /* Keep enum in sync with: */
+ /* /core/object/script_language.h - ScriptCodeCompletionOption::Kind */
+ enum CodeCompletionKind {
+ KIND_CLASS,
+ KIND_FUNCTION,
+ KIND_SIGNAL,
+ KIND_VARIABLE,
+ KIND_MEMBER,
+ KIND_ENUM,
+ KIND_CONSTANT,
+ KIND_NODE_PATH,
+ KIND_FILE_PATH,
+ KIND_PLAIN_TEXT,
+ };
+
private:
+ /* Indent management */
+ int indent_size = 4;
+ String indent_text = "\t";
+
+ bool auto_indent = false;
+ Set<char32_t> auto_indent_prefixes;
+
+ bool indent_using_spaces = false;
+ int _calculate_spaces_till_next_left_indent(int p_column) const;
+ int _calculate_spaces_till_next_right_indent(int p_column) const;
+
+ void _new_line(bool p_split_current_line = true, bool p_above = false);
+
/* Main Gutter */
enum MainGutterType {
MAIN_GUTTER_BREAKPOINT = 0x01,
@@ -80,16 +109,137 @@ private:
void _fold_gutter_draw_callback(int p_line, int p_gutter, Rect2 p_region);
void _gutter_clicked(int p_line, int p_gutter);
- void _lines_edited_from(int p_from_line, int p_to_line);
-
void _update_gutter_indexes();
+ /* Line Folding */
+ bool line_folding_enabled = true;
+
+ /* Delimiters */
+ enum DelimiterType {
+ TYPE_STRING,
+ TYPE_COMMENT,
+ };
+
+ struct Delimiter {
+ DelimiterType type;
+ String start_key = "";
+ String end_key = "";
+ bool line_only = true;
+ };
+ bool setting_delimiters = false;
+ Vector<Delimiter> delimiters;
+ /*
+ * Vector entry per line, contains a Map of column numbers to delimiter index, -1 marks the end of a region.
+ * e.g the following text will be stored as so:
+ *
+ * 0: nothing here
+ * 1:
+ * 2: # test
+ * 3: "test" text "multiline
+ * 4:
+ * 5: test
+ * 6: string"
+ *
+ * Vector [
+ * 0 = []
+ * 1 = []
+ * 2 = [
+ * 1 = 1
+ * 6 = -1
+ * ]
+ * 3 = [
+ * 1 = 0
+ * 6 = -1
+ * 13 = 0
+ * ]
+ * 4 = [
+ * 0 = 0
+ * ]
+ * 5 = [
+ * 5 = 0
+ * ]
+ * 6 = [
+ * 7 = -1
+ * ]
+ * ]
+ */
+ Vector<Map<int, int>> delimiter_cache;
+
+ void _update_delimiter_cache(int p_from_line = 0, int p_to_line = -1);
+ int _is_in_delimiter(int p_line, int p_column, DelimiterType p_type) const;
+
+ void _add_delimiter(const String &p_start_key, const String &p_end_key, bool p_line_only, DelimiterType p_type);
+ void _remove_delimiter(const String &p_start_key, DelimiterType p_type);
+ bool _has_delimiter(const String &p_start_key, DelimiterType p_type) const;
+
+ void _set_delimiters(const TypedArray<String> &p_delimiters, DelimiterType p_type);
+ void _clear_delimiters(DelimiterType p_type);
+ TypedArray<String> _get_delimiters(DelimiterType p_type) const;
+
+ /* Code Hint */
+ String code_hint = "";
+
+ bool code_hint_draw_below = true;
+ int code_hint_xpos = -0xFFFF;
+
+ /* Code Completion */
+ bool code_completion_enabled = false;
+ bool code_completion_forced = false;
+
+ int code_completion_max_width = 0;
+ int code_completion_max_lines = 7;
+ int code_completion_scroll_width = 0;
+ Color code_completion_scroll_color = Color(0, 0, 0, 0);
+ Color code_completion_background_color = Color(0, 0, 0, 0);
+ Color code_completion_selected_color = Color(0, 0, 0, 0);
+ Color code_completion_existing_color = Color(0, 0, 0, 0);
+
+ bool code_completion_active = false;
+ Vector<ScriptCodeCompletionOption> code_completion_options;
+ int code_completion_line_ofs = 0;
+ int code_completion_current_selected = 0;
+ int code_completion_longest_line = 0;
+ Rect2i code_completion_rect;
+
+ Set<String> code_completion_prefixes;
+ List<ScriptCodeCompletionOption> code_completion_option_submitted;
+ List<ScriptCodeCompletionOption> code_completion_option_sources;
+ String code_completion_base;
+
+ void _filter_code_completion_candidates();
+
+ void _lines_edited_from(int p_from_line, int p_to_line);
+
protected:
+ void _gui_input(const Ref<InputEvent> &p_gui_input) override;
void _notification(int p_what);
static void _bind_methods();
public:
+ virtual CursorShape get_cursor_shape(const Point2 &p_pos = Point2i()) const override;
+
+ /* Indent management */
+ void set_indent_size(const int p_size);
+ int get_indent_size() const;
+
+ void set_indent_using_spaces(const bool p_use_spaces);
+ bool is_indent_using_spaces() const;
+
+ void set_auto_indent_enabled(bool p_enabled);
+ bool is_auto_indent_enabled() const;
+
+ void set_auto_indent_prefixes(const TypedArray<String> &p_prefixes);
+ TypedArray<String> get_auto_indent_prefixes() const;
+
+ void do_indent();
+ void do_unindent();
+
+ void indent_lines();
+ void unindent_lines();
+
+ virtual void backspace() override;
+
/* Main Gutter */
void set_draw_breakpoints_gutter(bool p_draw);
bool is_drawing_breakpoints_gutter() const;
@@ -128,8 +278,79 @@ public:
void set_draw_fold_gutter(bool p_draw);
bool is_drawing_fold_gutter() const;
+ /* Line Folding */
+ void set_line_folding_enabled(bool p_enabled);
+ bool is_line_folding_enabled() const;
+
+ bool can_fold_line(int p_line) const;
+
+ void fold_line(int p_line);
+ void unfold_line(int p_line);
+ void fold_all_lines();
+ void unfold_all_lines();
+ void toggle_foldable_line(int p_line);
+
+ bool is_line_folded(int p_line) const;
+ TypedArray<int> get_folded_lines() const;
+
+ /* Delimiters */
+ void add_string_delimiter(const String &p_start_key, const String &p_end_key, bool p_line_only = false);
+ void remove_string_delimiter(const String &p_start_key);
+ bool has_string_delimiter(const String &p_start_key) const;
+
+ void set_string_delimiters(const TypedArray<String> &p_string_delimiters);
+ void clear_string_delimiters();
+ TypedArray<String> get_string_delimiters() const;
+
+ int is_in_string(int p_line, int p_column = -1) const;
+
+ void add_comment_delimiter(const String &p_start_key, const String &p_end_key, bool p_line_only = false);
+ void remove_comment_delimiter(const String &p_start_key);
+ bool has_comment_delimiter(const String &p_start_key) const;
+
+ void set_comment_delimiters(const TypedArray<String> &p_comment_delimiters);
+ void clear_comment_delimiters();
+ TypedArray<String> get_comment_delimiters() const;
+
+ int is_in_comment(int p_line, int p_column = -1) const;
+
+ String get_delimiter_start_key(int p_delimiter_idx) const;
+ String get_delimiter_end_key(int p_delimiter_idx) const;
+
+ Point2 get_delimiter_start_position(int p_line, int p_column) const;
+ Point2 get_delimiter_end_position(int p_line, int p_column) const;
+
+ /* Code hint */
+ void set_code_hint(const String &p_hint);
+ void set_code_hint_draw_below(bool p_below);
+
+ /* Code Completion */
+ void set_code_completion_enabled(bool p_enable);
+ bool is_code_completion_enabled() const;
+
+ void set_code_completion_prefixes(const TypedArray<String> &p_prefixes);
+ TypedArray<String> get_code_completion_prefixes() const;
+
+ String get_text_for_code_completion() const;
+
+ void request_code_completion(bool p_force = false);
+
+ void add_code_completion_option(CodeCompletionKind p_type, const String &p_display_text, const String &p_insert_text, const Color &p_text_color = Color(1, 1, 1), const RES &p_icon = RES(), const Variant &p_value = Variant::NIL);
+ void update_code_completion_options(bool p_forced = false);
+
+ TypedArray<Dictionary> get_code_completion_options() const;
+ Dictionary get_code_completion_option(int p_index) const;
+
+ int get_code_completion_selected_index() const;
+ void set_code_completion_selected_index(int p_index);
+
+ void confirm_code_completion(bool p_replace = false);
+ void cancel_code_completion();
+
CodeEdit();
~CodeEdit();
};
+VARIANT_ENUM_CAST(CodeEdit::CodeCompletionKind);
+
#endif // CODEEDIT_H
diff --git a/scene/gui/color_picker.cpp b/scene/gui/color_picker.cpp
index 5822119b46..659d14ae70 100644
--- a/scene/gui/color_picker.cpp
+++ b/scene/gui/color_picker.cpp
@@ -40,6 +40,8 @@
#endif
#include "scene/main/window.h"
+List<Color> ColorPicker::preset_cache;
+
void ColorPicker::_notification(int p_what) {
switch (p_what) {
case NOTIFICATION_THEME_CHANGED: {
@@ -57,11 +59,17 @@ void ColorPicker::_notification(int p_what) {
#ifdef TOOLS_ENABLED
if (Engine::get_singleton()->is_editor_hint()) {
- PackedColorArray saved_presets = EditorSettings::get_singleton()->get_project_metadata("color_picker", "presets", PackedColorArray());
+ if (preset_cache.is_empty()) {
+ PackedColorArray saved_presets = EditorSettings::get_singleton()->get_project_metadata("color_picker", "presets", PackedColorArray());
+ for (int i = 0; i < saved_presets.size(); i++) {
+ preset_cache.push_back(saved_presets[i]);
+ }
+ }
- for (int i = 0; i < saved_presets.size(); i++) {
- add_preset(saved_presets[i]);
+ for (int i = 0; i < preset_cache.size(); i++) {
+ presets.push_back(preset_cache[i]);
}
+ preset->update();
}
#endif
} break;
@@ -84,6 +92,55 @@ void ColorPicker::_notification(int p_what) {
}
}
+Ref<Shader> ColorPicker::wheel_shader;
+Ref<Shader> ColorPicker::circle_shader;
+
+void ColorPicker::init_shaders() {
+ wheel_shader.instantiate();
+ wheel_shader->set_code(
+ "shader_type canvas_item;"
+ "void fragment() {"
+ " float x = UV.x - 0.5;"
+ " float y = UV.y - 0.5;"
+ " float a = atan(y, x);"
+ " x += 0.001;"
+ " y += 0.001;"
+ " float b = float(sqrt(x * x + y * y) < 0.5) * float(sqrt(x * x + y * y) > 0.42);"
+ " x -= 0.002;"
+ " float b2 = float(sqrt(x * x + y * y) < 0.5) * float(sqrt(x * x + y * y) > 0.42);"
+ " y -= 0.002;"
+ " float b3 = float(sqrt(x * x + y * y) < 0.5) * float(sqrt(x * x + y * y) > 0.42);"
+ " x += 0.002;"
+ " float b4 = float(sqrt(x * x + y * y) < 0.5) * float(sqrt(x * x + y * y) > 0.42);"
+ " COLOR = vec4(clamp((abs(fract(((a - TAU) / TAU) + vec3(3.0, 2.0, 1.0) / 3.0) * 6.0 - 3.0) - 1.0), 0.0, 1.0), (b + b2 + b3 + b4) / 4.00);"
+ "}");
+
+ circle_shader.instantiate();
+ circle_shader->set_code(
+ "shader_type canvas_item;"
+ "uniform float v = 1.0;"
+ "void fragment() {"
+ " float x = UV.x - 0.5;"
+ " float y = UV.y - 0.5;"
+ " float a = atan(y, x);"
+ " x += 0.001;"
+ " y += 0.001;"
+ " float b = float(sqrt(x * x + y * y) < 0.5);"
+ " x -= 0.002;"
+ " float b2 = float(sqrt(x * x + y * y) < 0.5);"
+ " y -= 0.002;"
+ " float b3 = float(sqrt(x * x + y * y) < 0.5);"
+ " x += 0.002;"
+ " float b4 = float(sqrt(x * x + y * y) < 0.5);"
+ " COLOR = vec4(mix(vec3(1.0), clamp(abs(fract(vec3((a - TAU) / TAU) + vec3(1.0, 2.0 / 3.0, 1.0 / 3.0)) * 6.0 - vec3(3.0)) - vec3(1.0), 0.0, 1.0), ((float(sqrt(x * x + y * y)) * 2.0)) / 1.0) * vec3(v), (b + b2 + b3 + b4) / 4.00);"
+ "}");
+}
+
+void ColorPicker::finish_shaders() {
+ wheel_shader.unref();
+ circle_shader.unref();
+}
+
void ColorPicker::set_focus_on_line_edit() {
c_text->call_deferred("grab_focus");
}
@@ -115,19 +172,22 @@ void ColorPicker::_update_controls() {
if (raw_mode_enabled) {
for (int i = 0; i < 3; i++) {
- scroll[i]->add_theme_icon_override("grabber", Ref<Texture2D>());
- scroll[i]->add_theme_icon_override("grabber_highlight", Ref<Texture2D>());
- scroll[i]->add_theme_style_override("slider", Ref<StyleBox>());
- scroll[i]->add_theme_style_override("grabber_area", Ref<StyleBox>());
- scroll[i]->add_theme_style_override("grabber_area_highlight", Ref<StyleBox>());
+ scroll[i]->remove_theme_icon_override("grabber");
+ scroll[i]->remove_theme_icon_override("grabber_highlight");
+ scroll[i]->remove_theme_style_override("slider");
+ scroll[i]->remove_theme_style_override("grabber_area");
+ scroll[i]->remove_theme_style_override("grabber_area_highlight");
}
} else {
- for (int i = 0; i < 3; i++) {
- scroll[i]->add_theme_icon_override("grabber", get_theme_icon("bar_arrow"));
- scroll[i]->add_theme_icon_override("grabber_highlight", get_theme_icon("bar_arrow"));
- scroll[i]->add_theme_style_override("slider", Ref<StyleBoxEmpty>(memnew(StyleBoxEmpty)));
- scroll[i]->add_theme_style_override("grabber_area", Ref<StyleBoxEmpty>(memnew(StyleBoxEmpty)));
- scroll[i]->add_theme_style_override("grabber_area_highlight", Ref<StyleBoxEmpty>(memnew(StyleBoxEmpty)));
+ Ref<StyleBoxEmpty> style_box_empty(memnew(StyleBoxEmpty));
+ Ref<Texture2D> bar_arrow = get_theme_icon("bar_arrow");
+
+ for (int i = 0; i < 4; i++) {
+ scroll[i]->add_theme_icon_override("grabber", bar_arrow);
+ scroll[i]->add_theme_icon_override("grabber_highlight", bar_arrow);
+ scroll[i]->add_theme_style_override("slider", style_box_empty);
+ scroll[i]->add_theme_style_override("grabber_area", style_box_empty);
+ scroll[i]->add_theme_style_override("grabber_area_highlight", style_box_empty);
}
}
@@ -140,6 +200,30 @@ void ColorPicker::_update_controls() {
scroll[3]->hide();
labels[3]->hide();
}
+
+ switch (picker_type) {
+ case SHAPE_HSV_RECTANGLE:
+ wheel_edit->hide();
+ w_edit->show();
+ uv_edit->show();
+ break;
+ case SHAPE_HSV_WHEEL:
+ wheel_edit->show();
+ w_edit->hide();
+ uv_edit->hide();
+
+ wheel->set_material(wheel_mat);
+ break;
+ case SHAPE_VHS_CIRCLE:
+ wheel_edit->show();
+ w_edit->show();
+ uv_edit->hide();
+
+ wheel->set_material(circle_mat);
+ break;
+ default: {
+ }
+ }
}
void ColorPicker::_set_pick_color(const Color &p_color, bool p_update_sliders) {
@@ -162,6 +246,18 @@ void ColorPicker::set_pick_color(const Color &p_color) {
_set_pick_color(p_color, true); //because setters can't have more arguments
}
+void ColorPicker::set_old_color(const Color &p_color) {
+ old_color = p_color;
+}
+
+void ColorPicker::set_display_old_color(bool p_enabled) {
+ display_old_color = p_enabled;
+}
+
+bool ColorPicker::is_displaying_old_color() const {
+ return display_old_color;
+}
+
void ColorPicker::set_edit_alpha(bool p_show) {
edit_alpha = p_show;
_update_controls();
@@ -201,7 +297,7 @@ void ColorPicker::_value_changed(double) {
emit_signal("color_changed", color);
}
-void ColorPicker::_html_entered(const String &p_html) {
+void ColorPicker::_html_submitted(const String &p_html) {
if (updating || text_is_constructor || !c_text->is_visible()) {
return;
}
@@ -264,6 +360,8 @@ void ColorPicker::_update_color(bool p_update_sliders) {
for (int i = 0; i < 4; i++) {
scroll[i]->update();
}
+ wheel->update();
+ wheel_uv->update();
updating = false;
}
@@ -306,11 +404,24 @@ Color ColorPicker::get_pick_color() const {
return color;
}
+void ColorPicker::set_picker_shape(PickerShapeType p_picker_type) {
+ ERR_FAIL_INDEX(p_picker_type, SHAPE_MAX);
+ picker_type = p_picker_type;
+
+ _update_controls();
+ _update_color();
+}
+
+ColorPicker::PickerShapeType ColorPicker::get_picker_shape() const {
+ return picker_type;
+}
+
void ColorPicker::add_preset(const Color &p_color) {
if (presets.find(p_color)) {
presets.move_to_back(presets.find(p_color));
} else {
presets.push_back(p_color);
+ preset_cache.push_back(p_color);
}
preset->update();
@@ -325,6 +436,7 @@ void ColorPicker::add_preset(const Color &p_color) {
void ColorPicker::erase_preset(const Color &p_color) {
if (presets.find(p_color)) {
presets.erase(presets.find(p_color));
+ preset_cache.erase(preset_cache.find(p_color));
preset->update();
#ifdef TOOLS_ENABLED
@@ -417,18 +529,53 @@ void ColorPicker::_update_text_value() {
c_text->set_visible(visible);
}
+void ColorPicker::_sample_input(const Ref<InputEvent> &p_event) {
+ const Ref<InputEventMouseButton> mb = p_event;
+ if (mb.is_valid() && mb->is_pressed() && mb->get_button_index() == MOUSE_BUTTON_LEFT) {
+ const Rect2 rect_old = Rect2(Point2(), Size2(sample->get_size().width * 0.5, sample->get_size().height * 0.95));
+ if (rect_old.has_point(mb->get_position())) {
+ // Revert to the old color when left-clicking the old color sample.
+ color = old_color;
+ _update_color();
+ emit_signal("color_changed", color);
+ }
+ }
+}
+
void ColorPicker::_sample_draw() {
- const Rect2 r = Rect2(Point2(), Size2(uv_edit->get_size().width, sample->get_size().height * 0.95));
+ // Covers the right half of the sample if the old color is being displayed,
+ // or the whole sample if it's not being displayed.
+ Rect2 rect_new;
+
+ if (display_old_color) {
+ rect_new = Rect2(Point2(sample->get_size().width * 0.5, 0), Size2(sample->get_size().width * 0.5, sample->get_size().height * 0.95));
+
+ // Draw both old and new colors for easier comparison (only if spawned from a ColorPickerButton).
+ const Rect2 rect_old = Rect2(Point2(), Size2(sample->get_size().width * 0.5, sample->get_size().height * 0.95));
+
+ if (display_old_color && old_color.a < 1.0) {
+ sample->draw_texture_rect(get_theme_icon("preset_bg", "ColorPicker"), rect_old, true);
+ }
+
+ sample->draw_rect(rect_old, old_color);
+
+ if (old_color.r > 1 || old_color.g > 1 || old_color.b > 1) {
+ // Draw an indicator to denote that the old color is "overbright" and can't be displayed accurately in the preview.
+ sample->draw_texture(get_theme_icon("overbright_indicator", "ColorPicker"), Point2());
+ }
+ } else {
+ rect_new = Rect2(Point2(), Size2(sample->get_size().width, sample->get_size().height * 0.95));
+ }
if (color.a < 1.0) {
- sample->draw_texture_rect(get_theme_icon("preset_bg", "ColorPicker"), r, true);
+ sample->draw_texture_rect(get_theme_icon("preset_bg", "ColorPicker"), rect_new, true);
}
- sample->draw_rect(r, color);
+ sample->draw_rect(rect_new, color);
if (color.r > 1 || color.g > 1 || color.b > 1) {
- // Draw an indicator to denote that the color is "overbright" and can't be displayed accurately in the preview
- sample->draw_texture(get_theme_icon("overbright_indicator", "ColorPicker"), Point2());
+ // Draw an indicator to denote that the new color is "overbright" and can't be displayed accurately in the preview.
+ sample->draw_texture(get_theme_icon("overbright_indicator", "ColorPicker"), Point2(uv_edit->get_size().width * 0.5, 0));
}
}
@@ -438,42 +585,131 @@ void ColorPicker::_hsv_draw(int p_which, Control *c) {
}
if (p_which == 0) {
Vector<Point2> points;
- points.push_back(Vector2());
- points.push_back(Vector2(c->get_size().x, 0));
- points.push_back(c->get_size());
- points.push_back(Vector2(0, c->get_size().y));
Vector<Color> colors;
- colors.push_back(Color(1, 1, 1, 1));
- colors.push_back(Color(1, 1, 1, 1));
- colors.push_back(Color(0, 0, 0, 1));
- colors.push_back(Color(0, 0, 0, 1));
- c->draw_polygon(points, colors);
Vector<Color> colors2;
Color col = color;
+ Vector2 center = c->get_size() / 2.0;
+
+ switch (picker_type) {
+ case SHAPE_HSV_WHEEL: {
+ points.resize(4);
+ colors.resize(4);
+ colors2.resize(4);
+ real_t ring_radius_x = Math_SQRT12 * c->get_size().width * 0.42;
+ real_t ring_radius_y = Math_SQRT12 * c->get_size().height * 0.42;
+
+ points.set(0, center - Vector2(ring_radius_x, ring_radius_y));
+ points.set(1, center + Vector2(ring_radius_x, -ring_radius_y));
+ points.set(2, center + Vector2(ring_radius_x, ring_radius_y));
+ points.set(3, center + Vector2(-ring_radius_x, ring_radius_y));
+ colors.set(0, Color(1, 1, 1, 1));
+ colors.set(1, Color(1, 1, 1, 1));
+ colors.set(2, Color(0, 0, 0, 1));
+ colors.set(3, Color(0, 0, 0, 1));
+ c->draw_polygon(points, colors);
+
+ col.set_hsv(h, 1, 1);
+ col.a = 0;
+ colors2.set(0, col);
+ col.a = 1;
+ colors2.set(1, col);
+ col.set_hsv(h, 1, 0);
+ colors2.set(2, col);
+ col.a = 0;
+ colors2.set(3, col);
+ c->draw_polygon(points, colors2);
+ break;
+ }
+ case SHAPE_HSV_RECTANGLE: {
+ points.resize(4);
+ colors.resize(4);
+ colors2.resize(4);
+ points.set(0, Vector2());
+ points.set(1, Vector2(c->get_size().x, 0));
+ points.set(2, c->get_size());
+ points.set(3, Vector2(0, c->get_size().y));
+ colors.set(0, Color(1, 1, 1, 1));
+ colors.set(1, Color(1, 1, 1, 1));
+ colors.set(2, Color(0, 0, 0, 1));
+ colors.set(3, Color(0, 0, 0, 1));
+ c->draw_polygon(points, colors);
+ col = color;
+ col.set_hsv(h, 1, 1);
+ col.a = 0;
+ colors2.set(0, col);
+ col.a = 1;
+ colors2.set(1, col);
+ col.set_hsv(h, 1, 0);
+ colors2.set(2, col);
+ col.a = 0;
+ colors2.set(3, col);
+ c->draw_polygon(points, colors2);
+ break;
+ }
+ default: {
+ }
+ }
+ Ref<Texture2D> cursor = get_theme_icon("picker_cursor", "ColorPicker");
+ int x;
+ int y;
+ if (picker_type == SHAPE_VHS_CIRCLE) {
+ x = center.x + (center.x * Math::cos(h * Math_TAU) * s) - (cursor->get_width() / 2);
+ y = center.y + (center.y * Math::sin(h * Math_TAU) * s) - (cursor->get_height() / 2);
+ } else {
+ real_t corner_x = (c == wheel_uv) ? center.x - Math_SQRT12 * c->get_size().width * 0.42 : 0;
+ real_t corner_y = (c == wheel_uv) ? center.y - Math_SQRT12 * c->get_size().height * 0.42 : 0;
+
+ Size2 real_size(c->get_size().x - corner_x * 2, c->get_size().y - corner_y * 2);
+ x = CLAMP(real_size.x * s, 0, real_size.x) + corner_x - (cursor->get_width() / 2);
+ y = CLAMP(real_size.y - real_size.y * v, 0, real_size.y) + corner_y - (cursor->get_height() / 2);
+ }
+ c->draw_texture(cursor, Point2(x, y));
+
col.set_hsv(h, 1, 1);
- col.a = 0;
- colors2.push_back(col);
- col.a = 1;
- colors2.push_back(col);
- col.set_hsv(h, 1, 0);
- colors2.push_back(col);
- col.a = 0;
- colors2.push_back(col);
- c->draw_polygon(points, colors2);
- int x = CLAMP(c->get_size().x * s, 0, c->get_size().x);
- int y = CLAMP(c->get_size().y - c->get_size().y * v, 0, c->get_size().y);
- col = color;
- col.a = 1;
- c->draw_line(Point2(x, 0), Point2(x, c->get_size().y), col.inverted());
- c->draw_line(Point2(0, y), Point2(c->get_size().x, y), col.inverted());
- c->draw_line(Point2(x, y), Point2(x, y), Color(1, 1, 1), 2);
+ if (picker_type == SHAPE_HSV_WHEEL) {
+ points.resize(4);
+ double h1 = h - (0.5 / 360);
+ double h2 = h + (0.5 / 360);
+ points.set(0, Point2(center.x + (center.x * Math::cos(h1 * Math_TAU)), center.y + (center.y * Math::sin(h1 * Math_TAU))));
+ points.set(1, Point2(center.x + (center.x * Math::cos(h1 * Math_TAU) * 0.84), center.y + (center.y * Math::sin(h1 * Math_TAU) * 0.84)));
+ points.set(2, Point2(center.x + (center.x * Math::cos(h2 * Math_TAU)), center.y + (center.y * Math::sin(h2 * Math_TAU))));
+ points.set(3, Point2(center.x + (center.x * Math::cos(h2 * Math_TAU) * 0.84), center.y + (center.y * Math::sin(h2 * Math_TAU) * 0.84)));
+ c->draw_multiline(points, col.inverted());
+ }
+
} else if (p_which == 1) {
- Ref<Texture2D> hue = get_theme_icon("color_hue", "ColorPicker");
- c->draw_texture_rect(hue, Rect2(Point2(), c->get_size()));
- int y = c->get_size().y - c->get_size().y * (1.0 - h);
- Color col = Color();
- col.set_hsv(h, 1, 1);
- c->draw_line(Point2(0, y), Point2(c->get_size().x, y), col.inverted());
+ if (picker_type == SHAPE_HSV_RECTANGLE) {
+ Ref<Texture2D> hue = get_theme_icon("color_hue", "ColorPicker");
+ c->draw_texture_rect(hue, Rect2(Point2(), c->get_size()));
+ int y = c->get_size().y - c->get_size().y * (1.0 - h);
+ Color col;
+ col.set_hsv(h, 1, 1);
+ c->draw_line(Point2(0, y), Point2(c->get_size().x, y), col.inverted());
+ } else if (picker_type == SHAPE_VHS_CIRCLE) {
+ Vector<Point2> points;
+ Vector<Color> colors;
+ Color col;
+ col.set_hsv(h, s, 1);
+ points.resize(4);
+ colors.resize(4);
+ points.set(0, Vector2());
+ points.set(1, Vector2(c->get_size().x, 0));
+ points.set(2, c->get_size());
+ points.set(3, Vector2(0, c->get_size().y));
+ colors.set(0, col);
+ colors.set(1, col);
+ colors.set(2, Color(0, 0, 0));
+ colors.set(3, Color(0, 0, 0));
+ c->draw_polygon(points, colors);
+ int y = c->get_size().y - c->get_size().y * CLAMP(v, 0, 1);
+ col.set_hsv(h, 1, v);
+ c->draw_line(Point2(0, y), Point2(c->get_size().x, y), col.inverted());
+ }
+ } else if (p_which == 2) {
+ c->draw_rect(Rect2(Point2(), c->get_size()), Color(1, 1, 1));
+ if (picker_type == SHAPE_VHS_CIRCLE) {
+ circle_mat->set_shader_param("v", v);
+ }
}
}
@@ -540,16 +776,51 @@ void ColorPicker::_slider_draw(int p_which) {
scroll[p_which]->draw_polygon(pos, col);
}
-void ColorPicker::_uv_input(const Ref<InputEvent> &p_event) {
+void ColorPicker::_uv_input(const Ref<InputEvent> &p_event, Control *c) {
Ref<InputEventMouseButton> bev = p_event;
if (bev.is_valid()) {
if (bev->is_pressed() && bev->get_button_index() == MOUSE_BUTTON_LEFT) {
+ Vector2 center = c->get_size() / 2.0;
+ if (picker_type == SHAPE_VHS_CIRCLE) {
+ real_t dist = center.distance_to(bev->get_position());
+
+ if (dist <= center.x) {
+ real_t rad = Math::atan2(bev->get_position().y - center.y, bev->get_position().x - center.x);
+ h = ((rad >= 0) ? rad : (Math_TAU + rad)) / Math_TAU;
+ s = CLAMP(dist / center.x, 0, 1);
+ } else {
+ return;
+ }
+ } else {
+ real_t corner_x = (c == wheel_uv) ? center.x - Math_SQRT12 * c->get_size().width * 0.42 : 0;
+ real_t corner_y = (c == wheel_uv) ? center.y - Math_SQRT12 * c->get_size().height * 0.42 : 0;
+ Size2 real_size(c->get_size().x - corner_x * 2, c->get_size().y - corner_y * 2);
+
+ if (bev->get_position().x < corner_x || bev->get_position().x > c->get_size().x - corner_x ||
+ bev->get_position().y < corner_y || bev->get_position().y > c->get_size().y - corner_y) {
+ {
+ real_t dist = center.distance_to(bev->get_position());
+
+ if (dist >= center.x * 0.84 && dist <= center.x) {
+ real_t rad = Math::atan2(bev->get_position().y - center.y, bev->get_position().x - center.x);
+ h = ((rad >= 0) ? rad : (Math_TAU + rad)) / Math_TAU;
+ spinning = true;
+ } else {
+ return;
+ }
+ }
+ }
+
+ if (!spinning) {
+ real_t x = CLAMP(bev->get_position().x, corner_x, c->get_size().x - corner_x);
+ real_t y = CLAMP(bev->get_position().y, corner_x, c->get_size().y - corner_y);
+
+ s = (x - c->get_position().x - corner_x) / real_size.x;
+ v = 1.0 - (y - c->get_position().y - corner_y) / real_size.y;
+ }
+ }
changing_color = true;
- float x = CLAMP((float)bev->get_position().x, 0, uv_edit->get_size().width);
- float y = CLAMP((float)bev->get_position().y, 0, uv_edit->get_size().height);
- s = x / uv_edit->get_size().width;
- v = 1.0 - y / uv_edit->get_size().height;
color.set_hsv(h, s, v, color.a);
last_hsv = color;
set_pick_color(color);
@@ -560,8 +831,10 @@ void ColorPicker::_uv_input(const Ref<InputEvent> &p_event) {
} else if (deferred_mode_enabled && !bev->is_pressed() && bev->get_button_index() == MOUSE_BUTTON_LEFT) {
emit_signal("color_changed", color);
changing_color = false;
+ spinning = false;
} else {
changing_color = false;
+ spinning = false;
}
}
@@ -571,10 +844,30 @@ void ColorPicker::_uv_input(const Ref<InputEvent> &p_event) {
if (!changing_color) {
return;
}
- float x = CLAMP((float)mev->get_position().x, 0, uv_edit->get_size().width);
- float y = CLAMP((float)mev->get_position().y, 0, uv_edit->get_size().height);
- s = x / uv_edit->get_size().width;
- v = 1.0 - y / uv_edit->get_size().height;
+
+ Vector2 center = c->get_size() / 2.0;
+ if (picker_type == SHAPE_VHS_CIRCLE) {
+ real_t dist = center.distance_to(mev->get_position());
+ real_t rad = Math::atan2(mev->get_position().y - center.y, mev->get_position().x - center.x);
+ h = ((rad >= 0) ? rad : (Math_TAU + rad)) / Math_TAU;
+ s = CLAMP(dist / center.x, 0, 1);
+ } else {
+ if (spinning) {
+ real_t rad = Math::atan2(mev->get_position().y - center.y, mev->get_position().x - center.x);
+ h = ((rad >= 0) ? rad : (Math_TAU + rad)) / Math_TAU;
+ } else {
+ real_t corner_x = (c == wheel_uv) ? center.x - Math_SQRT12 * c->get_size().width * 0.42 : 0;
+ real_t corner_y = (c == wheel_uv) ? center.y - Math_SQRT12 * c->get_size().height * 0.42 : 0;
+ Size2 real_size(c->get_size().x - corner_x * 2, c->get_size().y - corner_y * 2);
+
+ real_t x = CLAMP(mev->get_position().x, corner_x, c->get_size().x - corner_x);
+ real_t y = CLAMP(mev->get_position().y, corner_x, c->get_size().y - corner_y);
+
+ s = (x - corner_x) / real_size.x;
+ v = 1.0 - (y - corner_y) / real_size.y;
+ }
+ }
+
color.set_hsv(h, s, v, color.a);
last_hsv = color;
set_pick_color(color);
@@ -592,7 +885,11 @@ void ColorPicker::_w_input(const Ref<InputEvent> &p_event) {
if (bev->is_pressed() && bev->get_button_index() == MOUSE_BUTTON_LEFT) {
changing_color = true;
float y = CLAMP((float)bev->get_position().y, 0, w_edit->get_size().height);
- h = y / w_edit->get_size().height;
+ if (picker_type == SHAPE_VHS_CIRCLE) {
+ v = 1.0 - (y / w_edit->get_size().height);
+ } else {
+ h = y / w_edit->get_size().height;
+ }
} else {
changing_color = false;
}
@@ -614,7 +911,11 @@ void ColorPicker::_w_input(const Ref<InputEvent> &p_event) {
return;
}
float y = CLAMP((float)mev->get_position().y, 0, w_edit->get_size().height);
- h = y / w_edit->get_size().height;
+ if (picker_type == SHAPE_VHS_CIRCLE) {
+ v = 1.0 - (y / w_edit->get_size().height);
+ } else {
+ h = y / w_edit->get_size().height;
+ }
color.set_hsv(h, s, v, color.a);
last_hsv = color;
set_pick_color(color);
@@ -682,7 +983,7 @@ void ColorPicker::_screen_input(const Ref<InputEvent> &p_event) {
return;
}
- Ref<Image> img = r->get_texture()->get_data();
+ Ref<Image> img = r->get_texture()->get_image();
if (img.is_valid() && !img->is_empty()) {
Vector2 ofs = mev->get_global_position() - r->get_visible_rect().get_position();
Color c = img->get_pixel(ofs.x, r->get_visible_rect().size.height - ofs.y);
@@ -750,7 +1051,7 @@ void ColorPicker::_html_focus_exit() {
if (c_text->get_menu()->is_visible()) {
return;
}
- _html_entered(c_text->get_text());
+ _html_submitted(c_text->get_text());
_focus_exit();
}
@@ -798,18 +1099,25 @@ void ColorPicker::_bind_methods() {
ClassDB::bind_method(D_METHOD("add_preset", "color"), &ColorPicker::add_preset);
ClassDB::bind_method(D_METHOD("erase_preset", "color"), &ColorPicker::erase_preset);
ClassDB::bind_method(D_METHOD("get_presets"), &ColorPicker::get_presets);
+ ClassDB::bind_method(D_METHOD("set_picker_shape", "picker"), &ColorPicker::set_picker_shape);
+ ClassDB::bind_method(D_METHOD("get_picker_shape"), &ColorPicker::get_picker_shape);
ADD_PROPERTY(PropertyInfo(Variant::COLOR, "color"), "set_pick_color", "get_pick_color");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "edit_alpha"), "set_edit_alpha", "is_editing_alpha");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "hsv_mode"), "set_hsv_mode", "is_hsv_mode");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "raw_mode"), "set_raw_mode", "is_raw_mode");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "deferred_mode"), "set_deferred_mode", "is_deferred_mode");
+ ADD_PROPERTY(PropertyInfo(Variant::INT, "picker_shape", PROPERTY_HINT_ENUM, "HSV Rectangle,HSV Rectangle Wheel,VHS Circle"), "set_picker_shape", "get_picker_shape");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "presets_enabled"), "set_presets_enabled", "are_presets_enabled");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "presets_visible"), "set_presets_visible", "are_presets_visible");
ADD_SIGNAL(MethodInfo("color_changed", PropertyInfo(Variant::COLOR, "color")));
ADD_SIGNAL(MethodInfo("preset_added", PropertyInfo(Variant::COLOR, "color")));
ADD_SIGNAL(MethodInfo("preset_removed", PropertyInfo(Variant::COLOR, "color")));
+
+ BIND_ENUM_CONSTANT(SHAPE_HSV_RECTANGLE);
+ BIND_ENUM_CONSTANT(SHAPE_HSV_WHEEL);
+ BIND_ENUM_CONSTANT(SHAPE_VHS_CIRCLE);
}
ColorPicker::ColorPicker() :
@@ -818,36 +1126,26 @@ ColorPicker::ColorPicker() :
add_child(hb_edit);
hb_edit->set_v_size_flags(SIZE_EXPAND_FILL);
- uv_edit = memnew(Control);
hb_edit->add_child(uv_edit);
- uv_edit->connect("gui_input", callable_mp(this, &ColorPicker::_uv_input));
+ uv_edit->connect("gui_input", callable_mp(this, &ColorPicker::_uv_input), make_binds(uv_edit));
uv_edit->set_mouse_filter(MOUSE_FILTER_PASS);
uv_edit->set_h_size_flags(SIZE_EXPAND_FILL);
uv_edit->set_v_size_flags(SIZE_EXPAND_FILL);
uv_edit->set_custom_minimum_size(Size2(get_theme_constant("sv_width"), get_theme_constant("sv_height")));
uv_edit->connect("draw", callable_mp(this, &ColorPicker::_hsv_draw), make_binds(0, uv_edit));
- w_edit = memnew(Control);
- hb_edit->add_child(w_edit);
- w_edit->set_custom_minimum_size(Size2(get_theme_constant("h_width"), 0));
- w_edit->set_h_size_flags(SIZE_FILL);
- w_edit->set_v_size_flags(SIZE_EXPAND_FILL);
- w_edit->connect("gui_input", callable_mp(this, &ColorPicker::_w_input));
- w_edit->connect("draw", callable_mp(this, &ColorPicker::_hsv_draw), make_binds(1, w_edit));
-
HBoxContainer *hb_smpl = memnew(HBoxContainer);
add_child(hb_smpl);
- sample = memnew(TextureRect);
hb_smpl->add_child(sample);
sample->set_h_size_flags(SIZE_EXPAND_FILL);
+ sample->connect("gui_input", callable_mp(this, &ColorPicker::_sample_input));
sample->connect("draw", callable_mp(this, &ColorPicker::_sample_draw));
- btn_pick = memnew(Button);
btn_pick->set_flat(true);
hb_smpl->add_child(btn_pick);
btn_pick->set_toggle_mode(true);
- btn_pick->set_tooltip(TTR("Pick a color from the editor window."));
+ btn_pick->set_tooltip(RTR("Pick a color from the editor window."));
btn_pick->connect("pressed", callable_mp(this, &ColorPicker::_screen_pick_pressed));
VBoxContainer *vbl = memnew(VBoxContainer);
@@ -887,27 +1185,20 @@ ColorPicker::ColorPicker() :
vbr->add_child(hbc);
}
+
labels[3]->set_text("A");
- scroll[3]->add_theme_icon_override("grabber", get_theme_icon("bar_arrow"));
- scroll[3]->add_theme_icon_override("grabber_highlight", get_theme_icon("bar_arrow"));
- scroll[3]->add_theme_style_override("slider", Ref<StyleBoxEmpty>(memnew(StyleBoxEmpty)));
- scroll[3]->add_theme_style_override("grabber_area", Ref<StyleBoxEmpty>(memnew(StyleBoxEmpty)));
- scroll[3]->add_theme_style_override("grabber_area_highlight", Ref<StyleBoxEmpty>(memnew(StyleBoxEmpty)));
HBoxContainer *hhb = memnew(HBoxContainer);
vbr->add_child(hhb);
- btn_hsv = memnew(CheckButton);
hhb->add_child(btn_hsv);
- btn_hsv->set_text(TTR("HSV"));
+ btn_hsv->set_text(RTR("HSV"));
btn_hsv->connect("toggled", callable_mp(this, &ColorPicker::set_hsv_mode));
- btn_raw = memnew(CheckButton);
hhb->add_child(btn_raw);
- btn_raw->set_text(TTR("Raw"));
+ btn_raw->set_text(RTR("Raw"));
btn_raw->connect("toggled", callable_mp(this, &ColorPicker::set_raw_mode));
- text_type = memnew(Button);
hhb->add_child(text_type);
text_type->set_text("#");
text_type->set_tooltip(TTR("Switch between hexadecimal and code values."));
@@ -921,41 +1212,76 @@ ColorPicker::ColorPicker() :
text_type->set_mouse_filter(MOUSE_FILTER_IGNORE);
}
- c_text = memnew(LineEdit);
hhb->add_child(c_text);
c_text->set_h_size_flags(SIZE_EXPAND_FILL);
- c_text->connect("text_entered", callable_mp(this, &ColorPicker::_html_entered));
+ c_text->connect("text_submitted", callable_mp(this, &ColorPicker::_html_submitted));
c_text->connect("focus_entered", callable_mp(this, &ColorPicker::_focus_enter));
c_text->connect("focus_exited", callable_mp(this, &ColorPicker::_html_focus_exit));
+ wheel_edit->set_h_size_flags(SIZE_EXPAND_FILL);
+ wheel_edit->set_v_size_flags(SIZE_EXPAND_FILL);
+ wheel_edit->set_custom_minimum_size(Size2(get_theme_constant("sv_width"), get_theme_constant("sv_height")));
+ hb_edit->add_child(wheel_edit);
+
+ wheel_mat.instantiate();
+ wheel_mat->set_shader(wheel_shader);
+ circle_mat.instantiate();
+ circle_mat->set_shader(circle_shader);
+
+ MarginContainer *wheel_margin(memnew(MarginContainer));
+#ifdef TOOLS_ENABLED
+ wheel_margin->add_theme_constant_override("margin_bottom", 8 * EDSCALE);
+#else
+ wheel_margin->add_theme_constant_override("margin_bottom", 8);
+#endif
+ wheel_edit->add_child(wheel_margin);
+
+ wheel_margin->add_child(wheel);
+ wheel->set_mouse_filter(MOUSE_FILTER_PASS);
+ wheel->connect("draw", callable_mp(this, &ColorPicker::_hsv_draw), make_binds(2, wheel));
+
+ wheel_margin->add_child(wheel_uv);
+ wheel_uv->connect("gui_input", callable_mp(this, &ColorPicker::_uv_input), make_binds(wheel_uv));
+ wheel_uv->connect("draw", callable_mp(this, &ColorPicker::_hsv_draw), make_binds(0, wheel_uv));
+
+ hb_edit->add_child(w_edit);
+ w_edit->set_custom_minimum_size(Size2(get_theme_constant("h_width"), 0));
+ w_edit->set_h_size_flags(SIZE_FILL);
+ w_edit->set_v_size_flags(SIZE_EXPAND_FILL);
+ w_edit->connect("gui_input", callable_mp(this, &ColorPicker::_w_input));
+ w_edit->connect("draw", callable_mp(this, &ColorPicker::_hsv_draw), make_binds(1, w_edit));
+
+ picker_type = SHAPE_HSV_RECTANGLE;
_update_controls();
updating = false;
set_pick_color(Color(1, 1, 1));
- preset_separator = memnew(HSeparator);
add_child(preset_separator);
- preset_container = memnew(HBoxContainer);
preset_container->set_h_size_flags(SIZE_EXPAND_FILL);
add_child(preset_container);
- preset = memnew(TextureRect);
preset_container->add_child(preset);
preset->connect("gui_input", callable_mp(this, &ColorPicker::_preset_input));
preset->connect("draw", callable_mp(this, &ColorPicker::_update_presets));
- preset_container2 = memnew(HBoxContainer);
preset_container2->set_h_size_flags(SIZE_EXPAND_FILL);
add_child(preset_container2);
- bt_add_preset = memnew(Button);
preset_container2->add_child(bt_add_preset);
- bt_add_preset->set_tooltip(TTR("Add current color as a preset."));
+ bt_add_preset->set_tooltip(RTR("Add current color as a preset."));
bt_add_preset->connect("pressed", callable_mp(this, &ColorPicker::_add_preset_pressed));
}
/////////////////
+void ColorPickerButton::_about_to_popup() {
+ set_pressed(true);
+ if (picker) {
+ picker->set_old_color(color);
+ }
+}
+
void ColorPickerButton::_color_changed(const Color &p_color) {
color = p_color;
update();
@@ -1068,10 +1394,11 @@ void ColorPickerButton::_update_picker() {
popup->add_child(picker);
add_child(popup);
picker->connect("color_changed", callable_mp(this, &ColorPickerButton::_color_changed));
- popup->connect("about_to_popup", callable_mp((BaseButton *)this, &BaseButton::set_pressed), varray(true));
+ popup->connect("about_to_popup", callable_mp(this, &ColorPickerButton::_about_to_popup));
popup->connect("popup_hide", callable_mp(this, &ColorPickerButton::_modal_closed));
picker->set_pick_color(color);
picker->set_edit_alpha(edit_alpha);
+ picker->set_display_old_color(true);
emit_signal("picker_created");
}
}
@@ -1083,6 +1410,7 @@ void ColorPickerButton::_bind_methods() {
ClassDB::bind_method(D_METHOD("get_popup"), &ColorPickerButton::get_popup);
ClassDB::bind_method(D_METHOD("set_edit_alpha", "show"), &ColorPickerButton::set_edit_alpha);
ClassDB::bind_method(D_METHOD("is_editing_alpha"), &ColorPickerButton::is_editing_alpha);
+ ClassDB::bind_method(D_METHOD("_about_to_popup"), &ColorPickerButton::_about_to_popup);
ADD_SIGNAL(MethodInfo("color_changed", PropertyInfo(Variant::COLOR, "color")));
ADD_SIGNAL(MethodInfo("popup_closed"));
diff --git a/scene/gui/color_picker.h b/scene/gui/color_picker.h
index 24e1746c41..60da3957aa 100644
--- a/scene/gui/color_picker.h
+++ b/scene/gui/color_picker.h
@@ -31,6 +31,7 @@
#ifndef COLOR_PICKER_H
#define COLOR_PICKER_H
+#include "scene/gui/aspect_ratio_container.h"
#include "scene/gui/box_container.h"
#include "scene/gui/button.h"
#include "scene/gui/check_button.h"
@@ -45,36 +46,58 @@
class ColorPicker : public BoxContainer {
GDCLASS(ColorPicker, BoxContainer);
+public:
+ enum PickerShapeType {
+ SHAPE_HSV_RECTANGLE,
+ SHAPE_HSV_WHEEL,
+ SHAPE_VHS_CIRCLE,
+
+ SHAPE_MAX
+ };
+
private:
+ static Ref<Shader> wheel_shader;
+ static Ref<Shader> circle_shader;
+ static List<Color> preset_cache;
+
Control *screen = nullptr;
- Control *uv_edit;
- Control *w_edit;
- TextureRect *sample;
- TextureRect *preset;
- HBoxContainer *preset_container;
- HBoxContainer *preset_container2;
- HSeparator *preset_separator;
- Button *bt_add_preset;
+ Control *uv_edit = memnew(Control);
+ Control *w_edit = memnew(Control);
+ AspectRatioContainer *wheel_edit = memnew(AspectRatioContainer);
+ Ref<ShaderMaterial> wheel_mat;
+ Ref<ShaderMaterial> circle_mat;
+ Control *wheel = memnew(Control);
+ Control *wheel_uv = memnew(Control);
+ TextureRect *sample = memnew(TextureRect);
+ TextureRect *preset = memnew(TextureRect);
+ HBoxContainer *preset_container = memnew(HBoxContainer);
+ HBoxContainer *preset_container2 = memnew(HBoxContainer);
+ HSeparator *preset_separator = memnew(HSeparator);
+ Button *bt_add_preset = memnew(Button);
List<Color> presets;
- Button *btn_pick;
- CheckButton *btn_hsv;
- CheckButton *btn_raw;
+ Button *btn_pick = memnew(Button);
+ CheckButton *btn_hsv = memnew(CheckButton);
+ CheckButton *btn_raw = memnew(CheckButton);
HSlider *scroll[4];
SpinBox *values[4];
Label *labels[4];
- Button *text_type;
- LineEdit *c_text;
+ Button *text_type = memnew(Button);
+ LineEdit *c_text = memnew(LineEdit);
bool edit_alpha = true;
Size2i ms;
bool text_is_constructor = false;
int presets_per_row = 0;
+ PickerShapeType picker_type = SHAPE_HSV_WHEEL;
Color color;
+ Color old_color;
+ bool display_old_color = false;
bool raw_mode_enabled = false;
bool hsv_mode_enabled = false;
bool deferred_mode_enabled = false;
bool updating = true;
bool changing_color = false;
+ bool spinning = false;
bool presets_enabled = true;
bool presets_visible = true;
float h = 0.0;
@@ -82,18 +105,19 @@ private:
float v = 0.0;
Color last_hsv;
- void _html_entered(const String &p_html);
+ void _html_submitted(const String &p_html);
void _value_changed(double);
void _update_controls();
void _update_color(bool p_update_sliders = true);
void _update_presets();
void _update_text_value();
void _text_type_toggled();
+ void _sample_input(const Ref<InputEvent> &p_event);
void _sample_draw();
void _hsv_draw(int p_which, Control *c);
void _slider_draw(int p_which);
- void _uv_input(const Ref<InputEvent> &p_event);
+ void _uv_input(const Ref<InputEvent> &p_event, Control *c);
void _w_input(const Ref<InputEvent> &p_event);
void _preset_input(const Ref<InputEvent> &p_event);
void _screen_input(const Ref<InputEvent> &p_event);
@@ -108,12 +132,22 @@ protected:
static void _bind_methods();
public:
+ static void init_shaders();
+ static void finish_shaders();
+
void set_edit_alpha(bool p_show);
bool is_editing_alpha() const;
void _set_pick_color(const Color &p_color, bool p_update_sliders);
void set_pick_color(const Color &p_color);
Color get_pick_color() const;
+ void set_old_color(const Color &p_color);
+
+ void set_display_old_color(bool p_enabled);
+ bool is_displaying_old_color() const;
+
+ void set_picker_shape(PickerShapeType p_picker_type);
+ PickerShapeType get_picker_shape() const;
void add_preset(const Color &p_color);
void erase_preset(const Color &p_color);
@@ -151,6 +185,7 @@ class ColorPickerButton : public Button {
Color color;
bool edit_alpha = true;
+ void _about_to_popup();
void _color_changed(const Color &p_color);
void _modal_closed();
@@ -175,4 +210,5 @@ public:
ColorPickerButton();
};
+VARIANT_ENUM_CAST(ColorPicker::PickerShapeType);
#endif // COLOR_PICKER_H
diff --git a/scene/gui/container.cpp b/scene/gui/container.cpp
index 2e6b798eea..dea69aae6b 100644
--- a/scene/gui/container.cpp
+++ b/scene/gui/container.cpp
@@ -159,16 +159,14 @@ void Container::_notification(int p_what) {
}
}
-String Container::get_configuration_warning() const {
- String warning = Control::get_configuration_warning();
+TypedArray<String> Container::get_configuration_warnings() const {
+ TypedArray<String> warnings = Control::get_configuration_warnings();
if (get_class() == "Container" && get_script().is_null()) {
- if (!warning.is_empty()) {
- warning += "\n\n";
- }
- warning += TTR("Container by itself serves no purpose unless a script configures its children placement behavior.\nIf you don't intend to add a script, use a plain Control node instead.");
+ warnings.push_back(TTR("Container by itself serves no purpose unless a script configures its children placement behavior.\nIf you don't intend to add a script, use a plain Control node instead."));
}
- return warning;
+
+ return warnings;
}
void Container::_bind_methods() {
diff --git a/scene/gui/container.h b/scene/gui/container.h
index a4f392a3ae..bce3085f0c 100644
--- a/scene/gui/container.h
+++ b/scene/gui/container.h
@@ -56,7 +56,7 @@ public:
void fit_child_in_rect(Control *p_child, const Rect2 &p_rect);
- virtual String get_configuration_warning() const override;
+ TypedArray<String> get_configuration_warnings() const override;
Container();
};
diff --git a/scene/gui/control.cpp b/scene/gui/control.cpp
index 2e391adf2c..718e754514 100644
--- a/scene/gui/control.cpp
+++ b/scene/gui/control.cpp
@@ -168,6 +168,12 @@ Size2 Control::_edit_get_minimum_size() const {
}
#endif
+void Control::accept_event() {
+ if (is_inside_tree()) {
+ get_viewport()->_gui_accept_event();
+ }
+}
+
void Control::set_custom_minimum_size(const Size2 &p_custom) {
if (p_custom == data.custom_minimum_size) {
return;
@@ -290,15 +296,11 @@ void Control::_update_minimum_size() {
}
Size2 minsize = get_combined_minimum_size();
- if (minsize.x > data.size_cache.x ||
- minsize.y > data.size_cache.y) {
- _size_changed();
- }
-
data.updating_last_minimum_size = false;
if (minsize != data.last_minimum_size) {
data.last_minimum_size = minsize;
+ _size_changed();
emit_signal(SceneStringNames::get_singleton()->minimum_size_changed);
}
}
@@ -327,7 +329,6 @@ bool Control::_get(const StringName &p_name, Variant &r_ret) const {
r_ret = data.color_override.has(name) ? Variant(data.color_override[name]) : Variant();
} else if (sname.begins_with("custom_constants/")) {
String name = sname.get_slicec('/', 1);
-
r_ret = data.constant_override.has(name) ? Variant(data.constant_override[name]) : Variant();
} else {
return false;
@@ -338,88 +339,109 @@ bool Control::_get(const StringName &p_name, Variant &r_ret) const {
void Control::_get_property_list(List<PropertyInfo> *p_list) const {
Ref<Theme> theme = Theme::get_default();
- /* Using the default theme since the properties below are meant for editor only
- if (data.theme.is_valid()) {
- theme = data.theme;
- } else {
- theme = Theme::get_default();
-
- }*/
{
List<StringName> names;
theme->get_icon_list(get_class_name(), &names);
for (List<StringName>::Element *E = names.front(); E; E = E->next()) {
- uint32_t hint = PROPERTY_USAGE_EDITOR | PROPERTY_USAGE_CHECKABLE;
+ uint32_t usage = PROPERTY_USAGE_EDITOR | PROPERTY_USAGE_CHECKABLE;
if (data.icon_override.has(E->get())) {
- hint |= PROPERTY_USAGE_STORAGE | PROPERTY_USAGE_CHECKED;
+ usage |= PROPERTY_USAGE_STORAGE | PROPERTY_USAGE_CHECKED;
}
- p_list->push_back(PropertyInfo(Variant::OBJECT, "custom_icons/" + E->get(), PROPERTY_HINT_RESOURCE_TYPE, "Texture2D", hint));
+ p_list->push_back(PropertyInfo(Variant::OBJECT, "custom_icons/" + E->get(), PROPERTY_HINT_RESOURCE_TYPE, "Texture2D", usage));
}
}
{
List<StringName> names;
theme->get_stylebox_list(get_class_name(), &names);
for (List<StringName>::Element *E = names.front(); E; E = E->next()) {
- uint32_t hint = PROPERTY_USAGE_EDITOR | PROPERTY_USAGE_CHECKABLE;
+ uint32_t usage = PROPERTY_USAGE_EDITOR | PROPERTY_USAGE_CHECKABLE;
if (data.style_override.has(E->get())) {
- hint |= PROPERTY_USAGE_STORAGE | PROPERTY_USAGE_CHECKED;
+ usage |= PROPERTY_USAGE_STORAGE | PROPERTY_USAGE_CHECKED;
}
- p_list->push_back(PropertyInfo(Variant::OBJECT, "custom_styles/" + E->get(), PROPERTY_HINT_RESOURCE_TYPE, "StyleBox", hint));
+ p_list->push_back(PropertyInfo(Variant::OBJECT, "custom_styles/" + E->get(), PROPERTY_HINT_RESOURCE_TYPE, "StyleBox", usage));
}
}
{
List<StringName> names;
theme->get_font_list(get_class_name(), &names);
for (List<StringName>::Element *E = names.front(); E; E = E->next()) {
- uint32_t hint = PROPERTY_USAGE_EDITOR | PROPERTY_USAGE_CHECKABLE;
+ uint32_t usage = PROPERTY_USAGE_EDITOR | PROPERTY_USAGE_CHECKABLE;
if (data.font_override.has(E->get())) {
- hint |= PROPERTY_USAGE_STORAGE | PROPERTY_USAGE_CHECKED;
+ usage |= PROPERTY_USAGE_STORAGE | PROPERTY_USAGE_CHECKED;
}
- p_list->push_back(PropertyInfo(Variant::OBJECT, "custom_fonts/" + E->get(), PROPERTY_HINT_RESOURCE_TYPE, "Font", hint));
+ p_list->push_back(PropertyInfo(Variant::OBJECT, "custom_fonts/" + E->get(), PROPERTY_HINT_RESOURCE_TYPE, "Font", usage));
}
}
{
List<StringName> names;
theme->get_font_size_list(get_class_name(), &names);
for (List<StringName>::Element *E = names.front(); E; E = E->next()) {
- uint32_t hint = PROPERTY_USAGE_EDITOR | PROPERTY_USAGE_CHECKABLE;
+ uint32_t usage = PROPERTY_USAGE_EDITOR | PROPERTY_USAGE_CHECKABLE;
if (data.font_size_override.has(E->get())) {
- hint |= PROPERTY_USAGE_STORAGE | PROPERTY_USAGE_CHECKED;
+ usage |= PROPERTY_USAGE_STORAGE | PROPERTY_USAGE_CHECKED;
}
- p_list->push_back(PropertyInfo(Variant::INT, "custom_font_sizes/" + E->get(), PROPERTY_HINT_NONE, "", hint));
+ p_list->push_back(PropertyInfo(Variant::INT, "custom_font_sizes/" + E->get(), PROPERTY_HINT_NONE, "", usage));
}
}
{
List<StringName> names;
theme->get_color_list(get_class_name(), &names);
for (List<StringName>::Element *E = names.front(); E; E = E->next()) {
- uint32_t hint = PROPERTY_USAGE_EDITOR | PROPERTY_USAGE_CHECKABLE;
+ uint32_t usage = PROPERTY_USAGE_EDITOR | PROPERTY_USAGE_CHECKABLE;
if (data.color_override.has(E->get())) {
- hint |= PROPERTY_USAGE_STORAGE | PROPERTY_USAGE_CHECKED;
+ usage |= PROPERTY_USAGE_STORAGE | PROPERTY_USAGE_CHECKED;
}
- p_list->push_back(PropertyInfo(Variant::COLOR, "custom_colors/" + E->get(), PROPERTY_HINT_NONE, "", hint));
+ p_list->push_back(PropertyInfo(Variant::COLOR, "custom_colors/" + E->get(), PROPERTY_HINT_NONE, "", usage));
}
}
{
List<StringName> names;
theme->get_constant_list(get_class_name(), &names);
for (List<StringName>::Element *E = names.front(); E; E = E->next()) {
- uint32_t hint = PROPERTY_USAGE_EDITOR | PROPERTY_USAGE_CHECKABLE;
+ uint32_t usage = PROPERTY_USAGE_EDITOR | PROPERTY_USAGE_CHECKABLE;
if (data.constant_override.has(E->get())) {
- hint |= PROPERTY_USAGE_STORAGE | PROPERTY_USAGE_CHECKED;
+ usage |= PROPERTY_USAGE_STORAGE | PROPERTY_USAGE_CHECKED;
}
- p_list->push_back(PropertyInfo(Variant::INT, "custom_constants/" + E->get(), PROPERTY_HINT_RANGE, "-16384,16384", hint));
+ p_list->push_back(PropertyInfo(Variant::INT, "custom_constants/" + E->get(), PROPERTY_HINT_RANGE, "-16384,16384", usage));
}
}
}
+void Control::_validate_property(PropertyInfo &property) const {
+ if (property.name == "theme_type_variation") {
+ List<StringName> names;
+
+ // Only the default theme and the project theme are used for the list of options.
+ // This is an imposed limitation to simplify the logic needed to leverage those options.
+ Theme::get_default()->get_type_variation_list(get_class_name(), &names);
+ if (Theme::get_project_default().is_valid()) {
+ Theme::get_project_default()->get_type_variation_list(get_class_name(), &names);
+ }
+ names.sort_custom<StringName::AlphCompare>();
+
+ Vector<StringName> unique_names;
+ String hint_string;
+ for (List<StringName>::Element *E = names.front(); E; E = E->next()) {
+ // Skip duplicate values.
+ if (unique_names.has(E->get())) {
+ continue;
+ }
+
+ hint_string += String(E->get()) + ",";
+ unique_names.append(E->get());
+ }
+
+ property.hint_string = hint_string;
+ }
+}
+
Control *Control::get_parent_control() const {
return data.parent;
}
@@ -514,7 +536,9 @@ void Control::_notification(int p_notification) {
get_viewport()->_gui_remove_control(this);
} break;
case NOTIFICATION_READY: {
+#ifdef DEBUG_ENABLED
connect("ready", callable_mp(this, &Control::_clear_size_warning), varray(), CONNECT_DEFERRED | CONNECT_ONESHOT);
+#endif
} break;
case NOTIFICATION_ENTER_CANVAS: {
@@ -647,27 +671,11 @@ void Control::_notification(int p_notification) {
}
}
-bool Control::clips_input() const {
- if (get_script_instance()) {
- return get_script_instance()->call(SceneStringNames::get_singleton()->_clips_input);
- }
- return false;
-}
-
bool Control::has_point(const Point2 &p_point) const {
- if (get_script_instance()) {
- Variant v = p_point;
- const Variant *p = &v;
- Callable::CallError ce;
- Variant ret = get_script_instance()->call(SceneStringNames::get_singleton()->has_point, &p, 1, ce);
- if (ce.error == Callable::CallError::CALL_OK) {
- return ret;
- }
+ bool ret;
+ if (GDVIRTUAL_CALL(_has_point, p_point, ret)) {
+ return ret;
}
- /*if (has_stylebox("mask")) {
- Ref<StyleBox> mask = get_stylebox("mask");
- return mask->test_mask(p_point,Rect2(Point2(),get_size()));
- }*/
return Rect2(Point2(), get_size()).has_point(p_point);
}
@@ -684,7 +692,7 @@ Variant Control::get_drag_data(const Point2 &p_point) {
Object *obj = ObjectDB::get_instance(data.drag_owner);
if (obj) {
Control *c = Object::cast_to<Control>(obj);
- return c->call("get_drag_data_fw", p_point, this);
+ return c->call("_get_drag_data_fw", p_point, this);
}
}
@@ -692,7 +700,7 @@ Variant Control::get_drag_data(const Point2 &p_point) {
Variant v = p_point;
const Variant *p = &v;
Callable::CallError ce;
- Variant ret = get_script_instance()->call(SceneStringNames::get_singleton()->get_drag_data, &p, 1, ce);
+ Variant ret = get_script_instance()->call(SceneStringNames::get_singleton()->_get_drag_data, &p, 1, ce);
if (ce.error == Callable::CallError::CALL_OK) {
return ret;
}
@@ -706,7 +714,7 @@ bool Control::can_drop_data(const Point2 &p_point, const Variant &p_data) const
Object *obj = ObjectDB::get_instance(data.drag_owner);
if (obj) {
Control *c = Object::cast_to<Control>(obj);
- return c->call("can_drop_data_fw", p_point, p_data, this);
+ return c->call("_can_drop_data_fw", p_point, p_data, this);
}
}
@@ -714,7 +722,7 @@ bool Control::can_drop_data(const Point2 &p_point, const Variant &p_data) const
Variant v = p_point;
const Variant *p[2] = { &v, &p_data };
Callable::CallError ce;
- Variant ret = get_script_instance()->call(SceneStringNames::get_singleton()->can_drop_data, p, 2, ce);
+ Variant ret = get_script_instance()->call(SceneStringNames::get_singleton()->_can_drop_data, p, 2, ce);
if (ce.error == Callable::CallError::CALL_OK) {
return ret;
}
@@ -728,7 +736,7 @@ void Control::drop_data(const Point2 &p_point, const Variant &p_data) {
Object *obj = ObjectDB::get_instance(data.drag_owner);
if (obj) {
Control *c = Object::cast_to<Control>(obj);
- c->call("drop_data_fw", p_point, p_data, this);
+ c->call("_drop_data_fw", p_point, p_data, this);
return;
}
}
@@ -737,7 +745,7 @@ void Control::drop_data(const Point2 &p_point, const Variant &p_data) {
Variant v = p_point;
const Variant *p[2] = { &v, &p_data };
Callable::CallError ce;
- Variant ret = get_script_instance()->call(SceneStringNames::get_singleton()->drop_data, p, 2, ce);
+ Variant ret = get_script_instance()->call(SceneStringNames::get_singleton()->_drop_data, p, 2, ce);
if (ce.error == Callable::CallError::CALL_OK) {
return;
}
@@ -770,32 +778,27 @@ Size2 Control::get_minimum_size() const {
}
template <class T>
-bool Control::_find_theme_item(Control *p_theme_owner, Window *p_theme_owner_window, T &r_ret, T (Theme::*get_func)(const StringName &, const StringName &) const, bool (Theme::*has_func)(const StringName &, const StringName &) const, const StringName &p_name, const StringName &p_node_type) {
- // try with custom themes
+T Control::get_theme_item_in_types(Control *p_theme_owner, Window *p_theme_owner_window, Theme::DataType p_data_type, const StringName &p_name, List<StringName> p_theme_types) {
+ ERR_FAIL_COND_V_MSG(p_theme_types.size() == 0, T(), "At least one theme type must be specified.");
+
+ // First, look through each control or window node in the branch, until no valid parent can be found.
+ // For each control iterate through its inheritance chain and see if p_name exists in any of them.
Control *theme_owner = p_theme_owner;
Window *theme_owner_window = p_theme_owner_window;
while (theme_owner || theme_owner_window) {
- StringName class_name = p_node_type;
-
- while (class_name != StringName()) {
- if (theme_owner && (theme_owner->data.theme.operator->()->*has_func)(p_name, class_name)) {
- r_ret = (theme_owner->data.theme.operator->()->*get_func)(p_name, class_name);
- return true;
+ for (List<StringName>::Element *E = p_theme_types.front(); E; E = E->next()) {
+ if (theme_owner && theme_owner->data.theme->has_theme_item(p_data_type, p_name, E->get())) {
+ return theme_owner->data.theme->get_theme_item(p_data_type, p_name, E->get());
}
- if (theme_owner_window && (theme_owner_window->theme.operator->()->*has_func)(p_name, class_name)) {
- r_ret = (theme_owner_window->theme.operator->()->*get_func)(p_name, class_name);
- return true;
+ if (theme_owner_window && theme_owner_window->theme->has_theme_item(p_data_type, p_name, E->get())) {
+ return theme_owner_window->theme->get_theme_item(p_data_type, p_name, E->get());
}
-
- class_name = ClassDB::get_parent_class_nocheck(class_name);
}
Node *parent = theme_owner ? theme_owner->get_parent() : theme_owner_window->get_parent();
-
Control *parent_c = Object::cast_to<Control>(parent);
-
if (parent_c) {
theme_owner = parent_c->data.theme_owner;
theme_owner_window = parent_c->data.theme_owner_window;
@@ -810,33 +813,47 @@ bool Control::_find_theme_item(Control *p_theme_owner, Window *p_theme_owner_win
}
}
}
- return false;
+
+ // Secondly, check the project-defined Theme resource.
+ if (Theme::get_project_default().is_valid()) {
+ for (List<StringName>::Element *E = p_theme_types.front(); E; E = E->next()) {
+ if (Theme::get_project_default()->has_theme_item(p_data_type, p_name, E->get())) {
+ return Theme::get_project_default()->get_theme_item(p_data_type, p_name, E->get());
+ }
+ }
+ }
+
+ // Lastly, fall back on the items defined in the default Theme, if they exist.
+ for (List<StringName>::Element *E = p_theme_types.front(); E; E = E->next()) {
+ if (Theme::get_default()->has_theme_item(p_data_type, p_name, E->get())) {
+ return Theme::get_default()->get_theme_item(p_data_type, p_name, E->get());
+ }
+ }
+ // If they don't exist, use any type to return the default/empty value.
+ return Theme::get_default()->get_theme_item(p_data_type, p_name, p_theme_types[0]);
}
-bool Control::_has_theme_item(Control *p_theme_owner, Window *p_theme_owner_window, bool (Theme::*has_func)(const StringName &, const StringName &) const, const StringName &p_name, const StringName &p_node_type) {
- // try with custom themes
+bool Control::has_theme_item_in_types(Control *p_theme_owner, Window *p_theme_owner_window, Theme::DataType p_data_type, const StringName &p_name, List<StringName> p_theme_types) {
+ ERR_FAIL_COND_V_MSG(p_theme_types.size() == 0, false, "At least one theme type must be specified.");
+
+ // First, look through each control or window node in the branch, until no valid parent can be found.
+ // For each control iterate through its inheritance chain and see if p_name exists in any of them.
Control *theme_owner = p_theme_owner;
Window *theme_owner_window = p_theme_owner_window;
while (theme_owner || theme_owner_window) {
- StringName class_name = p_node_type;
-
- while (class_name != StringName()) {
- if (theme_owner && (theme_owner->data.theme.operator->()->*has_func)(p_name, class_name)) {
+ for (List<StringName>::Element *E = p_theme_types.front(); E; E = E->next()) {
+ if (theme_owner && theme_owner->data.theme->has_theme_item(p_data_type, p_name, E->get())) {
return true;
}
- if (theme_owner_window && (theme_owner_window->theme.operator->()->*has_func)(p_name, class_name)) {
+ if (theme_owner_window && theme_owner_window->theme->has_theme_item(p_data_type, p_name, E->get())) {
return true;
}
-
- class_name = ClassDB::get_parent_class_nocheck(class_name);
}
Node *parent = theme_owner ? theme_owner->get_parent() : theme_owner_window->get_parent();
-
Control *parent_c = Object::cast_to<Control>(parent);
-
if (parent_c) {
theme_owner = parent_c->data.theme_owner;
theme_owner_window = parent_c->data.theme_owner_window;
@@ -851,179 +868,113 @@ bool Control::_has_theme_item(Control *p_theme_owner, Window *p_theme_owner_wind
}
}
}
- return false;
-}
-Ref<Texture2D> Control::get_theme_icon(const StringName &p_name, const StringName &p_node_type) const {
- if (p_node_type == StringName() || p_node_type == get_class_name()) {
- const Ref<Texture2D> *tex = data.icon_override.getptr(p_name);
- if (tex) {
- return *tex;
+ // Secondly, check the project-defined Theme resource.
+ if (Theme::get_project_default().is_valid()) {
+ for (List<StringName>::Element *E = p_theme_types.front(); E; E = E->next()) {
+ if (Theme::get_project_default()->has_theme_item(p_data_type, p_name, E->get())) {
+ return true;
+ }
}
}
- StringName type = p_node_type ? p_node_type : get_class_name();
-
- return get_icons(data.theme_owner, data.theme_owner_window, p_name, type);
+ // Lastly, fall back on the items defined in the default Theme, if they exist.
+ for (List<StringName>::Element *E = p_theme_types.front(); E; E = E->next()) {
+ if (Theme::get_default()->has_theme_item(p_data_type, p_name, E->get())) {
+ return true;
+ }
+ }
+ return false;
}
-Ref<Texture2D> Control::get_icons(Control *p_theme_owner, Window *p_theme_owner_window, const StringName &p_name, const StringName &p_node_type) {
- Ref<Texture2D> icon;
-
- if (_find_theme_item(p_theme_owner, p_theme_owner_window, icon, &Theme::get_icon, &Theme::has_icon, p_name, p_node_type)) {
- return icon;
+void Control::_get_theme_type_dependencies(const StringName &p_theme_type, List<StringName> *p_list) const {
+ if (p_theme_type == StringName() || p_theme_type == get_class_name() || p_theme_type == data.theme_type_variation) {
+ if (Theme::get_project_default().is_valid() && Theme::get_project_default()->get_type_variation_base(data.theme_type_variation) != StringName()) {
+ Theme::get_project_default()->get_type_dependencies(get_class_name(), data.theme_type_variation, p_list);
+ } else {
+ Theme::get_default()->get_type_dependencies(get_class_name(), data.theme_type_variation, p_list);
+ }
+ } else {
+ Theme::get_default()->get_type_dependencies(p_theme_type, StringName(), p_list);
}
+}
- if (Theme::get_project_default().is_valid()) {
- if (Theme::get_project_default()->has_icon(p_name, p_node_type)) {
- return Theme::get_project_default()->get_icon(p_name, p_node_type);
+Ref<Texture2D> Control::get_theme_icon(const StringName &p_name, const StringName &p_theme_type) const {
+ if (p_theme_type == StringName() || p_theme_type == get_class_name() || p_theme_type == data.theme_type_variation) {
+ const Ref<Texture2D> *tex = data.icon_override.getptr(p_name);
+ if (tex) {
+ return *tex;
}
}
- return Theme::get_default()->get_icon(p_name, p_node_type);
+ List<StringName> theme_types;
+ _get_theme_type_dependencies(p_theme_type, &theme_types);
+ return get_theme_item_in_types<Ref<Texture2D>>(data.theme_owner, data.theme_owner_window, Theme::DATA_TYPE_ICON, p_name, theme_types);
}
-Ref<StyleBox> Control::get_theme_stylebox(const StringName &p_name, const StringName &p_node_type) const {
- if (p_node_type == StringName() || p_node_type == get_class_name()) {
+Ref<StyleBox> Control::get_theme_stylebox(const StringName &p_name, const StringName &p_theme_type) const {
+ if (p_theme_type == StringName() || p_theme_type == get_class_name() || p_theme_type == data.theme_type_variation) {
const Ref<StyleBox> *style = data.style_override.getptr(p_name);
if (style) {
return *style;
}
}
- StringName type = p_node_type ? p_node_type : get_class_name();
-
- return get_styleboxs(data.theme_owner, data.theme_owner_window, p_name, type);
+ List<StringName> theme_types;
+ _get_theme_type_dependencies(p_theme_type, &theme_types);
+ return get_theme_item_in_types<Ref<StyleBox>>(data.theme_owner, data.theme_owner_window, Theme::DATA_TYPE_STYLEBOX, p_name, theme_types);
}
-Ref<StyleBox> Control::get_styleboxs(Control *p_theme_owner, Window *p_theme_owner_window, const StringName &p_name, const StringName &p_node_type) {
- Ref<StyleBox> stylebox;
-
- if (_find_theme_item(p_theme_owner, p_theme_owner_window, stylebox, &Theme::get_stylebox, &Theme::has_stylebox, p_name, p_node_type)) {
- return stylebox;
- }
-
- if (Theme::get_project_default().is_valid()) {
- if (Theme::get_project_default()->has_stylebox(p_name, p_node_type)) {
- return Theme::get_project_default()->get_stylebox(p_name, p_node_type);
- }
- }
-
- return Theme::get_default()->get_stylebox(p_name, p_node_type);
-}
-
-Ref<Font> Control::get_theme_font(const StringName &p_name, const StringName &p_node_type) const {
- if (p_node_type == StringName() || p_node_type == get_class_name()) {
+Ref<Font> Control::get_theme_font(const StringName &p_name, const StringName &p_theme_type) const {
+ if (p_theme_type == StringName() || p_theme_type == get_class_name() || p_theme_type == data.theme_type_variation) {
const Ref<Font> *font = data.font_override.getptr(p_name);
if (font) {
return *font;
}
}
- StringName type = p_node_type ? p_node_type : get_class_name();
-
- return get_fonts(data.theme_owner, data.theme_owner_window, p_name, type);
+ List<StringName> theme_types;
+ _get_theme_type_dependencies(p_theme_type, &theme_types);
+ return get_theme_item_in_types<Ref<Font>>(data.theme_owner, data.theme_owner_window, Theme::DATA_TYPE_FONT, p_name, theme_types);
}
-int Control::get_theme_font_size(const StringName &p_name, const StringName &p_node_type) const {
- if (p_node_type == StringName() || p_node_type == get_class_name()) {
+int Control::get_theme_font_size(const StringName &p_name, const StringName &p_theme_type) const {
+ if (p_theme_type == StringName() || p_theme_type == get_class_name() || p_theme_type == data.theme_type_variation) {
const int *font_size = data.font_size_override.getptr(p_name);
if (font_size) {
return *font_size;
}
}
- StringName type = p_node_type ? p_node_type : get_class_name();
-
- return get_font_sizes(data.theme_owner, data.theme_owner_window, p_name, type);
-}
-
-Ref<Font> Control::get_fonts(Control *p_theme_owner, Window *p_theme_owner_window, const StringName &p_name, const StringName &p_node_type) {
- Ref<Font> font;
-
- if (_find_theme_item(p_theme_owner, p_theme_owner_window, font, &Theme::get_font, &Theme::has_font, p_name, p_node_type)) {
- return font;
- }
-
- if (Theme::get_project_default().is_valid()) {
- if (Theme::get_project_default()->has_font(p_name, p_node_type)) {
- return Theme::get_project_default()->get_font(p_name, p_node_type);
- }
- }
-
- return Theme::get_default()->get_font(p_name, p_node_type);
-}
-
-int Control::get_font_sizes(Control *p_theme_owner, Window *p_theme_owner_window, const StringName &p_name, const StringName &p_node_type) {
- int font_size;
-
- if (_find_theme_item(p_theme_owner, p_theme_owner_window, font_size, &Theme::get_font_size, &Theme::has_font_size, p_name, p_node_type)) {
- return font_size;
- }
-
- if (Theme::get_project_default().is_valid()) {
- if (Theme::get_project_default()->has_font_size(p_name, p_node_type)) {
- return Theme::get_project_default()->get_font_size(p_name, p_node_type);
- }
- }
-
- return Theme::get_default()->get_font_size(p_name, p_node_type);
+ List<StringName> theme_types;
+ _get_theme_type_dependencies(p_theme_type, &theme_types);
+ return get_theme_item_in_types<int>(data.theme_owner, data.theme_owner_window, Theme::DATA_TYPE_FONT_SIZE, p_name, theme_types);
}
-Color Control::get_theme_color(const StringName &p_name, const StringName &p_node_type) const {
- if (p_node_type == StringName() || p_node_type == get_class_name()) {
+Color Control::get_theme_color(const StringName &p_name, const StringName &p_theme_type) const {
+ if (p_theme_type == StringName() || p_theme_type == get_class_name() || p_theme_type == data.theme_type_variation) {
const Color *color = data.color_override.getptr(p_name);
if (color) {
return *color;
}
}
- StringName type = p_node_type ? p_node_type : get_class_name();
-
- return get_colors(data.theme_owner, data.theme_owner_window, p_name, type);
-}
-
-Color Control::get_colors(Control *p_theme_owner, Window *p_theme_owner_window, const StringName &p_name, const StringName &p_node_type) {
- Color color;
-
- if (_find_theme_item(p_theme_owner, p_theme_owner_window, color, &Theme::get_color, &Theme::has_color, p_name, p_node_type)) {
- return color;
- }
-
- if (Theme::get_project_default().is_valid()) {
- if (Theme::get_project_default()->has_color(p_name, p_node_type)) {
- return Theme::get_project_default()->get_color(p_name, p_node_type);
- }
- }
- return Theme::get_default()->get_color(p_name, p_node_type);
+ List<StringName> theme_types;
+ _get_theme_type_dependencies(p_theme_type, &theme_types);
+ return get_theme_item_in_types<Color>(data.theme_owner, data.theme_owner_window, Theme::DATA_TYPE_COLOR, p_name, theme_types);
}
-int Control::get_theme_constant(const StringName &p_name, const StringName &p_node_type) const {
- if (p_node_type == StringName() || p_node_type == get_class_name()) {
+int Control::get_theme_constant(const StringName &p_name, const StringName &p_theme_type) const {
+ if (p_theme_type == StringName() || p_theme_type == get_class_name() || p_theme_type == data.theme_type_variation) {
const int *constant = data.constant_override.getptr(p_name);
if (constant) {
return *constant;
}
}
- StringName type = p_node_type ? p_node_type : get_class_name();
-
- return get_constants(data.theme_owner, data.theme_owner_window, p_name, type);
-}
-
-int Control::get_constants(Control *p_theme_owner, Window *p_theme_owner_window, const StringName &p_name, const StringName &p_node_type) {
- int constant;
-
- if (_find_theme_item(p_theme_owner, p_theme_owner_window, constant, &Theme::get_constant, &Theme::has_constant, p_name, p_node_type)) {
- return constant;
- }
-
- if (Theme::get_project_default().is_valid()) {
- if (Theme::get_project_default()->has_constant(p_name, p_node_type)) {
- return Theme::get_project_default()->get_constant(p_name, p_node_type);
- }
- }
- return Theme::get_default()->get_constant(p_name, p_node_type);
+ List<StringName> theme_types;
+ _get_theme_type_dependencies(p_theme_type, &theme_types);
+ return get_theme_item_in_types<int>(data.theme_owner, data.theme_owner_window, Theme::DATA_TYPE_CONSTANT, p_name, theme_types);
}
bool Control::has_theme_icon_override(const StringName &p_name) const {
@@ -1056,154 +1007,76 @@ bool Control::has_theme_constant_override(const StringName &p_name) const {
return constant != nullptr;
}
-bool Control::has_theme_icon(const StringName &p_name, const StringName &p_node_type) const {
- if (p_node_type == StringName() || p_node_type == get_class_name()) {
+bool Control::has_theme_icon(const StringName &p_name, const StringName &p_theme_type) const {
+ if (p_theme_type == StringName() || p_theme_type == get_class_name() || p_theme_type == data.theme_type_variation) {
if (has_theme_icon_override(p_name)) {
return true;
}
}
- StringName type = p_node_type ? p_node_type : get_class_name();
-
- return has_icons(data.theme_owner, data.theme_owner_window, p_name, type);
+ List<StringName> theme_types;
+ _get_theme_type_dependencies(p_theme_type, &theme_types);
+ return has_theme_item_in_types(data.theme_owner, data.theme_owner_window, Theme::DATA_TYPE_ICON, p_name, theme_types);
}
-bool Control::has_icons(Control *p_theme_owner, Window *p_theme_owner_window, const StringName &p_name, const StringName &p_node_type) {
- if (_has_theme_item(p_theme_owner, p_theme_owner_window, &Theme::has_icon, p_name, p_node_type)) {
- return true;
- }
-
- if (Theme::get_project_default().is_valid()) {
- if (Theme::get_project_default()->has_color(p_name, p_node_type)) {
- return true;
- }
- }
- return Theme::get_default()->has_icon(p_name, p_node_type);
-}
-
-bool Control::has_theme_stylebox(const StringName &p_name, const StringName &p_node_type) const {
- if (p_node_type == StringName() || p_node_type == get_class_name()) {
+bool Control::has_theme_stylebox(const StringName &p_name, const StringName &p_theme_type) const {
+ if (p_theme_type == StringName() || p_theme_type == get_class_name() || p_theme_type == data.theme_type_variation) {
if (has_theme_stylebox_override(p_name)) {
return true;
}
}
- StringName type = p_node_type ? p_node_type : get_class_name();
-
- return has_styleboxs(data.theme_owner, data.theme_owner_window, p_name, type);
+ List<StringName> theme_types;
+ _get_theme_type_dependencies(p_theme_type, &theme_types);
+ return has_theme_item_in_types(data.theme_owner, data.theme_owner_window, Theme::DATA_TYPE_STYLEBOX, p_name, theme_types);
}
-bool Control::has_styleboxs(Control *p_theme_owner, Window *p_theme_owner_window, const StringName &p_name, const StringName &p_node_type) {
- if (_has_theme_item(p_theme_owner, p_theme_owner_window, &Theme::has_stylebox, p_name, p_node_type)) {
- return true;
- }
-
- if (Theme::get_project_default().is_valid()) {
- if (Theme::get_project_default()->has_stylebox(p_name, p_node_type)) {
- return true;
- }
- }
- return Theme::get_default()->has_stylebox(p_name, p_node_type);
-}
-
-bool Control::has_theme_font(const StringName &p_name, const StringName &p_node_type) const {
- if (p_node_type == StringName() || p_node_type == get_class_name()) {
+bool Control::has_theme_font(const StringName &p_name, const StringName &p_theme_type) const {
+ if (p_theme_type == StringName() || p_theme_type == get_class_name() || p_theme_type == data.theme_type_variation) {
if (has_theme_font_override(p_name)) {
return true;
}
}
- StringName type = p_node_type ? p_node_type : get_class_name();
-
- return has_fonts(data.theme_owner, data.theme_owner_window, p_name, type);
+ List<StringName> theme_types;
+ _get_theme_type_dependencies(p_theme_type, &theme_types);
+ return has_theme_item_in_types(data.theme_owner, data.theme_owner_window, Theme::DATA_TYPE_FONT, p_name, theme_types);
}
-bool Control::has_fonts(Control *p_theme_owner, Window *p_theme_owner_window, const StringName &p_name, const StringName &p_node_type) {
- if (_has_theme_item(p_theme_owner, p_theme_owner_window, &Theme::has_font, p_name, p_node_type)) {
- return true;
- }
-
- if (Theme::get_project_default().is_valid()) {
- if (Theme::get_project_default()->has_font(p_name, p_node_type)) {
- return true;
- }
- }
- return Theme::get_default()->has_font(p_name, p_node_type);
-}
-
-bool Control::has_theme_font_size(const StringName &p_name, const StringName &p_node_type) const {
- if (p_node_type == StringName() || p_node_type == get_class_name()) {
+bool Control::has_theme_font_size(const StringName &p_name, const StringName &p_theme_type) const {
+ if (p_theme_type == StringName() || p_theme_type == get_class_name() || p_theme_type == data.theme_type_variation) {
if (has_theme_font_size_override(p_name)) {
return true;
}
}
- StringName type = p_node_type ? p_node_type : get_class_name();
-
- return has_font_sizes(data.theme_owner, data.theme_owner_window, p_name, type);
+ List<StringName> theme_types;
+ _get_theme_type_dependencies(p_theme_type, &theme_types);
+ return has_theme_item_in_types(data.theme_owner, data.theme_owner_window, Theme::DATA_TYPE_FONT_SIZE, p_name, theme_types);
}
-bool Control::has_font_sizes(Control *p_theme_owner, Window *p_theme_owner_window, const StringName &p_name, const StringName &p_node_type) {
- if (_has_theme_item(p_theme_owner, p_theme_owner_window, &Theme::has_font_size, p_name, p_node_type)) {
- return true;
- }
-
- if (Theme::get_project_default().is_valid()) {
- if (Theme::get_project_default()->has_font_size(p_name, p_node_type)) {
- return true;
- }
- }
- return Theme::get_default()->has_font_size(p_name, p_node_type);
-}
-
-bool Control::has_theme_color(const StringName &p_name, const StringName &p_node_type) const {
- if (p_node_type == StringName() || p_node_type == get_class_name()) {
+bool Control::has_theme_color(const StringName &p_name, const StringName &p_theme_type) const {
+ if (p_theme_type == StringName() || p_theme_type == get_class_name() || p_theme_type == data.theme_type_variation) {
if (has_theme_color_override(p_name)) {
return true;
}
}
- StringName type = p_node_type ? p_node_type : get_class_name();
-
- return has_colors(data.theme_owner, data.theme_owner_window, p_name, type);
-}
-
-bool Control::has_colors(Control *p_theme_owner, Window *p_theme_owner_window, const StringName &p_name, const StringName &p_node_type) {
- if (_has_theme_item(p_theme_owner, p_theme_owner_window, &Theme::has_color, p_name, p_node_type)) {
- return true;
- }
-
- if (Theme::get_project_default().is_valid()) {
- if (Theme::get_project_default()->has_color(p_name, p_node_type)) {
- return true;
- }
- }
- return Theme::get_default()->has_color(p_name, p_node_type);
+ List<StringName> theme_types;
+ _get_theme_type_dependencies(p_theme_type, &theme_types);
+ return has_theme_item_in_types(data.theme_owner, data.theme_owner_window, Theme::DATA_TYPE_COLOR, p_name, theme_types);
}
-bool Control::has_theme_constant(const StringName &p_name, const StringName &p_node_type) const {
- if (p_node_type == StringName() || p_node_type == get_class_name()) {
+bool Control::has_theme_constant(const StringName &p_name, const StringName &p_theme_type) const {
+ if (p_theme_type == StringName() || p_theme_type == get_class_name() || p_theme_type == data.theme_type_variation) {
if (has_theme_constant_override(p_name)) {
return true;
}
}
- StringName type = p_node_type ? p_node_type : get_class_name();
-
- return has_constants(data.theme_owner, data.theme_owner_window, p_name, p_node_type);
-}
-
-bool Control::has_constants(Control *p_theme_owner, Window *p_theme_owner_window, const StringName &p_name, const StringName &p_node_type) {
- if (_has_theme_item(p_theme_owner, p_theme_owner_window, &Theme::has_constant, p_name, p_node_type)) {
- return true;
- }
-
- if (Theme::get_project_default().is_valid()) {
- if (Theme::get_project_default()->has_constant(p_name, p_node_type)) {
- return true;
- }
- }
- return Theme::get_default()->has_constant(p_name, p_node_type);
+ List<StringName> theme_types;
+ _get_theme_type_dependencies(p_theme_type, &theme_types);
+ return has_theme_item_in_types(data.theme_owner, data.theme_owner_window, Theme::DATA_TYPE_CONSTANT, p_name, theme_types);
}
Rect2 Control::get_parent_anchorable_rect() const {
@@ -1217,7 +1090,7 @@ Rect2 Control::get_parent_anchorable_rect() const {
} else {
#ifdef TOOLS_ENABLED
Node *edited_root = get_tree()->get_edited_scene_root();
- if (edited_root && (this == edited_root || edited_root->is_a_parent_of(this))) {
+ if (edited_root && (this == edited_root || edited_root->is_ancestor_of(this))) {
parent_rect.size = Size2(ProjectSettings::get_singleton()->get("display/window/size/width"), ProjectSettings::get_singleton()->get("display/window/size/height"));
} else {
parent_rect = get_viewport()->get_visible_rect();
@@ -1637,7 +1510,7 @@ Point2 Control::get_global_position() const {
Point2 Control::get_screen_position() const {
ERR_FAIL_COND_V(!is_inside_tree(), Point2());
- Point2 global_pos = get_global_position();
+ Point2 global_pos = get_viewport()->get_canvas_transform().xform(get_global_position());
Window *w = Object::cast_to<Window>(get_viewport());
if (w && !w->is_embedding_subwindows()) {
global_pos += w->get_position();
@@ -1714,8 +1587,8 @@ void Control::set_rect(const Rect2 &p_rect) {
void Control::_set_size(const Size2 &p_size) {
#ifdef DEBUG_ENABLED
- if (data.size_warning) {
- WARN_PRINT("Adjusting the size of Control nodes before they are fully initialized is unreliable. Consider deferring it with set_deferred().");
+ if (data.size_warning && (data.anchor[SIDE_LEFT] != data.anchor[SIDE_RIGHT] || data.anchor[SIDE_TOP] != data.anchor[SIDE_BOTTOM])) {
+ WARN_PRINT("Nodes with non-equal opposite anchors will have their size overriden after _ready(). \nIf you want to set size, change the anchors or consider using set_deferred().");
}
#endif
set_size(p_size);
@@ -1780,53 +1653,38 @@ Rect2 Control::get_anchorable_rect() const {
}
void Control::add_theme_icon_override(const StringName &p_name, const Ref<Texture2D> &p_icon) {
+ ERR_FAIL_COND(!p_icon.is_valid());
+
if (data.icon_override.has(p_name)) {
data.icon_override[p_name]->disconnect("changed", callable_mp(this, &Control::_override_changed));
}
- // clear if "null" is passed instead of an icon
- if (p_icon.is_null()) {
- data.icon_override.erase(p_name);
- } else {
- data.icon_override[p_name] = p_icon;
- if (data.icon_override[p_name].is_valid()) {
- data.icon_override[p_name]->connect("changed", callable_mp(this, &Control::_override_changed), Vector<Variant>(), CONNECT_REFERENCE_COUNTED);
- }
- }
+ data.icon_override[p_name] = p_icon;
+ data.icon_override[p_name]->connect("changed", callable_mp(this, &Control::_override_changed), Vector<Variant>(), CONNECT_REFERENCE_COUNTED);
notification(NOTIFICATION_THEME_CHANGED);
}
void Control::add_theme_style_override(const StringName &p_name, const Ref<StyleBox> &p_style) {
+ ERR_FAIL_COND(!p_style.is_valid());
+
if (data.style_override.has(p_name)) {
data.style_override[p_name]->disconnect("changed", callable_mp(this, &Control::_override_changed));
}
- // clear if "null" is passed instead of a style
- if (p_style.is_null()) {
- data.style_override.erase(p_name);
- } else {
- data.style_override[p_name] = p_style;
- if (data.style_override[p_name].is_valid()) {
- data.style_override[p_name]->connect("changed", callable_mp(this, &Control::_override_changed), Vector<Variant>(), CONNECT_REFERENCE_COUNTED);
- }
- }
+ data.style_override[p_name] = p_style;
+ data.style_override[p_name]->connect("changed", callable_mp(this, &Control::_override_changed), Vector<Variant>(), CONNECT_REFERENCE_COUNTED);
notification(NOTIFICATION_THEME_CHANGED);
}
void Control::add_theme_font_override(const StringName &p_name, const Ref<Font> &p_font) {
+ ERR_FAIL_COND(!p_font.is_valid());
+
if (data.font_override.has(p_name)) {
data.font_override[p_name]->disconnect("changed", callable_mp(this, &Control::_override_changed));
}
- // clear if "null" is passed instead of a font
- if (p_font.is_null()) {
- data.font_override.erase(p_name);
- } else {
- data.font_override[p_name] = p_font;
- if (data.font_override[p_name].is_valid()) {
- data.font_override[p_name]->connect("changed", callable_mp(this, &Control::_override_changed), Vector<Variant>(), CONNECT_REFERENCE_COUNTED);
- }
- }
+ data.font_override[p_name] = p_font;
+ data.font_override[p_name]->connect("changed", callable_mp(this, &Control::_override_changed), Vector<Variant>(), CONNECT_REFERENCE_COUNTED);
notification(NOTIFICATION_THEME_CHANGED);
}
@@ -1845,6 +1703,48 @@ void Control::add_theme_constant_override(const StringName &p_name, int p_consta
notification(NOTIFICATION_THEME_CHANGED);
}
+void Control::remove_theme_icon_override(const StringName &p_name) {
+ if (data.icon_override.has(p_name)) {
+ data.icon_override[p_name]->disconnect("changed", callable_mp(this, &Control::_override_changed));
+ }
+
+ data.icon_override.erase(p_name);
+ notification(NOTIFICATION_THEME_CHANGED);
+}
+
+void Control::remove_theme_style_override(const StringName &p_name) {
+ if (data.style_override.has(p_name)) {
+ data.style_override[p_name]->disconnect("changed", callable_mp(this, &Control::_override_changed));
+ }
+
+ data.style_override.erase(p_name);
+ notification(NOTIFICATION_THEME_CHANGED);
+}
+
+void Control::remove_theme_font_override(const StringName &p_name) {
+ if (data.font_override.has(p_name)) {
+ data.font_override[p_name]->disconnect("changed", callable_mp(this, &Control::_override_changed));
+ }
+
+ data.font_override.erase(p_name);
+ notification(NOTIFICATION_THEME_CHANGED);
+}
+
+void Control::remove_theme_font_size_override(const StringName &p_name) {
+ data.font_size_override.erase(p_name);
+ notification(NOTIFICATION_THEME_CHANGED);
+}
+
+void Control::remove_theme_color_override(const StringName &p_name) {
+ data.color_override.erase(p_name);
+ notification(NOTIFICATION_THEME_CHANGED);
+}
+
+void Control::remove_theme_constant_override(const StringName &p_name) {
+ data.constant_override.erase(p_name);
+ notification(NOTIFICATION_THEME_CHANGED);
+}
+
void Control::set_focus_mode(FocusMode p_focus_mode) {
ERR_FAIL_INDEX((int)p_focus_mode, 3);
@@ -2149,19 +2049,22 @@ void Control::set_theme(const Ref<Theme> &p_theme) {
}
}
-void Control::accept_event() {
- if (is_inside_tree()) {
- get_viewport()->_gui_accept_event();
- }
-}
-
Ref<Theme> Control::get_theme() const {
return data.theme;
}
+void Control::set_theme_type_variation(const StringName &p_theme_type) {
+ data.theme_type_variation = p_theme_type;
+ _propagate_theme_changed(this, data.theme_owner, data.theme_owner_window);
+}
+
+StringName Control::get_theme_type_variation() const {
+ return data.theme_type_variation;
+}
+
void Control::set_tooltip(const String &p_tooltip) {
data.tooltip = p_tooltip;
- update_configuration_warning();
+ update_configuration_warnings();
}
String Control::get_tooltip(const Point2 &p_pos) const {
@@ -2446,7 +2349,7 @@ int Control::get_v_size_flags() const {
void Control::set_mouse_filter(MouseFilter p_filter) {
ERR_FAIL_INDEX(p_filter, 3);
data.mouse_filter = p_filter;
- update_configuration_warning();
+ update_configuration_warnings();
}
Control::MouseFilter Control::get_mouse_filter() const {
@@ -2477,9 +2380,9 @@ bool Control::is_text_field() const {
return false;
}
-Vector<Vector2i> Control::structured_text_parser(StructuredTextParser p_node_type, const Array &p_args, const String p_text) const {
+Vector<Vector2i> Control::structured_text_parser(StructuredTextParser p_theme_type, const Array &p_args, const String p_text) const {
Vector<Vector2i> ret;
- switch (p_node_type) {
+ switch (p_theme_type) {
case STRUCTURED_TEXT_URI: {
int prev = 0;
for (int i = 0; i < p_text.length(); i++) {
@@ -2576,14 +2479,6 @@ real_t Control::get_rotation() const {
return data.rotation;
}
-void Control::set_rotation_degrees(real_t p_degrees) {
- set_rotation(Math::deg2rad(p_degrees));
-}
-
-real_t Control::get_rotation_degrees() const {
- return Math::rad2deg(get_rotation());
-}
-
void Control::_override_changed() {
notification(NOTIFICATION_THEME_CHANGED);
emit_signal(SceneStringNames::get_singleton()->theme_changed);
@@ -2666,15 +2561,15 @@ void Control::get_argument_options(const StringName &p_function, int p_idx, List
if (p_idx == 0) {
List<StringName> sn;
String pf = p_function;
- if (pf == "add_color_override" || pf == "has_color" || pf == "has_color_override" || pf == "get_color") {
+ if (pf == "add_theme_color_override" || pf == "has_theme_color" || pf == "has_theme_color_override" || pf == "get_theme_color") {
Theme::get_default()->get_color_list(get_class(), &sn);
- } else if (pf == "add_style_override" || pf == "has_style" || pf == "has_style_override" || pf == "get_style") {
+ } else if (pf == "add_theme_style_override" || pf == "has_theme_style" || pf == "has_theme_style_override" || pf == "get_theme_style") {
Theme::get_default()->get_stylebox_list(get_class(), &sn);
- } else if (pf == "add_font_override" || pf == "has_font" || pf == "has_font_override" || pf == "get_font") {
+ } else if (pf == "add_theme_font_override" || pf == "has_theme_font" || pf == "has_theme_font_override" || pf == "get_theme_font") {
Theme::get_default()->get_font_list(get_class(), &sn);
- } else if (pf == "add_font_size_override" || pf == "has_font_size" || pf == "has_font_size_override" || pf == "get_font_size") {
+ } else if (pf == "add_theme_font_size_override" || pf == "has_theme_font_size" || pf == "has_theme_font_size_override" || pf == "get_theme_font_size") {
Theme::get_default()->get_font_size_list(get_class(), &sn);
- } else if (pf == "add_constant_override" || pf == "has_constant" || pf == "has_constant_override" || pf == "get_constant") {
+ } else if (pf == "add_theme_constant_override" || pf == "has_theme_constant" || pf == "has_theme_constant_override" || pf == "get_theme_constant") {
Theme::get_default()->get_constant_list(get_class(), &sn);
}
@@ -2685,17 +2580,14 @@ void Control::get_argument_options(const StringName &p_function, int p_idx, List
}
}
-String Control::get_configuration_warning() const {
- String warning = CanvasItem::get_configuration_warning();
+TypedArray<String> Control::get_configuration_warnings() const {
+ TypedArray<String> warnings = Node::get_configuration_warnings();
if (data.mouse_filter == MOUSE_FILTER_IGNORE && data.tooltip != "") {
- if (!warning.is_empty()) {
- warning += "\n\n";
- }
- warning += TTR("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\".");
+ warnings.push_back(TTR("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\"."));
}
- return warning;
+ return warnings;
}
void Control::set_clip_contents(bool p_clip) {
@@ -2754,7 +2646,6 @@ void Control::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_global_position", "position", "keep_offsets"), &Control::set_global_position, DEFVAL(false));
ClassDB::bind_method(D_METHOD("_set_global_position", "position"), &Control::_set_global_position);
ClassDB::bind_method(D_METHOD("set_rotation", "radians"), &Control::set_rotation);
- ClassDB::bind_method(D_METHOD("set_rotation_degrees", "degrees"), &Control::set_rotation_degrees);
ClassDB::bind_method(D_METHOD("set_scale", "scale"), &Control::set_scale);
ClassDB::bind_method(D_METHOD("set_pivot_offset", "pivot_offset"), &Control::set_pivot_offset);
ClassDB::bind_method(D_METHOD("get_offset", "offset"), &Control::get_offset);
@@ -2763,7 +2654,6 @@ void Control::_bind_methods() {
ClassDB::bind_method(D_METHOD("get_position"), &Control::get_position);
ClassDB::bind_method(D_METHOD("get_size"), &Control::get_size);
ClassDB::bind_method(D_METHOD("get_rotation"), &Control::get_rotation);
- ClassDB::bind_method(D_METHOD("get_rotation_degrees"), &Control::get_rotation_degrees);
ClassDB::bind_method(D_METHOD("get_scale"), &Control::get_scale);
ClassDB::bind_method(D_METHOD("get_pivot_offset"), &Control::get_pivot_offset);
ClassDB::bind_method(D_METHOD("get_custom_minimum_size"), &Control::get_custom_minimum_size);
@@ -2792,6 +2682,9 @@ void Control::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_theme", "theme"), &Control::set_theme);
ClassDB::bind_method(D_METHOD("get_theme"), &Control::get_theme);
+ ClassDB::bind_method(D_METHOD("set_theme_type_variation", "theme_type"), &Control::set_theme_type_variation);
+ ClassDB::bind_method(D_METHOD("get_theme_type_variation"), &Control::get_theme_type_variation);
+
ClassDB::bind_method(D_METHOD("add_theme_icon_override", "name", "texture"), &Control::add_theme_icon_override);
ClassDB::bind_method(D_METHOD("add_theme_stylebox_override", "name", "stylebox"), &Control::add_theme_style_override);
ClassDB::bind_method(D_METHOD("add_theme_font_override", "name", "font"), &Control::add_theme_font_override);
@@ -2799,12 +2692,19 @@ void Control::_bind_methods() {
ClassDB::bind_method(D_METHOD("add_theme_color_override", "name", "color"), &Control::add_theme_color_override);
ClassDB::bind_method(D_METHOD("add_theme_constant_override", "name", "constant"), &Control::add_theme_constant_override);
- ClassDB::bind_method(D_METHOD("get_theme_icon", "name", "node_type"), &Control::get_theme_icon, DEFVAL(""));
- ClassDB::bind_method(D_METHOD("get_theme_stylebox", "name", "node_type"), &Control::get_theme_stylebox, DEFVAL(""));
- ClassDB::bind_method(D_METHOD("get_theme_font", "name", "node_type"), &Control::get_theme_font, DEFVAL(""));
- ClassDB::bind_method(D_METHOD("get_theme_font_size", "name", "node_type"), &Control::get_theme_font_size, DEFVAL(""));
- ClassDB::bind_method(D_METHOD("get_theme_color", "name", "node_type"), &Control::get_theme_color, DEFVAL(""));
- ClassDB::bind_method(D_METHOD("get_theme_constant", "name", "node_type"), &Control::get_theme_constant, DEFVAL(""));
+ ClassDB::bind_method(D_METHOD("remove_theme_icon_override", "name"), &Control::remove_theme_icon_override);
+ ClassDB::bind_method(D_METHOD("remove_theme_stylebox_override", "name"), &Control::remove_theme_style_override);
+ ClassDB::bind_method(D_METHOD("remove_theme_font_override", "name"), &Control::remove_theme_font_override);
+ ClassDB::bind_method(D_METHOD("remove_theme_font_size_override", "name"), &Control::remove_theme_font_size_override);
+ ClassDB::bind_method(D_METHOD("remove_theme_color_override", "name"), &Control::remove_theme_color_override);
+ ClassDB::bind_method(D_METHOD("remove_theme_constant_override", "name"), &Control::remove_theme_constant_override);
+
+ ClassDB::bind_method(D_METHOD("get_theme_icon", "name", "theme_type"), &Control::get_theme_icon, DEFVAL(""));
+ ClassDB::bind_method(D_METHOD("get_theme_stylebox", "name", "theme_type"), &Control::get_theme_stylebox, DEFVAL(""));
+ ClassDB::bind_method(D_METHOD("get_theme_font", "name", "theme_type"), &Control::get_theme_font, DEFVAL(""));
+ ClassDB::bind_method(D_METHOD("get_theme_font_size", "name", "theme_type"), &Control::get_theme_font_size, DEFVAL(""));
+ ClassDB::bind_method(D_METHOD("get_theme_color", "name", "theme_type"), &Control::get_theme_color, DEFVAL(""));
+ ClassDB::bind_method(D_METHOD("get_theme_constant", "name", "theme_type"), &Control::get_theme_constant, DEFVAL(""));
ClassDB::bind_method(D_METHOD("has_theme_icon_override", "name"), &Control::has_theme_icon_override);
ClassDB::bind_method(D_METHOD("has_theme_stylebox_override", "name"), &Control::has_theme_stylebox_override);
@@ -2813,12 +2713,12 @@ void Control::_bind_methods() {
ClassDB::bind_method(D_METHOD("has_theme_color_override", "name"), &Control::has_theme_color_override);
ClassDB::bind_method(D_METHOD("has_theme_constant_override", "name"), &Control::has_theme_constant_override);
- ClassDB::bind_method(D_METHOD("has_theme_icon", "name", "node_type"), &Control::has_theme_icon, DEFVAL(""));
- ClassDB::bind_method(D_METHOD("has_theme_stylebox", "name", "node_type"), &Control::has_theme_stylebox, DEFVAL(""));
- ClassDB::bind_method(D_METHOD("has_theme_font", "name", "node_type"), &Control::has_theme_font, DEFVAL(""));
- ClassDB::bind_method(D_METHOD("has_theme_font_size", "name", "node_type"), &Control::has_theme_font_size, DEFVAL(""));
- ClassDB::bind_method(D_METHOD("has_theme_color", "name", "node_type"), &Control::has_theme_color, DEFVAL(""));
- ClassDB::bind_method(D_METHOD("has_theme_constant", "name", "node_type"), &Control::has_theme_constant, DEFVAL(""));
+ ClassDB::bind_method(D_METHOD("has_theme_icon", "name", "theme_type"), &Control::has_theme_icon, DEFVAL(""));
+ ClassDB::bind_method(D_METHOD("has_theme_stylebox", "name", "theme_type"), &Control::has_theme_stylebox, DEFVAL(""));
+ ClassDB::bind_method(D_METHOD("has_theme_font", "name", "theme_type"), &Control::has_theme_font, DEFVAL(""));
+ ClassDB::bind_method(D_METHOD("has_theme_font_size", "name", "theme_type"), &Control::has_theme_font_size, DEFVAL(""));
+ ClassDB::bind_method(D_METHOD("has_theme_color", "name", "theme_type"), &Control::has_theme_color, DEFVAL(""));
+ ClassDB::bind_method(D_METHOD("has_theme_constant", "name", "theme_type"), &Control::has_theme_constant, DEFVAL(""));
ClassDB::bind_method(D_METHOD("get_parent_control"), &Control::get_parent_control);
@@ -2871,16 +2771,15 @@ void Control::_bind_methods() {
BIND_VMETHOD(MethodInfo("_gui_input", PropertyInfo(Variant::OBJECT, "event", PROPERTY_HINT_RESOURCE_TYPE, "InputEvent")));
BIND_VMETHOD(MethodInfo(Variant::VECTOR2, "_get_minimum_size"));
- MethodInfo get_drag_data = MethodInfo("get_drag_data", PropertyInfo(Variant::VECTOR2, "position"));
+ MethodInfo get_drag_data = MethodInfo("_get_drag_data", PropertyInfo(Variant::VECTOR2, "position"));
get_drag_data.return_val.usage |= PROPERTY_USAGE_NIL_IS_VARIANT;
BIND_VMETHOD(get_drag_data);
- BIND_VMETHOD(MethodInfo(Variant::BOOL, "can_drop_data", PropertyInfo(Variant::VECTOR2, "position"), PropertyInfo(Variant::NIL, "data")));
- BIND_VMETHOD(MethodInfo("drop_data", PropertyInfo(Variant::VECTOR2, "position"), PropertyInfo(Variant::NIL, "data")));
+ BIND_VMETHOD(MethodInfo(Variant::BOOL, "_can_drop_data", PropertyInfo(Variant::VECTOR2, "position"), PropertyInfo(Variant::NIL, "data")));
+ BIND_VMETHOD(MethodInfo("_drop_data", PropertyInfo(Variant::VECTOR2, "position"), PropertyInfo(Variant::NIL, "data")));
BIND_VMETHOD(MethodInfo(
PropertyInfo(Variant::OBJECT, "control", PROPERTY_HINT_RESOURCE_TYPE, "Control"),
"_make_custom_tooltip", PropertyInfo(Variant::STRING, "for_text")));
- BIND_VMETHOD(MethodInfo(Variant::BOOL, "_clips_input"));
ADD_GROUP("Anchor", "anchor_");
ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "anchor_left", PROPERTY_HINT_RANGE, "0,1,0.001,or_lesser,or_greater"), "_set_anchor", "get_anchor", SIDE_LEFT);
@@ -2899,15 +2798,14 @@ void Control::_bind_methods() {
ADD_PROPERTY(PropertyInfo(Variant::INT, "grow_vertical", PROPERTY_HINT_ENUM, "Begin,End,Both"), "set_v_grow_direction", "get_v_grow_direction");
ADD_GROUP("Layout Direction", "layout_");
- ADD_PROPERTY(PropertyInfo(Variant::INT, "layout_direction", PROPERTY_HINT_ENUM, "Inherited,Locale,Left-to-right,Right-to-left"), "set_layout_direction", "get_layout_direction");
+ ADD_PROPERTY(PropertyInfo(Variant::INT, "layout_direction", PROPERTY_HINT_ENUM, "Inherited,Locale,Left-to-Right,Right-to-Left"), "set_layout_direction", "get_layout_direction");
ADD_GROUP("Rect", "rect_");
ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "rect_position", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_EDITOR), "_set_position", "get_position");
- ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "rect_global_position", PROPERTY_HINT_NONE, "", 0), "_set_global_position", "get_global_position");
+ ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "rect_global_position", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NONE), "_set_global_position", "get_global_position");
ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "rect_size", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_EDITOR), "_set_size", "get_size");
ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "rect_min_size"), "set_custom_minimum_size", "get_custom_minimum_size");
- ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "rect_rotation", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR), "set_rotation", "get_rotation");
- ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "rect_rotation_degrees", PROPERTY_HINT_RANGE, "-360,360,0.1,or_lesser,or_greater"), "set_rotation_degrees", "get_rotation_degrees");
+ ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "rect_rotation", PROPERTY_HINT_RANGE, "-360,360,0.1,or_lesser,or_greater,radians"), "set_rotation", "get_rotation");
ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "rect_scale"), "set_scale", "get_scale");
ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "rect_pivot_offset"), "set_pivot_offset", "get_pivot_offset");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "rect_clip_content"), "set_clip_contents", "is_clipping_contents");
@@ -2926,14 +2824,15 @@ void Control::_bind_methods() {
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_default_cursor_shape", PROPERTY_HINT_ENUM, "Arrow,Ibeam,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");
+ 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");
ADD_GROUP("Size Flags", "size_flags_");
ADD_PROPERTY(PropertyInfo(Variant::INT, "size_flags_horizontal", PROPERTY_HINT_FLAGS, "Fill,Expand,Shrink Center,Shrink End"), "set_h_size_flags", "get_h_size_flags");
ADD_PROPERTY(PropertyInfo(Variant::INT, "size_flags_vertical", PROPERTY_HINT_FLAGS, "Fill,Expand,Shrink Center,Shrink End"), "set_v_size_flags", "get_v_size_flags");
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "size_flags_stretch_ratio", PROPERTY_HINT_RANGE, "0,20,0.01,or_greater"), "set_stretch_ratio", "get_stretch_ratio");
- ADD_GROUP("Theme", "");
+ ADD_GROUP("Theme", "theme_");
ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "theme", PROPERTY_HINT_RESOURCE_TYPE, "Theme"), "set_theme", "get_theme");
+ ADD_PROPERTY(PropertyInfo(Variant::STRING, "theme_type_variation", PROPERTY_HINT_ENUM_SUGGESTION), "set_theme_type_variation", "get_theme_type_variation");
ADD_GROUP("", "");
BIND_ENUM_CONSTANT(FOCUS_NONE);
@@ -3035,5 +2934,5 @@ void Control::_bind_methods() {
ADD_SIGNAL(MethodInfo("minimum_size_changed"));
ADD_SIGNAL(MethodInfo("theme_changed"));
- BIND_VMETHOD(MethodInfo(Variant::BOOL, "has_point", PropertyInfo(Variant::VECTOR2, "point")));
+ GDVIRTUAL_BIND(_has_point);
}
diff --git a/scene/gui/control.h b/scene/gui/control.h
index a911d69c3f..fb01295668 100644
--- a/scene/gui/control.h
+++ b/scene/gui/control.h
@@ -32,6 +32,7 @@
#define CONTROL_H
#include "core/math/transform_2d.h"
+#include "core/object/gdvirtual.gen.inc"
#include "core/templates/rid.h"
#include "scene/gui/shortcut.h"
#include "scene/main/canvas_item.h"
@@ -201,6 +202,8 @@ private:
Ref<Theme> theme;
Control *theme_owner = nullptr;
Window *theme_owner_window = nullptr;
+ StringName theme_type_variation;
+
String tooltip;
CursorShape default_cursor = CURSOR_ARROW;
@@ -258,39 +261,26 @@ private:
static void _propagate_theme_changed(Node *p_at, Control *p_owner, Window *p_owner_window, bool p_assign = true);
template <class T>
- _FORCE_INLINE_ static bool _find_theme_item(Control *p_theme_owner, Window *p_theme_owner_window, T &, T (Theme::*get_func)(const StringName &, const StringName &) const, bool (Theme::*has_func)(const StringName &, const StringName &) const, const StringName &p_name, const StringName &p_node_type);
-
- _FORCE_INLINE_ static bool _has_theme_item(Control *p_theme_owner, Window *p_theme_owner_window, bool (Theme::*has_func)(const StringName &, const StringName &) const, const StringName &p_name, const StringName &p_node_type);
-
- static Ref<Texture2D> get_icons(Control *p_theme_owner, Window *p_theme_owner_window, const StringName &p_name, const StringName &p_node_type = StringName());
- static Ref<StyleBox> get_styleboxs(Control *p_theme_owner, Window *p_theme_owner_window, const StringName &p_name, const StringName &p_node_type = StringName());
- static Ref<Font> get_fonts(Control *p_theme_owner, Window *p_theme_owner_window, const StringName &p_name, const StringName &p_node_type = StringName());
- static int get_font_sizes(Control *p_theme_owner, Window *p_theme_owner_window, const StringName &p_name, const StringName &p_node_type = StringName());
- static Color get_colors(Control *p_theme_owner, Window *p_theme_owner_window, const StringName &p_name, const StringName &p_node_type = StringName());
- static int get_constants(Control *p_theme_owner, Window *p_theme_owner_window, const StringName &p_name, const StringName &p_node_type = StringName());
-
- static bool has_icons(Control *p_theme_owner, Window *p_theme_owner_window, const StringName &p_name, const StringName &p_node_type = StringName());
- static bool has_styleboxs(Control *p_theme_owner, Window *p_theme_owner_window, const StringName &p_name, const StringName &p_node_type = StringName());
- static bool has_fonts(Control *p_theme_owner, Window *p_theme_owner_window, const StringName &p_name, const StringName &p_node_type = StringName());
- static bool has_font_sizes(Control *p_theme_owner, Window *p_theme_owner_window, const StringName &p_name, const StringName &p_node_type = StringName());
- static bool has_colors(Control *p_theme_owner, Window *p_theme_owner_window, const StringName &p_name, const StringName &p_node_type = StringName());
- static bool has_constants(Control *p_theme_owner, Window *p_theme_owner_window, const StringName &p_name, const StringName &p_node_type = StringName());
+ static T get_theme_item_in_types(Control *p_theme_owner, Window *p_theme_owner_window, Theme::DataType p_data_type, const StringName &p_name, List<StringName> p_theme_types);
+ static bool has_theme_item_in_types(Control *p_theme_owner, Window *p_theme_owner_window, Theme::DataType p_data_type, const StringName &p_name, List<StringName> p_theme_types);
+ _FORCE_INLINE_ void _get_theme_type_dependencies(const StringName &p_theme_type, List<StringName> *p_list) const;
+ GDVIRTUAL1RC(bool, _has_point, Vector2)
protected:
virtual void add_child_notify(Node *p_child) override;
virtual void remove_child_notify(Node *p_child) override;
//virtual void _window_gui_input(InputEvent p_event);
- virtual Vector<Vector2i> structured_text_parser(StructuredTextParser p_node_type, const Array &p_args, const String p_text) const;
+ virtual Vector<Vector2i> structured_text_parser(StructuredTextParser p_theme_type, const Array &p_args, const String p_text) const;
bool _set(const StringName &p_name, const Variant &p_value);
bool _get(const StringName &p_name, Variant &r_ret) const;
void _get_property_list(List<PropertyInfo> *p_list) const;
void _notification(int p_notification);
-
static void _bind_methods();
+ virtual void _validate_property(PropertyInfo &property) const override;
//bind helpers
@@ -341,7 +331,6 @@ public:
virtual Size2 get_minimum_size() const;
virtual Size2 get_combined_minimum_size() const;
virtual bool has_point(const Point2 &p_point) const;
- virtual bool clips_input() const;
virtual void set_drag_forwarding(Control *p_target);
virtual Variant get_drag_data(const Point2 &p_point);
virtual bool can_drop_data(const Point2 &p_point, const Variant &p_data) const;
@@ -396,9 +385,7 @@ public:
void set_rect(const Rect2 &p_rect); // Reset anchors to begin and set rect, for faster container children sorting.
void set_rotation(real_t p_radians);
- void set_rotation_degrees(real_t p_degrees);
real_t get_rotation() const;
- real_t get_rotation_degrees() const;
void set_h_grow_direction(GrowDirection p_direction);
GrowDirection get_h_grow_direction() const;
@@ -415,6 +402,9 @@ public:
void set_theme(const Ref<Theme> &p_theme);
Ref<Theme> get_theme() const;
+ void set_theme_type_variation(const StringName &p_theme_type);
+ StringName get_theme_type_variation() const;
+
void set_h_size_flags(int p_flags);
int get_h_size_flags() const;
@@ -459,12 +449,19 @@ public:
void add_theme_color_override(const StringName &p_name, const Color &p_color);
void add_theme_constant_override(const StringName &p_name, int p_constant);
- Ref<Texture2D> get_theme_icon(const StringName &p_name, const StringName &p_node_type = StringName()) const;
- Ref<StyleBox> get_theme_stylebox(const StringName &p_name, const StringName &p_node_type = StringName()) const;
- Ref<Font> get_theme_font(const StringName &p_name, const StringName &p_node_type = StringName()) const;
- int get_theme_font_size(const StringName &p_name, const StringName &p_node_type = StringName()) const;
- Color get_theme_color(const StringName &p_name, const StringName &p_node_type = StringName()) const;
- int get_theme_constant(const StringName &p_name, const StringName &p_node_type = StringName()) const;
+ void remove_theme_icon_override(const StringName &p_name);
+ void remove_theme_style_override(const StringName &p_name);
+ void remove_theme_font_override(const StringName &p_name);
+ void remove_theme_font_size_override(const StringName &p_name);
+ void remove_theme_color_override(const StringName &p_name);
+ void remove_theme_constant_override(const StringName &p_name);
+
+ Ref<Texture2D> get_theme_icon(const StringName &p_name, const StringName &p_theme_type = StringName()) const;
+ Ref<StyleBox> get_theme_stylebox(const StringName &p_name, const StringName &p_theme_type = StringName()) const;
+ Ref<Font> get_theme_font(const StringName &p_name, const StringName &p_theme_type = StringName()) const;
+ int get_theme_font_size(const StringName &p_name, const StringName &p_theme_type = StringName()) const;
+ Color get_theme_color(const StringName &p_name, const StringName &p_theme_type = StringName()) const;
+ int get_theme_constant(const StringName &p_name, const StringName &p_theme_type = StringName()) const;
bool has_theme_icon_override(const StringName &p_name) const;
bool has_theme_stylebox_override(const StringName &p_name) const;
@@ -473,12 +470,12 @@ public:
bool has_theme_color_override(const StringName &p_name) const;
bool has_theme_constant_override(const StringName &p_name) const;
- bool has_theme_icon(const StringName &p_name, const StringName &p_node_type = StringName()) const;
- bool has_theme_stylebox(const StringName &p_name, const StringName &p_node_type = StringName()) const;
- bool has_theme_font(const StringName &p_name, const StringName &p_node_type = StringName()) const;
- bool has_theme_font_size(const StringName &p_name, const StringName &p_node_type = StringName()) const;
- bool has_theme_color(const StringName &p_name, const StringName &p_node_type = StringName()) const;
- bool has_theme_constant(const StringName &p_name, const StringName &p_node_type = StringName()) const;
+ bool has_theme_icon(const StringName &p_name, const StringName &p_theme_type = StringName()) const;
+ bool has_theme_stylebox(const StringName &p_name, const StringName &p_theme_type = StringName()) const;
+ bool has_theme_font(const StringName &p_name, const StringName &p_theme_type = StringName()) const;
+ bool has_theme_font_size(const StringName &p_name, const StringName &p_theme_type = StringName()) const;
+ bool has_theme_color(const StringName &p_name, const StringName &p_theme_type = StringName()) const;
+ bool has_theme_constant(const StringName &p_name, const StringName &p_theme_type = StringName()) const;
/* TOOLTIP */
@@ -517,7 +514,7 @@ public:
bool is_visibility_clip_disabled() const;
virtual void get_argument_options(const StringName &p_function, int p_idx, List<String> *r_options) const override;
- virtual String get_configuration_warning() const override;
+ TypedArray<String> get_configuration_warnings() const override;
Control() {}
};
diff --git a/scene/gui/dialogs.cpp b/scene/gui/dialogs.cpp
index b6884bd37d..9d0a6a3380 100644
--- a/scene/gui/dialogs.cpp
+++ b/scene/gui/dialogs.cpp
@@ -97,7 +97,7 @@ void AcceptDialog::_notification(int p_what) {
}
}
-void AcceptDialog::_text_entered(const String &p_text) {
+void AcceptDialog::_text_submitted(const String &p_text) {
_ok_pressed();
}
@@ -148,18 +148,18 @@ bool AcceptDialog::get_hide_on_ok() const {
}
void AcceptDialog::set_autowrap(bool p_autowrap) {
- label->set_autowrap(p_autowrap);
+ label->set_autowrap_mode(p_autowrap ? Label::AUTOWRAP_WORD : Label::AUTOWRAP_OFF);
}
bool AcceptDialog::has_autowrap() {
- return label->has_autowrap();
+ return label->get_autowrap_mode() != Label::AUTOWRAP_OFF;
}
-void AcceptDialog::register_text_enter(Node *p_line_edit) {
+void AcceptDialog::register_text_enter(Control *p_line_edit) {
ERR_FAIL_NULL(p_line_edit);
LineEdit *line_edit = Object::cast_to<LineEdit>(p_line_edit);
if (line_edit) {
- line_edit->connect("text_entered", callable_mp(this, &AcceptDialog::_text_entered));
+ line_edit->connect("text_submitted", callable_mp(this, &AcceptDialog::_text_submitted));
}
}
@@ -263,6 +263,28 @@ Button *AcceptDialog::add_cancel_button(const String &p_cancel) {
return b;
}
+void AcceptDialog::remove_button(Control *p_button) {
+ Button *button = Object::cast_to<Button>(p_button);
+ ERR_FAIL_NULL(button);
+ ERR_FAIL_COND_MSG(button->get_parent() != hbc, vformat("Cannot remove button %s as it does not belong to this dialog.", button->get_name()));
+ ERR_FAIL_COND_MSG(button == ok, "Cannot remove dialog's OK button.");
+
+ Node *right_spacer = hbc->get_child(button->get_index() + 1);
+ // Should always be valid but let's avoid crashing
+ if (right_spacer) {
+ hbc->remove_child(right_spacer);
+ memdelete(right_spacer);
+ }
+ hbc->remove_child(button);
+
+ if (button->is_connected("pressed", callable_mp(this, &AcceptDialog::_custom_action))) {
+ button->disconnect("pressed", callable_mp(this, &AcceptDialog::_custom_action));
+ }
+ if (button->is_connected("pressed", callable_mp(this, &AcceptDialog::_cancel_pressed))) {
+ button->disconnect("pressed", callable_mp(this, &AcceptDialog::_cancel_pressed));
+ }
+}
+
void AcceptDialog::_bind_methods() {
ClassDB::bind_method(D_METHOD("get_ok_button"), &AcceptDialog::get_ok_button);
ClassDB::bind_method(D_METHOD("get_label"), &AcceptDialog::get_label);
@@ -270,6 +292,7 @@ void AcceptDialog::_bind_methods() {
ClassDB::bind_method(D_METHOD("get_hide_on_ok"), &AcceptDialog::get_hide_on_ok);
ClassDB::bind_method(D_METHOD("add_button", "text", "right", "action"), &AcceptDialog::add_button, DEFVAL(false), DEFVAL(""));
ClassDB::bind_method(D_METHOD("add_cancel_button", "name"), &AcceptDialog::add_cancel_button);
+ ClassDB::bind_method(D_METHOD("remove_button", "button"), &AcceptDialog::remove_button);
ClassDB::bind_method(D_METHOD("register_text_enter", "line_edit"), &AcceptDialog::register_text_enter);
ClassDB::bind_method(D_METHOD("set_text", "text"), &AcceptDialog::set_text);
ClassDB::bind_method(D_METHOD("get_text"), &AcceptDialog::get_text);
diff --git a/scene/gui/dialogs.h b/scene/gui/dialogs.h
index b072055d49..8e803a2a7c 100644
--- a/scene/gui/dialogs.h
+++ b/scene/gui/dialogs.h
@@ -69,7 +69,7 @@ protected:
virtual void custom_action(const String &) {}
// Not private since used by derived classes signal.
- void _text_entered(const String &p_text);
+ void _text_submitted(const String &p_text);
void _ok_pressed();
void _cancel_pressed();
@@ -77,11 +77,12 @@ public:
Label *get_label() { return label; }
static void set_swap_cancel_ok(bool p_swap);
- void register_text_enter(Node *p_line_edit);
+ void register_text_enter(Control *p_line_edit);
Button *get_ok_button() { return ok; }
Button *add_button(const String &p_text, bool p_right = false, const String &p_action = "");
Button *add_cancel_button(const String &p_cancel = "");
+ void remove_button(Control *p_button);
void set_hide_on_ok(bool p_hide);
bool get_hide_on_ok() const;
diff --git a/scene/gui/file_dialog.cpp b/scene/gui/file_dialog.cpp
index 7ac8dbccca..f8cee6daec 100644
--- a/scene/gui/file_dialog.cpp
+++ b/scene/gui/file_dialog.cpp
@@ -96,6 +96,8 @@ void FileDialog::_notification(int p_what) {
}
void FileDialog::_unhandled_input(const Ref<InputEvent> &p_event) {
+ ERR_FAIL_COND(p_event.is_null());
+
Ref<InputEventKey> k = p_event;
if (k.is_valid() && has_focus()) {
if (k->is_pressed()) {
@@ -103,7 +105,7 @@ void FileDialog::_unhandled_input(const Ref<InputEvent> &p_event) {
switch (k->get_keycode()) {
case KEY_H: {
- if (k->get_command()) {
+ if (k->is_command_pressed()) {
set_show_hidden_files(!show_hidden_files);
} else {
handled = false;
@@ -114,7 +116,7 @@ void FileDialog::_unhandled_input(const Ref<InputEvent> &p_event) {
invalidate();
} break;
case KEY_BACKSPACE: {
- _dir_entered("..");
+ _dir_submitted("..");
} break;
default: {
handled = false;
@@ -154,7 +156,7 @@ void FileDialog::update_dir() {
deselect_all();
}
-void FileDialog::_dir_entered(String p_dir) {
+void FileDialog::_dir_submitted(String p_dir) {
dir_access->change_dir(p_dir);
file->set_text("");
invalidate();
@@ -162,7 +164,7 @@ void FileDialog::_dir_entered(String p_dir) {
_push_history();
}
-void FileDialog::_file_entered(const String &p_file) {
+void FileDialog::_file_submitted(const String &p_file) {
_action_pressed();
}
@@ -587,8 +589,8 @@ void FileDialog::update_file_list() {
files.pop_front();
}
- if (tree->get_root() && tree->get_root()->get_children() && tree->get_selected() == nullptr) {
- tree->get_root()->get_children()->select(0);
+ if (tree->get_root() && tree->get_root()->get_first_child() && tree->get_selected() == nullptr) {
+ tree->get_root()->get_first_child()->select(0);
}
}
@@ -1018,8 +1020,8 @@ FileDialog::FileDialog() {
tree->connect("cell_selected", callable_mp(this, &FileDialog::_tree_selected), varray(), CONNECT_DEFERRED);
tree->connect("item_activated", callable_mp(this, &FileDialog::_tree_item_activated), varray());
tree->connect("nothing_selected", callable_mp(this, &FileDialog::deselect_all));
- dir->connect("text_entered", callable_mp(this, &FileDialog::_dir_entered));
- file->connect("text_entered", callable_mp(this, &FileDialog::_file_entered));
+ dir->connect("text_submitted", callable_mp(this, &FileDialog::_dir_submitted));
+ file->connect("text_submitted", callable_mp(this, &FileDialog::_file_submitted));
filter->connect("item_selected", callable_mp(this, &FileDialog::_filter_selected));
confirm_save = memnew(ConfirmationDialog);
diff --git a/scene/gui/file_dialog.h b/scene/gui/file_dialog.h
index 4996f00cb3..7fbafc4bb4 100644
--- a/scene/gui/file_dialog.h
+++ b/scene/gui/file_dialog.h
@@ -32,7 +32,7 @@
#define FILE_DIALOG_H
#include "box_container.h"
-#include "core/os/dir_access.h"
+#include "core/io/dir_access.h"
#include "scene/gui/dialogs.h"
#include "scene/gui/line_edit.h"
#include "scene/gui/option_button.h"
@@ -118,8 +118,8 @@ private:
void _select_drive(int p_idx);
void _tree_item_activated();
- void _dir_entered(String p_dir);
- void _file_entered(const String &p_file);
+ void _dir_submitted(String p_dir);
+ void _file_submitted(const String &p_file);
void _action_pressed();
void _save_confirm_pressed();
void _cancel_pressed();
diff --git a/scene/gui/gradient_edit.cpp b/scene/gui/gradient_edit.cpp
index 36b383f16c..7278ca6e94 100644
--- a/scene/gui/gradient_edit.cpp
+++ b/scene/gui/gradient_edit.cpp
@@ -92,6 +92,8 @@ GradientEdit::~GradientEdit() {
}
void GradientEdit::_gui_input(const Ref<InputEvent> &p_event) {
+ ERR_FAIL_COND(p_event.is_null());
+
Ref<InputEventKey> k = p_event;
if (k.is_valid() && k->is_pressed() && k->get_keycode() == KEY_DELETE && grabbed != -1) {
@@ -105,7 +107,7 @@ void GradientEdit::_gui_input(const Ref<InputEvent> &p_event) {
Ref<InputEventMouseButton> mb = p_event;
//Show color picker on double click.
- if (mb.is_valid() && mb->get_button_index() == 1 && mb->is_doubleclick() && mb->is_pressed()) {
+ if (mb.is_valid() && mb->get_button_index() == 1 && mb->is_double_click() && mb->is_pressed()) {
grabbed = _get_point_from_pos(mb->get_position().x);
_show_color_picker();
accept_event();
@@ -125,7 +127,7 @@ void GradientEdit::_gui_input(const Ref<InputEvent> &p_event) {
}
//Hold alt key to duplicate selected color
- if (mb.is_valid() && mb->get_button_index() == 1 && mb->is_pressed() && mb->get_alt()) {
+ if (mb.is_valid() && mb->get_button_index() == 1 && mb->is_pressed() && mb->is_alt_pressed()) {
int x = mb->get_position().x;
grabbed = _get_point_from_pos(x);
@@ -234,9 +236,9 @@ void GradientEdit::_gui_input(const Ref<InputEvent> &p_event) {
// Snap to "round" coordinates if holding Ctrl.
// Be more precise if holding Shift as well
- if (mm->get_control()) {
- newofs = Math::snapped(newofs, mm->get_shift() ? 0.025 : 0.1);
- } else if (mm->get_shift()) {
+ if (mm->is_ctrl_pressed()) {
+ newofs = Math::snapped(newofs, mm->is_shift_pressed() ? 0.025 : 0.1);
+ } else if (mm->is_shift_pressed()) {
// Snap to nearest point if holding just Shift
const float snap_threshold = 0.03;
float smallest_ofs = snap_threshold;
diff --git a/scene/gui/graph_edit.cpp b/scene/gui/graph_edit.cpp
index 315bef38e8..39aa6749e7 100644
--- a/scene/gui/graph_edit.cpp
+++ b/scene/gui/graph_edit.cpp
@@ -40,13 +40,8 @@
#include "editor/editor_scale.h"
#endif
-#define ZOOM_SCALE 1.2
-
-#define MIN_ZOOM (((1 / ZOOM_SCALE) / ZOOM_SCALE) / ZOOM_SCALE)
-#define MAX_ZOOM (1 * ZOOM_SCALE * ZOOM_SCALE * ZOOM_SCALE)
-
-#define MINIMAP_OFFSET 12
-#define MINIMAP_PADDING 5
+constexpr int MINIMAP_OFFSET = 12;
+constexpr int MINIMAP_PADDING = 5;
bool GraphEditFilter::has_point(const Point2 &p_point) const {
return ge->_filter_input(p_point);
@@ -154,6 +149,8 @@ Vector2 GraphEditMinimap::_convert_to_graph_position(const Vector2 &p_position)
}
void GraphEditMinimap::_gui_input(const Ref<InputEvent> &p_ev) {
+ ERR_FAIL_COND(p_ev.is_null());
+
if (!ge->is_minimap_enabled()) {
return;
}
@@ -242,10 +239,6 @@ void GraphEdit::disconnect_node(const StringName &p_from, int p_from_port, const
}
}
-bool GraphEdit::clips_input() const {
- return true;
-}
-
void GraphEdit::get_connection_list(List<Connection> *r_connections) const {
*r_connections = connections;
}
@@ -822,7 +815,7 @@ void GraphEdit::_bake_segment2d(Vector<Vector2> &points, Vector<Color> &colors,
}
}
-void GraphEdit::_draw_cos_line(CanvasItem *p_where, const Vector2 &p_from, const Vector2 &p_to, const Color &p_color, const Color &p_to_color, float p_width, float p_bezier_ratio = 1.0) {
+void GraphEdit::_draw_cos_line(CanvasItem *p_where, const Vector2 &p_from, const Vector2 &p_to, const Color &p_color, const Color &p_to_color, float p_width, float p_bezier_ratio) {
//cubic bezier code
float diff = p_to.x - p_from.x;
float cp_offset;
@@ -1066,10 +1059,13 @@ void GraphEdit::set_selected(Node *p_child) {
}
void GraphEdit::_gui_input(const Ref<InputEvent> &p_ev) {
+ ERR_FAIL_COND(p_ev.is_null());
+
Ref<InputEventMouseMotion> mm = p_ev;
if (mm.is_valid() && (mm->get_button_mask() & MOUSE_BUTTON_MASK_MIDDLE || (mm->get_button_mask() & MOUSE_BUTTON_MASK_LEFT && Input::get_singleton()->is_key_pressed(KEY_SPACE)))) {
- h_scroll->set_value(h_scroll->get_value() - mm->get_relative().x);
- v_scroll->set_value(v_scroll->get_value() - mm->get_relative().y);
+ Vector2i relative = Input::get_singleton()->warp_mouse_motion(mm, get_global_rect());
+ h_scroll->set_value(h_scroll->get_value() - relative.x);
+ v_scroll->set_value(v_scroll->get_value() - relative.y);
}
if (mm.is_valid() && dragging) {
@@ -1087,7 +1083,7 @@ void GraphEdit::_gui_input(const Ref<InputEvent> &p_ev) {
// Snapping can be toggled temporarily by holding down Ctrl.
// This is done here as to not toggle the grid when holding down Ctrl.
- if (is_using_snap() ^ Input::get_singleton()->is_key_pressed(KEY_CONTROL)) {
+ if (is_using_snap() ^ Input::get_singleton()->is_key_pressed(KEY_CTRL)) {
const int snap = get_snap();
pos = pos.snapped(Vector2(snap, snap));
}
@@ -1123,7 +1119,7 @@ void GraphEdit::_gui_input(const Ref<InputEvent> &p_ev) {
}
gn->set_selected(box_selection_mode_additive);
} else {
- bool select = (previus_selected.find(gn) != nullptr);
+ bool select = (previous_selected.find(gn) != nullptr);
if (gn->is_selected() && !select) {
emit_signal("node_deselected", gn);
} else if (!gn->is_selected() && select) {
@@ -1148,7 +1144,7 @@ void GraphEdit::_gui_input(const Ref<InputEvent> &p_ev) {
continue;
}
- bool select = (previus_selected.find(gn) != nullptr);
+ bool select = (previous_selected.find(gn) != nullptr);
if (gn->is_selected() && !select) {
emit_signal("node_deselected", gn);
} else if (!gn->is_selected() && select) {
@@ -1170,7 +1166,7 @@ void GraphEdit::_gui_input(const Ref<InputEvent> &p_ev) {
}
if (b->get_button_index() == MOUSE_BUTTON_LEFT && !b->is_pressed() && dragging) {
- if (!just_selected && drag_accum == Vector2() && Input::get_singleton()->is_key_pressed(KEY_CONTROL)) {
+ if (!just_selected && drag_accum == Vector2() && Input::get_singleton()->is_key_pressed(KEY_CTRL)) {
//deselect current node
for (int i = get_child_count() - 1; i >= 0; i--) {
GraphNode *gn = Object::cast_to<GraphNode>(get_child(i));
@@ -1234,7 +1230,7 @@ void GraphEdit::_gui_input(const Ref<InputEvent> &p_ev) {
dragging = true;
drag_accum = Vector2();
just_selected = !gn->is_selected();
- if (!gn->is_selected() && !Input::get_singleton()->is_key_pressed(KEY_CONTROL)) {
+ if (!gn->is_selected() && !Input::get_singleton()->is_key_pressed(KEY_CTRL)) {
for (int i = 0; i < get_child_count(); i++) {
GraphNode *o_gn = Object::cast_to<GraphNode>(get_child(i));
if (o_gn) {
@@ -1271,31 +1267,31 @@ void GraphEdit::_gui_input(const Ref<InputEvent> &p_ev) {
box_selecting = true;
box_selecting_from = b->get_position();
- if (b->get_control()) {
+ if (b->is_ctrl_pressed()) {
box_selection_mode_additive = true;
- previus_selected.clear();
+ previous_selected.clear();
for (int i = get_child_count() - 1; i >= 0; i--) {
GraphNode *gn2 = Object::cast_to<GraphNode>(get_child(i));
if (!gn2 || !gn2->is_selected()) {
continue;
}
- previus_selected.push_back(gn2);
+ previous_selected.push_back(gn2);
}
- } else if (b->get_shift()) {
+ } else if (b->is_shift_pressed()) {
box_selection_mode_additive = false;
- previus_selected.clear();
+ previous_selected.clear();
for (int i = get_child_count() - 1; i >= 0; i--) {
GraphNode *gn2 = Object::cast_to<GraphNode>(get_child(i));
if (!gn2 || !gn2->is_selected()) {
continue;
}
- previus_selected.push_back(gn2);
+ previous_selected.push_back(gn2);
}
} else {
box_selection_mode_additive = true;
- previus_selected.clear();
+ previous_selected.clear();
for (int i = get_child_count() - 1; i >= 0; i--) {
GraphNode *gn2 = Object::cast_to<GraphNode>(get_child(i));
if (!gn2) {
@@ -1312,23 +1308,26 @@ void GraphEdit::_gui_input(const Ref<InputEvent> &p_ev) {
if (b->get_button_index() == MOUSE_BUTTON_LEFT && !b->is_pressed() && box_selecting) {
box_selecting = false;
- previus_selected.clear();
+ box_selecting_rect = Rect2();
+ previous_selected.clear();
top_layer->update();
minimap->update();
}
- if (b->get_button_index() == MOUSE_BUTTON_WHEEL_UP && Input::get_singleton()->is_key_pressed(KEY_CONTROL)) {
- set_zoom(zoom * ZOOM_SCALE);
- } else if (b->get_button_index() == MOUSE_BUTTON_WHEEL_DOWN && Input::get_singleton()->is_key_pressed(KEY_CONTROL)) {
- set_zoom(zoom / ZOOM_SCALE);
- } else if (b->get_button_index() == MOUSE_BUTTON_WHEEL_UP && !Input::get_singleton()->is_key_pressed(KEY_SHIFT)) {
- v_scroll->set_value(v_scroll->get_value() - v_scroll->get_page() * b->get_factor() / 8);
- } else if (b->get_button_index() == MOUSE_BUTTON_WHEEL_DOWN && !Input::get_singleton()->is_key_pressed(KEY_SHIFT)) {
- v_scroll->set_value(v_scroll->get_value() + v_scroll->get_page() * b->get_factor() / 8);
- } else if (b->get_button_index() == MOUSE_BUTTON_WHEEL_RIGHT || (b->get_button_index() == MOUSE_BUTTON_WHEEL_DOWN && Input::get_singleton()->is_key_pressed(KEY_SHIFT))) {
- h_scroll->set_value(h_scroll->get_value() + h_scroll->get_page() * b->get_factor() / 8);
- } else if (b->get_button_index() == MOUSE_BUTTON_WHEEL_LEFT || (b->get_button_index() == MOUSE_BUTTON_WHEEL_UP && Input::get_singleton()->is_key_pressed(KEY_SHIFT))) {
- h_scroll->set_value(h_scroll->get_value() - h_scroll->get_page() * b->get_factor() / 8);
+ int scroll_direction = (b->get_button_index() == MOUSE_BUTTON_WHEEL_DOWN) - (b->get_button_index() == MOUSE_BUTTON_WHEEL_UP);
+ if (scroll_direction != 0) {
+ if (b->is_ctrl_pressed()) {
+ if (b->is_shift_pressed()) {
+ // Horizontal scrolling.
+ h_scroll->set_value(h_scroll->get_value() + (h_scroll->get_page() * b->get_factor() / 8) * scroll_direction);
+ } else {
+ // Vertical scrolling.
+ v_scroll->set_value(v_scroll->get_value() + (v_scroll->get_page() * b->get_factor() / 8) * scroll_direction);
+ }
+ } else {
+ // Zooming.
+ set_zoom_custom(scroll_direction < 0 ? zoom * zoom_step : zoom / zoom_step, b->get_position());
+ }
}
}
@@ -1387,19 +1386,19 @@ void GraphEdit::set_zoom(float p_zoom) {
}
void GraphEdit::set_zoom_custom(float p_zoom, const Vector2 &p_center) {
- p_zoom = CLAMP(p_zoom, MIN_ZOOM, MAX_ZOOM);
+ p_zoom = CLAMP(p_zoom, zoom_min, zoom_max);
if (zoom == p_zoom) {
return;
}
- zoom_minus->set_disabled(zoom == MIN_ZOOM);
- zoom_plus->set_disabled(zoom == MAX_ZOOM);
-
Vector2 sbofs = (Vector2(h_scroll->get_value(), v_scroll->get_value()) + p_center) / zoom;
zoom = p_zoom;
top_layer->update();
+ zoom_minus->set_disabled(zoom == zoom_min);
+ zoom_plus->set_disabled(zoom == zoom_max);
+
_update_scroll();
minimap->update();
connections_layer->update();
@@ -1410,6 +1409,7 @@ void GraphEdit::set_zoom_custom(float p_zoom, const Vector2 &p_center) {
v_scroll->set_value(ofs.y);
}
+ _update_zoom_label();
update();
}
@@ -1417,6 +1417,61 @@ float GraphEdit::get_zoom() const {
return zoom;
}
+void GraphEdit::set_zoom_step(float p_zoom_step) {
+ p_zoom_step = abs(p_zoom_step);
+ if (zoom_step == p_zoom_step) {
+ return;
+ }
+
+ zoom_step = p_zoom_step;
+}
+
+float GraphEdit::get_zoom_step() const {
+ return zoom_step;
+}
+
+void GraphEdit::set_zoom_min(float p_zoom_min) {
+ ERR_FAIL_COND_MSG(p_zoom_min > zoom_max, "Cannot set min zoom level greater than max zoom level.");
+
+ if (zoom_min == p_zoom_min) {
+ return;
+ }
+
+ zoom_min = p_zoom_min;
+ set_zoom(zoom);
+}
+
+float GraphEdit::get_zoom_min() const {
+ return zoom_min;
+}
+
+void GraphEdit::set_zoom_max(float p_zoom_max) {
+ ERR_FAIL_COND_MSG(p_zoom_max < zoom_min, "Cannot set max zoom level lesser than min zoom level.");
+
+ if (zoom_max == p_zoom_max) {
+ return;
+ }
+
+ zoom_max = p_zoom_max;
+ set_zoom(zoom);
+}
+
+float GraphEdit::get_zoom_max() const {
+ return zoom_max;
+}
+
+void GraphEdit::set_show_zoom_label(bool p_enable) {
+ if (zoom_label->is_visible() == p_enable) {
+ return;
+ }
+
+ zoom_label->set_visible(p_enable);
+}
+
+bool GraphEdit::is_showing_zoom_label() const {
+ return zoom_label->is_visible();
+}
+
void GraphEdit::set_right_disconnects(bool p_enable) {
right_disconnects = p_enable;
}
@@ -1457,7 +1512,7 @@ Array GraphEdit::_get_connection_list() const {
}
void GraphEdit::_zoom_minus() {
- set_zoom(zoom / ZOOM_SCALE);
+ set_zoom(zoom / zoom_step);
}
void GraphEdit::_zoom_reset() {
@@ -1465,7 +1520,13 @@ void GraphEdit::_zoom_reset() {
}
void GraphEdit::_zoom_plus() {
- set_zoom(zoom * ZOOM_SCALE);
+ set_zoom(zoom * zoom_step);
+}
+
+void GraphEdit::_update_zoom_label() {
+ int zoom_percent = static_cast<int>(Math::round(zoom * 100));
+ String zoom_text = itos(zoom_percent) + "%";
+ zoom_label->set_text(zoom_text);
}
void GraphEdit::add_valid_connection_type(int p_type, int p_with_type) {
@@ -1606,6 +1667,18 @@ void GraphEdit::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_zoom", "zoom"), &GraphEdit::set_zoom);
ClassDB::bind_method(D_METHOD("get_zoom"), &GraphEdit::get_zoom);
+ ClassDB::bind_method(D_METHOD("set_zoom_min", "zoom_min"), &GraphEdit::set_zoom_min);
+ ClassDB::bind_method(D_METHOD("get_zoom_min"), &GraphEdit::get_zoom_min);
+
+ ClassDB::bind_method(D_METHOD("set_zoom_max", "zoom_max"), &GraphEdit::set_zoom_max);
+ ClassDB::bind_method(D_METHOD("get_zoom_max"), &GraphEdit::get_zoom_max);
+
+ ClassDB::bind_method(D_METHOD("set_zoom_step", "zoom_step"), &GraphEdit::set_zoom_step);
+ ClassDB::bind_method(D_METHOD("get_zoom_step"), &GraphEdit::get_zoom_step);
+
+ ClassDB::bind_method(D_METHOD("set_show_zoom_label", "enable"), &GraphEdit::set_show_zoom_label);
+ ClassDB::bind_method(D_METHOD("is_showing_zoom_label"), &GraphEdit::is_showing_zoom_label);
+
ClassDB::bind_method(D_METHOD("set_snap", "pixels"), &GraphEdit::set_snap);
ClassDB::bind_method(D_METHOD("get_snap"), &GraphEdit::get_snap);
@@ -1640,9 +1713,18 @@ void GraphEdit::_bind_methods() {
ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "scroll_offset"), "set_scroll_ofs", "get_scroll_ofs");
ADD_PROPERTY(PropertyInfo(Variant::INT, "snap_distance"), "set_snap", "get_snap");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "use_snap"), "set_use_snap", "is_using_snap");
- ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "zoom"), "set_zoom", "get_zoom");
+
+ ADD_GROUP("Connection Lines", "connection_lines");
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "connection_lines_thickness"), "set_connection_lines_thickness", "get_connection_lines_thickness");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "connection_lines_antialiased"), "set_connection_lines_antialiased", "is_connection_lines_antialiased");
+
+ ADD_GROUP("Zoom", "");
+ ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "zoom"), "set_zoom", "get_zoom");
+ ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "zoom_min"), "set_zoom_min", "get_zoom_min");
+ ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "zoom_max"), "set_zoom_max", "get_zoom_max");
+ ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "zoom_step"), "set_zoom_step", "get_zoom_step");
+ ADD_PROPERTY(PropertyInfo(Variant::BOOL, "show_zoom_label"), "set_show_zoom_label", "is_showing_zoom_label");
+
ADD_GROUP("Minimap", "minimap");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "minimap_enabled"), "set_minimap_enabled", "is_minimap_enabled");
ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "minimap_size"), "set_minimap_size", "get_minimap_size");
@@ -1667,6 +1749,13 @@ void GraphEdit::_bind_methods() {
GraphEdit::GraphEdit() {
set_focus_mode(FOCUS_ALL);
+ // Allow dezooming 8 times from the default zoom level.
+ // At low zoom levels, text is unreadable due to its small size and poor filtering,
+ // but this is still useful for previewing and navigation.
+ zoom_min = (1 / Math::pow(zoom_step, 8));
+ // Allow zooming 4 times from the default zoom level.
+ zoom_max = (1 * Math::pow(zoom_step, 4));
+
top_layer = memnew(GraphEditFilter(this));
add_child(top_layer);
top_layer->set_mouse_filter(MOUSE_FILTER_PASS);
@@ -1703,6 +1792,18 @@ GraphEdit::GraphEdit() {
top_layer->add_child(zoom_hb);
zoom_hb->set_position(Vector2(10, 10));
+ zoom_label = memnew(Label);
+ zoom_hb->add_child(zoom_label);
+ zoom_label->set_visible(false);
+ zoom_label->set_v_size_flags(Control::SIZE_SHRINK_CENTER);
+ zoom_label->set_align(Label::ALIGN_CENTER);
+#ifdef TOOLS_ENABLED
+ zoom_label->set_custom_minimum_size(Size2(48, 0) * EDSCALE);
+#else
+ zoom_label->set_custom_minimum_size(Size2(48, 0));
+#endif
+ _update_zoom_label();
+
zoom_minus = memnew(Button);
zoom_minus->set_flat(true);
zoom_hb->add_child(zoom_minus);
diff --git a/scene/gui/graph_edit.h b/scene/gui/graph_edit.h
index 8fdf975319..5251de1722 100644
--- a/scene/gui/graph_edit.h
+++ b/scene/gui/graph_edit.h
@@ -34,6 +34,7 @@
#include "scene/gui/box_container.h"
#include "scene/gui/button.h"
#include "scene/gui/graph_node.h"
+#include "scene/gui/label.h"
#include "scene/gui/scroll_bar.h"
#include "scene/gui/slider.h"
#include "scene/gui/spin_box.h"
@@ -105,6 +106,7 @@ public:
};
private:
+ Label *zoom_label;
Button *zoom_minus;
Button *zoom_reset;
Button *zoom_plus;
@@ -114,10 +116,6 @@ private:
Button *minimap_button;
- void _zoom_minus();
- void _zoom_reset();
- void _zoom_plus();
-
HScrollBar *h_scroll;
VScrollBar *v_scroll;
@@ -144,13 +142,21 @@ private:
Vector2 drag_accum;
float zoom = 1.0;
+ float zoom_step = 1.2;
+ float zoom_min;
+ float zoom_max;
+
+ void _zoom_minus();
+ void _zoom_reset();
+ void _zoom_plus();
+ void _update_zoom_label();
bool box_selecting = false;
bool box_selection_mode_additive = false;
Point2 box_selecting_from;
Point2 box_selecting_to;
Rect2 box_selecting_rect;
- List<GraphNode *> previus_selected;
+ List<GraphNode *> previous_selected;
bool setting_scroll_ofs = false;
bool right_disconnects = false;
@@ -163,7 +169,7 @@ private:
void _bake_segment2d(Vector<Vector2> &points, Vector<Color> &colors, float p_begin, float p_end, const Vector2 &p_a, const Vector2 &p_out, const Vector2 &p_b, const Vector2 &p_in, int p_depth, int p_min_depth, int p_max_depth, float p_tol, const Color &p_color, const Color &p_to_color, int &lines) const;
- void _draw_cos_line(CanvasItem *p_where, const Vector2 &p_from, const Vector2 &p_to, const Color &p_color, const Color &p_to_color, float p_width, float p_bezier_ratio);
+ void _draw_cos_line(CanvasItem *p_where, const Vector2 &p_from, const Vector2 &p_to, const Color &p_color, const Color &p_to_color, float p_width, float p_bezier_ratio = 1.0);
void _graph_node_raised(Node *p_gn);
void _graph_node_moved(Node *p_gn);
@@ -229,7 +235,6 @@ protected:
virtual void add_child_notify(Node *p_child) override;
virtual void remove_child_notify(Node *p_child) override;
void _notification(int p_what);
- virtual bool clips_input() const override;
public:
Error connect_node(const StringName &p_from, int p_from_port, const StringName &p_to, int p_to_port);
@@ -247,6 +252,18 @@ public:
void set_zoom_custom(float p_zoom, const Vector2 &p_center);
float get_zoom() const;
+ void set_zoom_min(float p_zoom_min);
+ float get_zoom_min() const;
+
+ void set_zoom_max(float p_zoom_max);
+ float get_zoom_max() const;
+
+ void set_zoom_step(float p_zoom_step);
+ float get_zoom_step() const;
+
+ void set_show_zoom_label(bool p_enable);
+ bool is_showing_zoom_label() const;
+
void set_minimap_size(Vector2 p_size);
Vector2 get_minimap_size() const;
void set_minimap_opacity(float p_opacity);
diff --git a/scene/gui/graph_node.cpp b/scene/gui/graph_node.cpp
index 8eba473d57..836bffdf46 100644
--- a/scene/gui/graph_node.cpp
+++ b/scene/gui/graph_node.cpp
@@ -228,7 +228,7 @@ void GraphNode::_resort() {
}
stretch_avail += stretch_diff - sb->get_margin(SIDE_BOTTOM) - sb->get_margin(SIDE_TOP); //available stretch space.
- /** Second, pass sucessively to discard elements that can't be stretched, this will run while stretchable
+ /** Second, pass successively to discard elements that can't be stretched, this will run while stretchable
elements exist */
while (stretch_ratio_total > 0) { // first of all, don't even be here if no stretchable objects exist
@@ -459,7 +459,7 @@ void GraphNode::_shape() {
}
void GraphNode::set_slot(int p_idx, bool p_enable_left, int p_type_left, const Color &p_color_left, bool p_enable_right, int p_type_right, const Color &p_color_right, const Ref<Texture2D> &p_custom_left, const Ref<Texture2D> &p_custom_right) {
- ERR_FAIL_COND(p_idx < 0);
+ ERR_FAIL_COND_MSG(p_idx < 0, vformat("Cannot set slot with p_idx (%d) lesser than zero.", p_idx));
if (!p_enable_left && p_type_left == 0 && p_color_left == Color(1, 1, 1, 1) &&
!p_enable_right && p_type_right == 0 && p_color_right == Color(1, 1, 1, 1) &&
@@ -503,6 +503,26 @@ bool GraphNode::is_slot_enabled_left(int p_idx) const {
return slot_info[p_idx].enable_left;
}
+void GraphNode::set_slot_enabled_left(int p_idx, bool p_enable_left) {
+ ERR_FAIL_COND_MSG(p_idx < 0, vformat("Cannot set enable_left for the slot with p_idx (%d) lesser than zero.", p_idx));
+
+ slot_info[p_idx].enable_left = p_enable_left;
+ update();
+ connpos_dirty = true;
+
+ emit_signal("slot_updated", p_idx);
+}
+
+void GraphNode::set_slot_type_left(int p_idx, int p_type_left) {
+ ERR_FAIL_COND_MSG(!slot_info.has(p_idx), vformat("Cannot set type_left for the slot '%d' because it hasn't been enabled.", p_idx));
+
+ slot_info[p_idx].type_left = p_type_left;
+ update();
+ connpos_dirty = true;
+
+ emit_signal("slot_updated", p_idx);
+}
+
int GraphNode::get_slot_type_left(int p_idx) const {
if (!slot_info.has(p_idx)) {
return 0;
@@ -510,6 +530,16 @@ int GraphNode::get_slot_type_left(int p_idx) const {
return slot_info[p_idx].type_left;
}
+void GraphNode::set_slot_color_left(int p_idx, const Color &p_color_left) {
+ ERR_FAIL_COND_MSG(!slot_info.has(p_idx), vformat("Cannot set color_left for the slot '%d' because it hasn't been enabled.", p_idx));
+
+ slot_info[p_idx].color_left = p_color_left;
+ update();
+ connpos_dirty = true;
+
+ emit_signal("slot_updated", p_idx);
+}
+
Color GraphNode::get_slot_color_left(int p_idx) const {
if (!slot_info.has(p_idx)) {
return Color(1, 1, 1, 1);
@@ -524,6 +554,26 @@ bool GraphNode::is_slot_enabled_right(int p_idx) const {
return slot_info[p_idx].enable_right;
}
+void GraphNode::set_slot_enabled_right(int p_idx, bool p_enable_right) {
+ ERR_FAIL_COND_MSG(p_idx < 0, vformat("Cannot set enable_right for the slot with p_idx (%d) lesser than zero.", p_idx));
+
+ slot_info[p_idx].enable_right = p_enable_right;
+ update();
+ connpos_dirty = true;
+
+ emit_signal("slot_updated", p_idx);
+}
+
+void GraphNode::set_slot_type_right(int p_idx, int p_type_right) {
+ ERR_FAIL_COND_MSG(!slot_info.has(p_idx), vformat("Cannot set type_right for the slot '%d' because it hasn't been enabled.", p_idx));
+
+ slot_info[p_idx].type_right = p_type_right;
+ update();
+ connpos_dirty = true;
+
+ emit_signal("slot_updated", p_idx);
+}
+
int GraphNode::get_slot_type_right(int p_idx) const {
if (!slot_info.has(p_idx)) {
return 0;
@@ -531,6 +581,16 @@ int GraphNode::get_slot_type_right(int p_idx) const {
return slot_info[p_idx].type_right;
}
+void GraphNode::set_slot_color_right(int p_idx, const Color &p_color_right) {
+ ERR_FAIL_COND_MSG(!slot_info.has(p_idx), vformat("Cannot set color_right for the slot '%d' because it hasn't been enabled.", p_idx));
+
+ slot_info[p_idx].color_right = p_color_right;
+ update();
+ connpos_dirty = true;
+
+ emit_signal("slot_updated", p_idx);
+}
+
Color GraphNode::get_slot_color_right(int p_idx) const {
if (!slot_info.has(p_idx)) {
return Color(1, 1, 1, 1);
@@ -697,7 +757,7 @@ void GraphNode::_connpos_update() {
continue;
}
- Size2i size = c->get_combined_minimum_size();
+ Size2i size = c->get_rect().size;
int y = sb->get_margin(SIDE_TOP) + vofs;
int h = size.y;
@@ -804,6 +864,8 @@ Color GraphNode::get_connection_output_color(int p_idx) {
}
void GraphNode::_gui_input(const Ref<InputEvent> &p_ev) {
+ ERR_FAIL_COND(p_ev.is_null());
+
Ref<InputEventMouseButton> mb = p_ev;
if (mb.is_valid()) {
ERR_FAIL_COND_MSG(get_parent_control() == nullptr, "GraphNode must be the child of a GraphEdit node.");
@@ -889,11 +951,23 @@ void GraphNode::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_slot", "idx", "enable_left", "type_left", "color_left", "enable_right", "type_right", "color_right", "custom_left", "custom_right"), &GraphNode::set_slot, DEFVAL(Ref<Texture2D>()), DEFVAL(Ref<Texture2D>()));
ClassDB::bind_method(D_METHOD("clear_slot", "idx"), &GraphNode::clear_slot);
ClassDB::bind_method(D_METHOD("clear_all_slots"), &GraphNode::clear_all_slots);
+
ClassDB::bind_method(D_METHOD("is_slot_enabled_left", "idx"), &GraphNode::is_slot_enabled_left);
+ ClassDB::bind_method(D_METHOD("set_slot_enabled_left", "idx", "enable_left"), &GraphNode::set_slot_enabled_left);
+
+ ClassDB::bind_method(D_METHOD("set_slot_type_left", "idx", "type_left"), &GraphNode::set_slot_type_left);
ClassDB::bind_method(D_METHOD("get_slot_type_left", "idx"), &GraphNode::get_slot_type_left);
+
+ ClassDB::bind_method(D_METHOD("set_slot_color_left", "idx", "color_left"), &GraphNode::set_slot_color_left);
ClassDB::bind_method(D_METHOD("get_slot_color_left", "idx"), &GraphNode::get_slot_color_left);
+
ClassDB::bind_method(D_METHOD("is_slot_enabled_right", "idx"), &GraphNode::is_slot_enabled_right);
+ ClassDB::bind_method(D_METHOD("set_slot_enabled_right", "idx", "enable_right"), &GraphNode::set_slot_enabled_right);
+
+ ClassDB::bind_method(D_METHOD("set_slot_type_right", "idx", "type_right"), &GraphNode::set_slot_type_right);
ClassDB::bind_method(D_METHOD("get_slot_type_right", "idx"), &GraphNode::get_slot_type_right);
+
+ ClassDB::bind_method(D_METHOD("set_slot_color_right", "idx", "color_right"), &GraphNode::set_slot_color_right);
ClassDB::bind_method(D_METHOD("get_slot_color_right", "idx"), &GraphNode::get_slot_color_right);
ClassDB::bind_method(D_METHOD("set_position_offset", "offset"), &GraphNode::set_position_offset);
@@ -925,7 +999,7 @@ void GraphNode::_bind_methods() {
ClassDB::bind_method(D_METHOD("get_overlay"), &GraphNode::get_overlay);
ADD_PROPERTY(PropertyInfo(Variant::STRING, "title"), "set_title", "get_title");
- ADD_PROPERTY(PropertyInfo(Variant::INT, "text_direction", PROPERTY_HINT_ENUM, "Auto,LTR,RTL,Inherited"), "set_text_direction", "get_text_direction");
+ ADD_PROPERTY(PropertyInfo(Variant::INT, "text_direction", PROPERTY_HINT_ENUM, "Auto,Left-to-Right,Right-to-Left,Inherited"), "set_text_direction", "get_text_direction");
ADD_PROPERTY(PropertyInfo(Variant::STRING, "language"), "set_language", "get_language");
ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "position_offset"), "set_position_offset", "get_position_offset");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "show_close"), "set_show_close_button", "is_close_button_visible");
@@ -947,6 +1021,6 @@ void GraphNode::_bind_methods() {
}
GraphNode::GraphNode() {
- title_buf.instance();
+ title_buf.instantiate();
set_mouse_filter(MOUSE_FILTER_STOP);
}
diff --git a/scene/gui/graph_node.h b/scene/gui/graph_node.h
index 1bc54dddb7..c70f616b47 100644
--- a/scene/gui/graph_node.h
+++ b/scene/gui/graph_node.h
@@ -113,11 +113,23 @@ public:
void set_slot(int p_idx, bool p_enable_left, int p_type_left, const Color &p_color_left, bool p_enable_right, int p_type_right, const Color &p_color_right, const Ref<Texture2D> &p_custom_left = Ref<Texture2D>(), const Ref<Texture2D> &p_custom_right = Ref<Texture2D>());
void clear_slot(int p_idx);
void clear_all_slots();
+
bool is_slot_enabled_left(int p_idx) const;
+ void set_slot_enabled_left(int p_idx, bool p_enable_left);
+
+ void set_slot_type_left(int p_idx, int p_type_left);
int get_slot_type_left(int p_idx) const;
+
+ void set_slot_color_left(int p_idx, const Color &p_color_left);
Color get_slot_color_left(int p_idx) const;
+
bool is_slot_enabled_right(int p_idx) const;
+ void set_slot_enabled_right(int p_idx, bool p_enable_right);
+
+ void set_slot_type_right(int p_idx, int p_type_right);
int get_slot_type_right(int p_idx) const;
+
+ void set_slot_color_right(int p_idx, const Color &p_color_right);
Color get_slot_color_right(int p_idx) const;
void set_title(const String &p_title);
diff --git a/scene/gui/grid_container.cpp b/scene/gui/grid_container.cpp
index 541925a802..a54f5eef06 100644
--- a/scene/gui/grid_container.cpp
+++ b/scene/gui/grid_container.cpp
@@ -33,7 +33,7 @@
void GridContainer::_notification(int p_what) {
switch (p_what) {
case NOTIFICATION_SORT_CHILDREN: {
- Map<int, int> col_minw; // Max of min_width of all controls in each col (indexed by col).
+ Map<int, int> col_minw; // Max of min_width of all controls in each col (indexed by col).
Map<int, int> row_minh; // Max of min_height of all controls in each row (indexed by row).
Set<int> col_expanded; // Columns which have the SIZE_EXPAND flag set.
Set<int> row_expanded; // Rows which have the SIZE_EXPAND flag set.
diff --git a/scene/gui/item_list.cpp b/scene/gui/item_list.cpp
index 482560d29d..b0d54bf8c9 100644
--- a/scene/gui/item_list.cpp
+++ b/scene/gui/item_list.cpp
@@ -57,7 +57,7 @@ int ItemList::add_item(const String &p_item, const Ref<Texture2D> &p_texture, bo
item.icon_region = Rect2i();
item.icon_modulate = Color(1, 1, 1, 1);
item.text = p_item;
- item.text_buf.instance();
+ item.text_buf.instantiate();
item.selectable = p_selectable;
item.selected = false;
item.disabled = false;
@@ -80,7 +80,7 @@ int ItemList::add_icon_item(const Ref<Texture2D> &p_item, bool p_selectable) {
item.icon_region = Rect2i();
item.icon_modulate = Color(1, 1, 1, 1);
//item.text=p_item;
- item.text_buf.instance();
+ item.text_buf.instantiate();
item.selectable = p_selectable;
item.selected = false;
item.disabled = false;
@@ -406,6 +406,9 @@ void ItemList::remove_item(int p_idx) {
ERR_FAIL_INDEX(p_idx, items.size());
items.remove(p_idx);
+ if (current == p_idx) {
+ current = -1;
+ }
update();
shape_changed = true;
defer_select_single = -1;
@@ -530,6 +533,8 @@ Size2 ItemList::Item::get_icon_size() const {
}
void ItemList::_gui_input(const Ref<InputEvent> &p_event) {
+ ERR_FAIL_COND(p_event.is_null());
+
double prev_scroll = scroll_bar->get_value();
Ref<InputEventMouseMotion> mm = p_event;
@@ -576,11 +581,11 @@ void ItemList::_gui_input(const Ref<InputEvent> &p_event) {
if (closest != -1) {
int i = closest;
- if (select_mode == SELECT_MULTI && items[i].selected && mb->get_command()) {
+ if (select_mode == SELECT_MULTI && items[i].selected && mb->is_command_pressed()) {
deselect(i);
emit_signal("multi_selected", i, false);
- } else if (select_mode == SELECT_MULTI && mb->get_shift() && current >= 0 && current < items.size() && current != i) {
+ } else if (select_mode == SELECT_MULTI && mb->is_shift_pressed() && current >= 0 && current < items.size() && current != i) {
int from = current;
int to = i;
if (i < current) {
@@ -598,7 +603,7 @@ void ItemList::_gui_input(const Ref<InputEvent> &p_event) {
emit_signal("item_rmb_selected", i, get_local_mouse_position());
}
} else {
- if (!mb->is_doubleclick() && !mb->get_command() && select_mode == SELECT_MULTI && items[i].selectable && !items[i].disabled && items[i].selected && mb->get_button_index() == MOUSE_BUTTON_LEFT) {
+ if (!mb->is_double_click() && !mb->is_command_pressed() && select_mode == SELECT_MULTI && items[i].selectable && !items[i].disabled && items[i].selected && mb->get_button_index() == MOUSE_BUTTON_LEFT) {
defer_select_single = i;
return;
}
@@ -608,7 +613,7 @@ void ItemList::_gui_input(const Ref<InputEvent> &p_event) {
} else {
bool selected = items[i].selected;
- select(i, select_mode == SELECT_SINGLE || !mb->get_command());
+ select(i, select_mode == SELECT_SINGLE || !mb->is_command_pressed());
if (!selected || allow_reselect) {
if (select_mode == SELECT_SINGLE) {
@@ -620,7 +625,7 @@ void ItemList::_gui_input(const Ref<InputEvent> &p_event) {
if (mb->get_button_index() == MOUSE_BUTTON_RIGHT) {
emit_signal("item_rmb_selected", i, get_local_mouse_position());
- } else if (/*select_mode==SELECT_SINGLE &&*/ mb->is_doubleclick()) {
+ } else if (/*select_mode==SELECT_SINGLE &&*/ mb->is_double_click()) {
emit_signal("item_activated", i);
}
}
diff --git a/scene/gui/label.cpp b/scene/gui/label.cpp
index be73fd8f51..78b9ad2569 100644
--- a/scene/gui/label.cpp
+++ b/scene/gui/label.cpp
@@ -36,20 +36,20 @@
#include "servers/text_server.h"
-void Label::set_autowrap(bool p_autowrap) {
- if (autowrap != p_autowrap) {
- autowrap = p_autowrap;
+void Label::set_autowrap_mode(Label::AutowrapMode p_mode) {
+ if (autowrap_mode != p_mode) {
+ autowrap_mode = p_mode;
lines_dirty = true;
}
update();
- if (clip) {
+ if (clip || overrun_behavior != OVERRUN_NO_TRIMMING) {
minimum_size_changed();
}
}
-bool Label::has_autowrap() const {
- return autowrap;
+Label::AutowrapMode Label::get_autowrap_mode() const {
+ return autowrap_mode;
}
void Label::set_uppercase(bool p_uppercase) {
@@ -94,24 +94,76 @@ void Label::_shape() {
dirty = false;
lines_dirty = true;
}
+
+ uint8_t overrun_flags = TextServer::OVERRUN_NO_TRIMMING;
if (lines_dirty) {
for (int i = 0; i < lines_rid.size(); i++) {
TS->free(lines_rid[i]);
}
lines_rid.clear();
- Vector<Vector2i> lines = TS->shaped_text_get_line_breaks(text_rid, width, 0, (autowrap) ? (TextServer::BREAK_MANDATORY | TextServer::BREAK_WORD_BOUND) : TextServer::BREAK_MANDATORY);
+ uint8_t autowrap_flags = TextServer::BREAK_MANDATORY;
+ switch (autowrap_mode) {
+ case AUTOWRAP_WORD_SMART:
+ autowrap_flags = TextServer::BREAK_WORD_BOUND_ADAPTIVE | TextServer::BREAK_MANDATORY;
+ break;
+ case AUTOWRAP_WORD:
+ autowrap_flags = TextServer::BREAK_WORD_BOUND | TextServer::BREAK_MANDATORY;
+ break;
+ case AUTOWRAP_ARBITRARY:
+ autowrap_flags = TextServer::BREAK_GRAPHEME_BOUND | TextServer::BREAK_MANDATORY;
+ break;
+ case AUTOWRAP_OFF:
+ break;
+ }
+ Vector<Vector2i> lines = TS->shaped_text_get_line_breaks(text_rid, width, 0, autowrap_flags);
+
for (int i = 0; i < lines.size(); i++) {
RID line = TS->shaped_text_substr(text_rid, lines[i].x, lines[i].y - lines[i].x);
+
+ switch (overrun_behavior) {
+ case OVERRUN_TRIM_WORD_ELLIPSIS:
+ overrun_flags |= TextServer::OVERRUN_TRIM;
+ overrun_flags |= TextServer::OVERRUN_TRIM_WORD_ONLY;
+ overrun_flags |= TextServer::OVERRUN_ADD_ELLIPSIS;
+ break;
+ case OVERRUN_TRIM_ELLIPSIS:
+ overrun_flags |= TextServer::OVERRUN_TRIM;
+ overrun_flags |= TextServer::OVERRUN_ADD_ELLIPSIS;
+ break;
+ case OVERRUN_TRIM_WORD:
+ overrun_flags |= TextServer::OVERRUN_TRIM;
+ overrun_flags |= TextServer::OVERRUN_TRIM_WORD_ONLY;
+ break;
+ case OVERRUN_TRIM_CHAR:
+ overrun_flags |= TextServer::OVERRUN_TRIM;
+ break;
+ case OVERRUN_NO_TRIMMING:
+ break;
+ }
+
+ if (autowrap_mode == AUTOWRAP_OFF && align != ALIGN_FILL && overrun_behavior != OVERRUN_NO_TRIMMING) {
+ TS->shaped_text_overrun_trim_to_width(line, width, overrun_flags);
+ }
+
lines_rid.push_back(line);
}
+
+ if (autowrap_mode != AUTOWRAP_OFF && overrun_behavior != OVERRUN_NO_TRIMMING) {
+ int visible_lines = get_visible_line_count();
+
+ if (visible_lines < lines_rid.size() && visible_lines > 0) {
+ overrun_flags |= TextServer::OVERRUN_ENFORCE_ELLIPSIS;
+ TS->shaped_text_overrun_trim_to_width(lines_rid[visible_lines - 1], width, overrun_flags);
+ }
+ }
}
if (xl_text.length() == 0) {
minsize = Size2(1, get_line_height());
return;
}
- if (!autowrap) {
+ if (autowrap_mode == AUTOWRAP_OFF) {
minsize.width = 0.0f;
for (int i = 0; i < lines_rid.size(); i++) {
if (minsize.width < TS->shaped_text_get_size(lines_rid[i]).x) {
@@ -120,10 +172,21 @@ void Label::_shape() {
}
}
- if (lines_dirty) { // Fill after min_size calculation.
+ if (lines_dirty) {
+ // Fill after min_size calculation.
if (align == ALIGN_FILL) {
for (int i = 0; i < lines_rid.size(); i++) {
- TS->shaped_text_fit_to_width(lines_rid.write[i], width);
+ if (overrun_behavior != OVERRUN_NO_TRIMMING && autowrap_mode == AUTOWRAP_OFF) {
+ float line_unaltered_width = TS->shaped_text_get_width(lines_rid[i]);
+ TS->shaped_text_fit_to_width(lines_rid[i], width);
+ float new_line_width = TS->shaped_text_get_width(lines_rid[i]);
+ // Begin trimming when there is no space between words available anymore.
+ if (new_line_width < line_unaltered_width) {
+ TS->shaped_text_overrun_trim_to_width(lines_rid[i], width, overrun_flags);
+ }
+ } else {
+ TS->shaped_text_fit_to_width(lines_rid[i], width);
+ }
}
}
lines_dirty = false;
@@ -131,7 +194,7 @@ void Label::_shape() {
_update_visible();
- if (!autowrap || !clip) {
+ if (autowrap_mode == AUTOWRAP_OFF || !clip || overrun_behavior == OVERRUN_NO_TRIMMING) {
minimum_size_changed();
}
}
@@ -217,6 +280,7 @@ void Label::_notification(int p_what) {
for (int64_t i = lines_skipped; i < last_line; i++) {
total_h += TS->shaped_text_get_size(lines_rid[i]).y + font->get_spacing(Font::SPACING_TOP) + font->get_spacing(Font::SPACING_BOTTOM) + line_spacing;
}
+ total_h += style->get_margin(SIDE_TOP) + style->get_margin(SIDE_BOTTOM);
int vbegin = 0, vsep = 0;
if (lines_visible > 0) {
@@ -369,13 +433,12 @@ Size2 Label::get_minimum_size() const {
min_size.height = MAX(min_size.height, font->get_height(get_theme_font_size("font_size")) + font->get_spacing(Font::SPACING_TOP) + font->get_spacing(Font::SPACING_BOTTOM));
Size2 min_style = get_theme_stylebox("normal")->get_minimum_size();
- if (autowrap) {
- return Size2(1, clip ? 1 : min_size.height) + min_style;
+ if (autowrap_mode != AUTOWRAP_OFF) {
+ return Size2(1, (clip || overrun_behavior != OVERRUN_NO_TRIMMING) ? 1 : min_size.height) + min_style;
} else {
- if (clip) {
+ if (clip || overrun_behavior != OVERRUN_NO_TRIMMING) {
min_size.width = 1;
}
-
return min_size + min_style;
}
}
@@ -452,6 +515,7 @@ void Label::set_text(const String &p_string) {
visible_chars = get_total_character_count() * percent_visible;
}
update();
+ minimum_size_changed();
}
void Label::set_text_direction(Control::TextDirection p_text_direction) {
@@ -534,6 +598,21 @@ bool Label::is_clipping_text() const {
return clip;
}
+void Label::set_text_overrun_behavior(Label::OverrunBehavior p_behavior) {
+ if (overrun_behavior != p_behavior) {
+ overrun_behavior = p_behavior;
+ lines_dirty = true;
+ }
+ update();
+ if (clip || overrun_behavior != OVERRUN_NO_TRIMMING) {
+ minimum_size_changed();
+ }
+}
+
+Label::OverrunBehavior Label::get_text_overrun_behavior() const {
+ return overrun_behavior;
+}
+
String Label::get_text() const {
return text;
}
@@ -661,10 +740,12 @@ void Label::_bind_methods() {
ClassDB::bind_method(D_METHOD("clear_opentype_features"), &Label::clear_opentype_features);
ClassDB::bind_method(D_METHOD("set_language", "language"), &Label::set_language);
ClassDB::bind_method(D_METHOD("get_language"), &Label::get_language);
- ClassDB::bind_method(D_METHOD("set_autowrap", "enable"), &Label::set_autowrap);
- ClassDB::bind_method(D_METHOD("has_autowrap"), &Label::has_autowrap);
+ ClassDB::bind_method(D_METHOD("set_autowrap_mode", "autowrap_mode"), &Label::set_autowrap_mode);
+ ClassDB::bind_method(D_METHOD("get_autowrap_mode"), &Label::get_autowrap_mode);
ClassDB::bind_method(D_METHOD("set_clip_text", "enable"), &Label::set_clip_text);
ClassDB::bind_method(D_METHOD("is_clipping_text"), &Label::is_clipping_text);
+ ClassDB::bind_method(D_METHOD("set_text_overrun_behavior", "overrun_behavior"), &Label::set_text_overrun_behavior);
+ ClassDB::bind_method(D_METHOD("get_text_overrun_behavior"), &Label::get_text_overrun_behavior);
ClassDB::bind_method(D_METHOD("set_uppercase", "enable"), &Label::set_uppercase);
ClassDB::bind_method(D_METHOD("is_uppercase"), &Label::is_uppercase);
ClassDB::bind_method(D_METHOD("get_line_height", "line"), &Label::get_line_height, DEFVAL(-1));
@@ -694,13 +775,25 @@ void Label::_bind_methods() {
BIND_ENUM_CONSTANT(VALIGN_BOTTOM);
BIND_ENUM_CONSTANT(VALIGN_FILL);
+ BIND_ENUM_CONSTANT(AUTOWRAP_OFF);
+ BIND_ENUM_CONSTANT(AUTOWRAP_ARBITRARY);
+ BIND_ENUM_CONSTANT(AUTOWRAP_WORD);
+ BIND_ENUM_CONSTANT(AUTOWRAP_WORD_SMART);
+
+ BIND_ENUM_CONSTANT(OVERRUN_NO_TRIMMING);
+ BIND_ENUM_CONSTANT(OVERRUN_TRIM_CHAR);
+ BIND_ENUM_CONSTANT(OVERRUN_TRIM_WORD);
+ BIND_ENUM_CONSTANT(OVERRUN_TRIM_ELLIPSIS);
+ BIND_ENUM_CONSTANT(OVERRUN_TRIM_WORD_ELLIPSIS);
+
ADD_PROPERTY(PropertyInfo(Variant::STRING, "text", PROPERTY_HINT_MULTILINE_TEXT, "", PROPERTY_USAGE_DEFAULT_INTL), "set_text", "get_text");
- ADD_PROPERTY(PropertyInfo(Variant::INT, "text_direction", PROPERTY_HINT_ENUM, "Auto,LTR,RTL,Inherited"), "set_text_direction", "get_text_direction");
+ ADD_PROPERTY(PropertyInfo(Variant::INT, "text_direction", PROPERTY_HINT_ENUM, "Auto,Left-to-Right,Right-to-Left,Inherited"), "set_text_direction", "get_text_direction");
ADD_PROPERTY(PropertyInfo(Variant::STRING, "language"), "set_language", "get_language");
ADD_PROPERTY(PropertyInfo(Variant::INT, "align", PROPERTY_HINT_ENUM, "Left,Center,Right,Fill"), "set_align", "get_align");
ADD_PROPERTY(PropertyInfo(Variant::INT, "valign", PROPERTY_HINT_ENUM, "Top,Center,Bottom,Fill"), "set_valign", "get_valign");
- ADD_PROPERTY(PropertyInfo(Variant::BOOL, "autowrap"), "set_autowrap", "has_autowrap");
+ ADD_PROPERTY(PropertyInfo(Variant::INT, "autowrap_mode", PROPERTY_HINT_ENUM, "Off,Arbitrary,Word,Word (Smart)"), "set_autowrap_mode", "get_autowrap_mode");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "clip_text"), "set_clip_text", "is_clipping_text");
+ ADD_PROPERTY(PropertyInfo(Variant::INT, "text_overrun_behavior", PROPERTY_HINT_ENUM, "Trim nothing,Trim characters,Trim words,Ellipsis,Word ellipsis"), "set_text_overrun_behavior", "get_text_overrun_behavior");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "uppercase"), "set_uppercase", "is_uppercase");
ADD_PROPERTY(PropertyInfo(Variant::INT, "visible_characters", PROPERTY_HINT_RANGE, "-1,128000,1", PROPERTY_USAGE_EDITOR), "set_visible_characters", "get_visible_characters");
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "percent_visible", PROPERTY_HINT_RANGE, "0,1,0.001"), "set_percent_visible", "get_percent_visible");
diff --git a/scene/gui/label.h b/scene/gui/label.h
index 032b4112e1..8b48eb9670 100644
--- a/scene/gui/label.h
+++ b/scene/gui/label.h
@@ -51,13 +51,29 @@ public:
VALIGN_FILL
};
+ enum AutowrapMode {
+ AUTOWRAP_OFF,
+ AUTOWRAP_ARBITRARY,
+ AUTOWRAP_WORD,
+ AUTOWRAP_WORD_SMART
+ };
+
+ enum OverrunBehavior {
+ OVERRUN_NO_TRIMMING,
+ OVERRUN_TRIM_CHAR,
+ OVERRUN_TRIM_WORD,
+ OVERRUN_TRIM_ELLIPSIS,
+ OVERRUN_TRIM_WORD_ELLIPSIS,
+ };
+
private:
Align align = ALIGN_LEFT;
VAlign valign = VALIGN_TOP;
String text;
String xl_text;
- bool autowrap = false;
+ AutowrapMode autowrap_mode = AUTOWRAP_OFF;
bool clip = false;
+ OverrunBehavior overrun_behavior = OVERRUN_NO_TRIMMING;
Size2 minsize;
bool uppercase = false;
@@ -118,8 +134,8 @@ public:
void set_structured_text_bidi_override_options(Array p_args);
Array get_structured_text_bidi_override_options() const;
- void set_autowrap(bool p_autowrap);
- bool has_autowrap() const;
+ void set_autowrap_mode(AutowrapMode p_mode);
+ AutowrapMode get_autowrap_mode() const;
void set_uppercase(bool p_uppercase);
bool is_uppercase() const;
@@ -131,6 +147,9 @@ public:
void set_clip_text(bool p_clip);
bool is_clipping_text() const;
+ void set_text_overrun_behavior(OverrunBehavior p_behavior);
+ OverrunBehavior get_text_overrun_behavior() const;
+
void set_percent_visible(float p_percent);
float get_percent_visible() const;
@@ -150,5 +169,7 @@ public:
VARIANT_ENUM_CAST(Label::Align);
VARIANT_ENUM_CAST(Label::VAlign);
+VARIANT_ENUM_CAST(Label::AutowrapMode);
+VARIANT_ENUM_CAST(Label::OverrunBehavior);
#endif
diff --git a/scene/gui/line_edit.cpp b/scene/gui/line_edit.cpp
index d1cd73c803..f2d0d9bb22 100644
--- a/scene/gui/line_edit.cpp
+++ b/scene/gui/line_edit.cpp
@@ -51,13 +51,13 @@ void LineEdit::_swap_current_input_direction() {
} else {
input_direction = TEXT_DIRECTION_LTR;
}
- set_cursor_position(get_cursor_position());
+ set_caret_column(get_caret_column());
update();
}
-void LineEdit::_move_cursor_left(bool p_select, bool p_move_by_word) {
+void LineEdit::_move_caret_left(bool p_select, bool p_move_by_word) {
if (selection.enabled && !p_select) {
- set_cursor_position(selection.begin);
+ set_caret_column(selection.begin);
deselect();
return;
}
@@ -65,7 +65,7 @@ void LineEdit::_move_cursor_left(bool p_select, bool p_move_by_word) {
shift_selection_check_pre(p_select);
if (p_move_by_word) {
- int cc = cursor_pos;
+ int cc = caret_column;
Vector<Vector2i> words = TS->shaped_text_get_word_breaks(text_rid);
for (int i = words.size() - 1; i >= 0; i--) {
@@ -75,21 +75,21 @@ void LineEdit::_move_cursor_left(bool p_select, bool p_move_by_word) {
}
}
- set_cursor_position(cc);
+ set_caret_column(cc);
} else {
- if (mid_grapheme_caret_enabled) {
- set_cursor_position(get_cursor_position() - 1);
+ if (caret_mid_grapheme_enabled) {
+ set_caret_column(get_caret_column() - 1);
} else {
- set_cursor_position(TS->shaped_text_prev_grapheme_pos(text_rid, get_cursor_position()));
+ set_caret_column(TS->shaped_text_prev_grapheme_pos(text_rid, get_caret_column()));
}
}
shift_selection_check_post(p_select);
}
-void LineEdit::_move_cursor_right(bool p_select, bool p_move_by_word) {
+void LineEdit::_move_caret_right(bool p_select, bool p_move_by_word) {
if (selection.enabled && !p_select) {
- set_cursor_position(selection.end);
+ set_caret_column(selection.end);
deselect();
return;
}
@@ -97,7 +97,7 @@ void LineEdit::_move_cursor_right(bool p_select, bool p_move_by_word) {
shift_selection_check_pre(p_select);
if (p_move_by_word) {
- int cc = cursor_pos;
+ int cc = caret_column;
Vector<Vector2i> words = TS->shaped_text_get_word_breaks(text_rid);
for (int i = 0; i < words.size(); i++) {
@@ -107,27 +107,27 @@ void LineEdit::_move_cursor_right(bool p_select, bool p_move_by_word) {
}
}
- set_cursor_position(cc);
+ set_caret_column(cc);
} else {
- if (mid_grapheme_caret_enabled) {
- set_cursor_position(get_cursor_position() + 1);
+ if (caret_mid_grapheme_enabled) {
+ set_caret_column(get_caret_column() + 1);
} else {
- set_cursor_position(TS->shaped_text_next_grapheme_pos(text_rid, get_cursor_position()));
+ set_caret_column(TS->shaped_text_next_grapheme_pos(text_rid, get_caret_column()));
}
}
shift_selection_check_post(p_select);
}
-void LineEdit::_move_cursor_start(bool p_select) {
+void LineEdit::_move_caret_start(bool p_select) {
shift_selection_check_pre(p_select);
- set_cursor_position(0);
+ set_caret_column(0);
shift_selection_check_post(p_select);
}
-void LineEdit::_move_cursor_end(bool p_select) {
+void LineEdit::_move_caret_end(bool p_select) {
shift_selection_check_pre(p_select);
- set_cursor_position(text.length());
+ set_caret_column(text.length());
shift_selection_check_post(p_select);
}
@@ -138,7 +138,7 @@ void LineEdit::_backspace(bool p_word, bool p_all_to_left) {
if (p_all_to_left) {
deselect();
- text = text.substr(0, cursor_pos);
+ text = text.substr(0, caret_column);
_text_changed();
return;
}
@@ -149,18 +149,19 @@ void LineEdit::_backspace(bool p_word, bool p_all_to_left) {
}
if (p_word) {
- int cc = cursor_pos;
+ int cc = caret_column;
Vector<Vector2i> words = TS->shaped_text_get_word_breaks(text_rid);
for (int i = words.size() - 1; i >= 0; i--) {
if (words[i].x < cc) {
cc = words[i].x;
+ break;
}
}
- delete_text(cc, cursor_pos);
+ delete_text(cc, caret_column);
- set_cursor_position(cc);
+ set_caret_column(cc);
} else {
delete_char();
}
@@ -173,9 +174,9 @@ void LineEdit::_delete(bool p_word, bool p_all_to_right) {
if (p_all_to_right) {
deselect();
- text = text.substr(cursor_pos, text.length() - cursor_pos);
+ text = text.substr(caret_column, text.length() - caret_column);
_shape();
- set_cursor_position(0);
+ set_caret_column(0);
_text_changed();
return;
}
@@ -187,12 +188,12 @@ void LineEdit::_delete(bool p_word, bool p_all_to_right) {
int text_len = text.length();
- if (cursor_pos == text_len) {
+ if (caret_column == text_len) {
return; // Nothing to do.
}
if (p_word) {
- int cc = cursor_pos;
+ int cc = caret_column;
Vector<Vector2i> words = TS->shaped_text_get_word_breaks(text_rid);
for (int i = 0; i < words.size(); i++) {
if (words[i].y > cc) {
@@ -201,20 +202,23 @@ void LineEdit::_delete(bool p_word, bool p_all_to_right) {
}
}
- delete_text(cursor_pos, cc);
+ delete_text(caret_column, cc);
+ set_caret_column(caret_column);
} else {
- if (mid_grapheme_caret_enabled) {
- set_cursor_position(cursor_pos + 1);
+ if (caret_mid_grapheme_enabled) {
+ set_caret_column(caret_column + 1);
delete_char();
} else {
- int cc = cursor_pos;
- set_cursor_position(TS->shaped_text_next_grapheme_pos(text_rid, cursor_pos));
- delete_text(cc, cursor_pos);
+ int cc = caret_column;
+ set_caret_column(TS->shaped_text_next_grapheme_pos(text_rid, caret_column));
+ delete_text(cc, caret_column);
}
}
}
void LineEdit::_gui_input(Ref<InputEvent> p_event) {
+ ERR_FAIL_COND(p_event.is_null());
+
Ref<InputEventMouseButton> b = p_event;
if (b.is_valid()) {
@@ -246,35 +250,35 @@ void LineEdit::_gui_input(Ref<InputEvent> p_event) {
return;
}
- shift_selection_check_pre(b->get_shift());
+ shift_selection_check_pre(b->is_shift_pressed());
- set_cursor_at_pixel_pos(b->get_position().x);
+ set_caret_at_pixel_pos(b->get_position().x);
- if (b->get_shift()) {
- selection_fill_at_cursor();
+ if (b->is_shift_pressed()) {
+ selection_fill_at_caret();
selection.creating = true;
} else {
if (selecting_enabled) {
- if (!b->is_doubleclick() && (OS::get_singleton()->get_ticks_msec() - selection.last_dblclk) < 600) {
+ if (!b->is_double_click() && (OS::get_singleton()->get_ticks_msec() - selection.last_dblclk) < 600) {
// Triple-click select all.
selection.enabled = true;
selection.begin = 0;
selection.end = text.length();
- selection.doubleclick = true;
+ selection.double_click = true;
selection.last_dblclk = 0;
- cursor_pos = selection.begin;
- } else if (b->is_doubleclick()) {
+ caret_column = selection.begin;
+ } else if (b->is_double_click()) {
// Double-click select word.
Vector<Vector2i> words = TS->shaped_text_get_word_breaks(text_rid);
for (int i = 0; i < words.size(); i++) {
- if (words[i].x < cursor_pos && words[i].y > cursor_pos) {
+ if (words[i].x < caret_column && words[i].y > caret_column) {
selection.enabled = true;
selection.begin = words[i].x;
selection.end = words[i].y;
- selection.doubleclick = true;
+ selection.double_click = true;
selection.last_dblclk = OS::get_singleton()->get_ticks_msec();
- cursor_pos = selection.end;
+ caret_column = selection.end;
break;
}
}
@@ -283,9 +287,9 @@ void LineEdit::_gui_input(Ref<InputEvent> p_event) {
selection.drag_attempt = false;
- if ((cursor_pos < selection.begin) || (cursor_pos > selection.end) || !selection.enabled) {
+ if ((caret_column < selection.begin) || (caret_column > selection.end) || !selection.enabled) {
deselect();
- selection.cursor_start = cursor_pos;
+ selection.start_column = caret_column;
selection.creating = true;
} else if (selection.enabled) {
selection.drag_attempt = true;
@@ -304,11 +308,11 @@ void LineEdit::_gui_input(Ref<InputEvent> p_event) {
}
}
- if ((!selection.creating) && (!selection.doubleclick)) {
+ if ((!selection.creating) && (!selection.double_click)) {
deselect();
}
selection.creating = false;
- selection.doubleclick = false;
+ selection.double_click = false;
show_virtual_keyboard();
}
@@ -329,8 +333,8 @@ void LineEdit::_gui_input(Ref<InputEvent> p_event) {
if (m->get_button_mask() & MOUSE_BUTTON_LEFT) {
if (selection.creating) {
- set_cursor_at_pixel_pos(m->get_position().x);
- selection_fill_at_cursor();
+ set_caret_at_pixel_pos(m->get_position().x);
+ selection_fill_at_caret();
}
}
}
@@ -344,7 +348,7 @@ void LineEdit::_gui_input(Ref<InputEvent> p_event) {
if (context_menu_enabled) {
if (k->is_action("ui_menu", true)) {
- Point2 pos = Point2(get_cursor_pixel_pos().x, (get_size().y + get_theme_font("font")->get_height(get_theme_font_size("font_size"))) / 2);
+ Point2 pos = Point2(get_caret_pixel_pos().x, (get_size().y + get_theme_font("font")->get_height(get_theme_font_size("font_size"))) / 2);
menu->set_position(get_global_transform().xform(pos));
menu->set_size(Vector2(1, 1));
_generate_context_menu();
@@ -353,9 +357,9 @@ void LineEdit::_gui_input(Ref<InputEvent> p_event) {
}
}
- // Default is ENTER, KP_ENTER. Cannot use ui_accept as default includes SPACE
- if (k->is_action("ui_text_newline", true)) {
- emit_signal("text_entered", text);
+ // Default is ENTER and KP_ENTER. Cannot use ui_accept as default includes SPACE
+ if (k->is_action("ui_text_submit", false)) {
+ emit_signal("text_submitted", text);
if (DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_VIRTUAL_KEYBOARD) && virtual_keyboard_enabled) {
DisplayServer::get_singleton()->virtual_keyboard_hide();
}
@@ -438,39 +442,39 @@ void LineEdit::_gui_input(Ref<InputEvent> p_event) {
// Cursor Movement
k = k->duplicate();
- bool shift_pressed = k->get_shift();
+ bool shift_pressed = k->is_shift_pressed();
// Remove shift or else actions will not match. Use above variable for selection.
- k->set_shift(false);
+ k->set_shift_pressed(false);
if (k->is_action("ui_text_caret_word_left", true)) {
- _move_cursor_left(shift_pressed, true);
+ _move_caret_left(shift_pressed, true);
accept_event();
return;
}
if (k->is_action("ui_text_caret_left", true)) {
- _move_cursor_left(shift_pressed);
+ _move_caret_left(shift_pressed);
accept_event();
return;
}
if (k->is_action("ui_text_caret_word_right", true)) {
- _move_cursor_right(shift_pressed, true);
+ _move_caret_right(shift_pressed, true);
accept_event();
return;
}
if (k->is_action("ui_text_caret_right", true)) {
- _move_cursor_right(shift_pressed, false);
+ _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_cursor_start(shift_pressed);
+ _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_cursor_end(shift_pressed);
+ _move_caret_end(shift_pressed);
accept_event();
return;
}
@@ -486,14 +490,14 @@ void LineEdit::_gui_input(Ref<InputEvent> p_event) {
// Allow unicode handling if:
// * No Modifiers are pressed (except shift)
- bool allow_unicode_handling = !(k->get_command() || k->get_control() || k->get_alt() || k->get_metakey());
+ bool allow_unicode_handling = !(k->is_command_pressed() || 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 active)
selection_delete();
char32_t ucodestr[2] = { (char32_t)k->get_unicode(), 0 };
int prev_len = text.length();
- append_at_cursor(ucodestr);
+ insert_text_at_caret(ucodestr);
if (text.length() != prev_len) {
_text_changed();
}
@@ -540,20 +544,20 @@ 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) {
- set_cursor_at_pixel_pos(p_point.x);
+ set_caret_at_pixel_pos(p_point.x);
int selected = selection.end - selection.begin;
text.erase(selection.begin, selected);
_shape();
- append_at_cursor(p_data);
- selection.begin = cursor_pos - selected;
- selection.end = cursor_pos;
+ insert_text_at_caret(p_data);
+ selection.begin = caret_column - selected;
+ selection.end = caret_column;
}
}
Control::CursorShape LineEdit::get_cursor_shape(const Point2 &p_pos) const {
- if (!text.is_empty() && is_editable() && _is_over_clear_button(p_pos)) {
+ if ((!text.is_empty() && is_editable() && _is_over_clear_button(p_pos)) || (!is_editable() && (!is_selecting_enabled() || text.is_empty()))) {
return CURSOR_ARROW;
}
return Control::get_cursor_shape(p_pos);
@@ -573,8 +577,8 @@ void LineEdit::_notification(int p_what) {
#ifdef TOOLS_ENABLED
case NOTIFICATION_ENTER_TREE: {
if (Engine::get_singleton()->is_editor_hint() && !get_tree()->is_node_being_edited(this)) {
- cursor_set_blink_enabled(EDITOR_DEF("text_editor/cursor/caret_blink", false));
- cursor_set_blink_speed(EDITOR_DEF("text_editor/cursor/caret_blink_speed", 0.65));
+ set_caret_blink_enabled(EDITOR_DEF("text_editor/cursor/caret_blink", false));
+ set_caret_blink_speed(EDITOR_DEF("text_editor/cursor/caret_blink_speed", 0.65));
if (!EditorSettings::get_singleton()->is_connected("settings_changed", callable_mp(this, &LineEdit::_editor_settings_changed))) {
EditorSettings::get_singleton()->connect("settings_changed", callable_mp(this, &LineEdit::_editor_settings_changed));
@@ -585,7 +589,7 @@ void LineEdit::_notification(int p_what) {
case NOTIFICATION_RESIZED: {
_fit_to_width();
scroll_offset = 0;
- set_cursor_position(get_cursor_position());
+ set_caret_column(get_caret_column());
} break;
case NOTIFICATION_LAYOUT_DIRECTION_CHANGED:
case NOTIFICATION_THEME_CHANGED: {
@@ -672,7 +676,7 @@ void LineEdit::_notification(int p_what) {
Color selection_color = get_theme_color("selection_color");
Color font_color = is_editable() ? get_theme_color("font_color") : get_theme_color("font_uneditable_color");
Color font_selected_color = get_theme_color("font_selected_color");
- Color cursor_color = get_theme_color("cursor_color");
+ Color caret_color = get_theme_color("caret_color");
// Draw placeholder color.
if (using_placeholder) {
@@ -776,7 +780,7 @@ void LineEdit::_notification(int p_what) {
// Normal caret.
Rect2 l_caret, t_caret;
TextServer::Direction l_dir, t_dir;
- TS->shaped_text_get_carets(text_rid, cursor_pos, l_caret, l_dir, t_caret, t_dir);
+ TS->shaped_text_get_carets(text_rid, caret_column, l_caret, l_dir, t_caret, t_dir);
if (l_caret == Rect2() && t_caret == Rect2()) {
// No carets, add one at the start.
@@ -789,28 +793,28 @@ void LineEdit::_notification(int p_what) {
l_dir = TextServer::DIRECTION_LTR;
l_caret = Rect2(Vector2(x_ofs, y), Size2(caret_width, h));
}
- RenderingServer::get_singleton()->canvas_item_add_rect(ci, l_caret, cursor_color);
+ RenderingServer::get_singleton()->canvas_item_add_rect(ci, l_caret, caret_color);
} else {
if (l_caret != Rect2() && l_dir == TextServer::DIRECTION_AUTO) {
// Draw extra marker on top of mid caret.
Rect2 trect = Rect2(l_caret.position.x - 3 * caret_width, l_caret.position.y, 6 * caret_width, caret_width);
trect.position += ofs;
- RenderingServer::get_singleton()->canvas_item_add_rect(ci, trect, cursor_color);
+ RenderingServer::get_singleton()->canvas_item_add_rect(ci, trect, caret_color);
}
l_caret.position += ofs;
l_caret.size.x = caret_width;
- RenderingServer::get_singleton()->canvas_item_add_rect(ci, l_caret, cursor_color);
+ RenderingServer::get_singleton()->canvas_item_add_rect(ci, l_caret, caret_color);
t_caret.position += ofs;
t_caret.size.x = caret_width;
- RenderingServer::get_singleton()->canvas_item_add_rect(ci, t_caret, cursor_color);
+ RenderingServer::get_singleton()->canvas_item_add_rect(ci, t_caret, caret_color);
}
} else {
{
// IME intermediate text range.
- Vector<Vector2> sel = TS->shaped_text_get_selection(text_rid, cursor_pos, cursor_pos + ime_text.length());
+ Vector<Vector2> sel = TS->shaped_text_get_selection(text_rid, caret_column, caret_column + ime_text.length());
for (int i = 0; i < sel.size(); i++) {
Rect2 rect = Rect2(sel[i].x + ofs.x, ofs.y, sel[i].y - sel[i].x, text_height);
if (rect.position.x + rect.size.x <= x_ofs || rect.position.x > ofs_max) {
@@ -823,12 +827,12 @@ void LineEdit::_notification(int p_what) {
rect.size.x = ofs_max - rect.position.x;
}
rect.size.y = caret_width;
- RenderingServer::get_singleton()->canvas_item_add_rect(ci, rect, cursor_color);
+ RenderingServer::get_singleton()->canvas_item_add_rect(ci, rect, caret_color);
}
}
{
// IME caret.
- Vector<Vector2> sel = TS->shaped_text_get_selection(text_rid, cursor_pos + ime_selection.x, cursor_pos + ime_selection.x + ime_selection.y);
+ Vector<Vector2> sel = TS->shaped_text_get_selection(text_rid, caret_column + ime_selection.x, caret_column + ime_selection.x + ime_selection.y);
for (int i = 0; i < sel.size(); i++) {
Rect2 rect = Rect2(sel[i].x + ofs.x, ofs.y, sel[i].y - sel[i].x, text_height);
if (rect.position.x + rect.size.x <= x_ofs || rect.position.x > ofs_max) {
@@ -841,7 +845,7 @@ void LineEdit::_notification(int p_what) {
rect.size.x = ofs_max - rect.position.x;
}
rect.size.y = caret_width * 3;
- RenderingServer::get_singleton()->canvas_item_add_rect(ci, rect, cursor_color);
+ RenderingServer::get_singleton()->canvas_item_add_rect(ci, rect, caret_color);
}
}
}
@@ -867,8 +871,8 @@ void LineEdit::_notification(int p_what) {
if (get_viewport()->get_window_id() != DisplayServer::INVALID_WINDOW_ID && DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_IME)) {
DisplayServer::get_singleton()->window_set_ime_active(true, get_viewport()->get_window_id());
- Point2 cursor_pos = Point2(get_cursor_position(), 1) * get_minimum_size().height;
- DisplayServer::get_singleton()->window_set_ime_position(get_global_position() + cursor_pos, get_viewport()->get_window_id());
+ Point2 caret_column = Point2(get_caret_column(), 1) * get_minimum_size().height;
+ DisplayServer::get_singleton()->window_set_ime_position(get_global_position() + caret_column, get_viewport()->get_window_id());
}
show_virtual_keyboard();
@@ -885,7 +889,7 @@ void LineEdit::_notification(int p_what) {
ime_text = "";
ime_selection = Point2();
_shape();
- set_cursor_position(cursor_pos); // Update scroll_offset
+ 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();
@@ -897,7 +901,7 @@ void LineEdit::_notification(int p_what) {
ime_text = DisplayServer::get_singleton()->ime_get_text();
ime_selection = DisplayServer::get_singleton()->ime_get_selection();
_shape();
- set_cursor_position(cursor_pos); // Update scroll_offset
+ set_caret_column(caret_column); // Update scroll_offset
update();
}
@@ -931,7 +935,7 @@ void LineEdit::paste_text() {
if (selection.enabled) {
selection_delete();
}
- append_at_cursor(paste_buffer);
+ insert_text_at_caret(paste_buffer);
if (!text_changed_dirty) {
if (is_inside_tree() && text.length() != prev_len) {
@@ -959,7 +963,7 @@ void LineEdit::undo() {
TextOperation op = undo_stack_pos->get();
text = op.text;
scroll_offset = op.scroll_offset;
- set_cursor_position(op.cursor_pos);
+ set_caret_column(op.caret_column);
_shape();
_emit_text_change();
@@ -980,7 +984,7 @@ void LineEdit::redo() {
TextOperation op = undo_stack_pos->get();
text = op.text;
scroll_offset = op.scroll_offset;
- set_cursor_position(op.cursor_pos);
+ set_caret_column(op.caret_column);
_shape();
_emit_text_change();
@@ -988,7 +992,7 @@ void LineEdit::redo() {
void LineEdit::shift_selection_check_pre(bool p_shift) {
if (!selection.enabled && p_shift) {
- selection.cursor_start = cursor_pos;
+ selection.start_column = caret_column;
}
if (!p_shift) {
deselect();
@@ -997,11 +1001,11 @@ void LineEdit::shift_selection_check_pre(bool p_shift) {
void LineEdit::shift_selection_check_post(bool p_shift) {
if (p_shift) {
- selection_fill_at_cursor();
+ selection_fill_at_caret();
}
}
-void LineEdit::set_cursor_at_pixel_pos(int p_x) {
+void LineEdit::set_caret_at_pixel_pos(int p_x) {
Ref<StyleBox> style = get_theme_stylebox("normal");
bool rtl = is_layout_rtl();
@@ -1046,10 +1050,10 @@ void LineEdit::set_cursor_at_pixel_pos(int p_x) {
}
int ofs = TS->shaped_text_hit_test_position(text_rid, p_x - x_ofs - scroll_offset);
- set_cursor_position(ofs);
+ set_caret_column(ofs);
}
-Vector2i LineEdit::get_cursor_pixel_pos() {
+Vector2i LineEdit::get_caret_pixel_pos() {
Ref<StyleBox> style = get_theme_stylebox("normal");
bool rtl = is_layout_rtl();
@@ -1098,9 +1102,9 @@ Vector2i LineEdit::get_cursor_pixel_pos() {
TextServer::Direction l_dir, t_dir;
// Get position of the start of caret.
if (ime_text.length() != 0 && ime_selection.x != 0) {
- TS->shaped_text_get_carets(text_rid, cursor_pos + ime_selection.x, l_caret, l_dir, t_caret, t_dir);
+ TS->shaped_text_get_carets(text_rid, caret_column + ime_selection.x, l_caret, l_dir, t_caret, t_dir);
} else {
- TS->shaped_text_get_carets(text_rid, cursor_pos, l_caret, l_dir, t_caret, t_dir);
+ TS->shaped_text_get_carets(text_rid, caret_column, l_caret, l_dir, t_caret, t_dir);
}
if ((l_caret != Rect2() && (l_dir == TextServer::DIRECTION_AUTO || l_dir == (TextServer::Direction)input_direction)) || (t_caret == Rect2())) {
@@ -1112,9 +1116,9 @@ Vector2i LineEdit::get_cursor_pixel_pos() {
// Get position of the end of caret.
if (ime_text.length() != 0) {
if (ime_selection.y != 0) {
- TS->shaped_text_get_carets(text_rid, cursor_pos + ime_selection.x + ime_selection.y, l_caret, l_dir, t_caret, t_dir);
+ TS->shaped_text_get_carets(text_rid, caret_column + ime_selection.x + ime_selection.y, l_caret, l_dir, t_caret, t_dir);
} else {
- TS->shaped_text_get_carets(text_rid, cursor_pos + ime_text.size(), l_caret, l_dir, t_caret, t_dir);
+ TS->shaped_text_get_carets(text_rid, caret_column + ime_text.size(), l_caret, l_dir, t_caret, t_dir);
}
if ((l_caret != Rect2() && (l_dir == TextServer::DIRECTION_AUTO || l_dir == (TextServer::Direction)input_direction)) || (t_caret == Rect2())) {
ret.y = x_ofs + l_caret.position.x + scroll_offset;
@@ -1128,19 +1132,19 @@ Vector2i LineEdit::get_cursor_pixel_pos() {
return ret;
}
-void LineEdit::set_mid_grapheme_caret_enabled(const bool p_enabled) {
- mid_grapheme_caret_enabled = p_enabled;
+void LineEdit::set_caret_mid_grapheme_enabled(const bool p_enabled) {
+ caret_mid_grapheme_enabled = p_enabled;
}
-bool LineEdit::get_mid_grapheme_caret_enabled() const {
- return mid_grapheme_caret_enabled;
+bool LineEdit::is_caret_mid_grapheme_enabled() const {
+ return caret_mid_grapheme_enabled;
}
-bool LineEdit::cursor_get_blink_enabled() const {
+bool LineEdit::is_caret_blink_enabled() const {
return caret_blink_enabled;
}
-void LineEdit::cursor_set_blink_enabled(const bool p_enabled) {
+void LineEdit::set_caret_blink_enabled(const bool p_enabled) {
caret_blink_enabled = p_enabled;
if (has_focus() || caret_force_displayed) {
@@ -1158,21 +1162,21 @@ void LineEdit::cursor_set_blink_enabled(const bool p_enabled) {
notify_property_list_changed();
}
-bool LineEdit::cursor_get_force_displayed() const {
+bool LineEdit::is_caret_force_displayed() const {
return caret_force_displayed;
}
-void LineEdit::cursor_set_force_displayed(const bool p_enabled) {
+void LineEdit::set_caret_force_displayed(const bool p_enabled) {
caret_force_displayed = p_enabled;
- cursor_set_blink_enabled(caret_blink_enabled);
+ set_caret_blink_enabled(caret_blink_enabled);
update();
}
-float LineEdit::cursor_get_blink_speed() const {
+float LineEdit::get_caret_blink_speed() const {
return caret_blink_timer->get_wait_time();
}
-void LineEdit::cursor_set_blink_speed(const float p_speed) {
+void LineEdit::set_caret_blink_speed(const float p_speed) {
ERR_FAIL_COND(p_speed <= 0);
caret_blink_timer->set_wait_time(p_speed);
}
@@ -1196,14 +1200,14 @@ void LineEdit::_toggle_draw_caret() {
}
void LineEdit::delete_char() {
- if ((text.length() <= 0) || (cursor_pos == 0)) {
+ if ((text.length() <= 0) || (caret_column == 0)) {
return;
}
- text.erase(cursor_pos - 1, 1);
+ text.erase(caret_column - 1, 1);
_shape();
- set_cursor_position(get_cursor_position() - 1);
+ set_caret_column(get_caret_column() - 1);
_text_changed();
}
@@ -1215,10 +1219,10 @@ void LineEdit::delete_text(int p_from_column, int p_to_column) {
text.erase(p_from_column, p_to_column - p_from_column);
_shape();
- cursor_pos -= CLAMP(cursor_pos - p_from_column, 0, p_to_column - p_from_column);
+ caret_column -= CLAMP(caret_column - p_from_column, 0, p_to_column - p_from_column);
- if (cursor_pos >= text.length()) {
- cursor_pos = text.length();
+ if (caret_column >= text.length()) {
+ caret_column = text.length();
}
if (!text_changed_dirty) {
@@ -1231,10 +1235,11 @@ void LineEdit::delete_text(int p_from_column, int p_to_column) {
void LineEdit::set_text(String p_text) {
clear_internal();
- append_at_cursor(p_text);
+ insert_text_at_caret(p_text);
+ _create_undo_state();
update();
- cursor_pos = 0;
+ caret_column = 0;
scroll_offset = 0;
}
@@ -1344,7 +1349,7 @@ void LineEdit::show_virtual_keyboard() {
if (selection.enabled) {
DisplayServer::get_singleton()->virtual_keyboard_show(text, get_global_rect(), false, max_length, selection.begin, selection.end);
} else {
- DisplayServer::get_singleton()->virtual_keyboard_show(text, get_global_rect(), false, max_length, cursor_pos);
+ DisplayServer::get_singleton()->virtual_keyboard_show(text, get_global_rect(), false, max_length, caret_column);
}
}
}
@@ -1373,16 +1378,16 @@ float LineEdit::get_placeholder_alpha() const {
return placeholder_alpha;
}
-void LineEdit::set_cursor_position(int p_pos) {
- if (p_pos > (int)text.length()) {
- p_pos = text.length();
+void LineEdit::set_caret_column(int p_column) {
+ if (p_column > (int)text.length()) {
+ p_column = text.length();
}
- if (p_pos < 0) {
- p_pos = 0;
+ if (p_column < 0) {
+ p_column = 0;
}
- cursor_pos = p_pos;
+ caret_column = p_column;
// Fit to window.
@@ -1437,7 +1442,7 @@ void LineEdit::set_cursor_position(int p_pos) {
}
// Note: Use two coordinates to fit IME input range.
- Vector2i primary_catret_offset = get_cursor_pixel_pos();
+ Vector2i primary_catret_offset = get_caret_pixel_pos();
if (MIN(primary_catret_offset.x, primary_catret_offset.y) <= x_ofs) {
scroll_offset += (x_ofs - MIN(primary_catret_offset.x, primary_catret_offset.y));
@@ -1449,8 +1454,8 @@ void LineEdit::set_cursor_position(int p_pos) {
update();
}
-int LineEdit::get_cursor_position() const {
- return cursor_pos;
+int LineEdit::get_caret_column() const {
+ return caret_column;
}
void LineEdit::set_scroll_offset(int p_pos) {
@@ -1464,26 +1469,30 @@ int LineEdit::get_scroll_offset() const {
return scroll_offset;
}
-void LineEdit::append_at_cursor(String p_text) {
- if ((max_length <= 0) || (text.length() + p_text.length() <= max_length)) {
- String pre = text.substr(0, cursor_pos);
- String post = text.substr(cursor_pos, text.length() - cursor_pos);
- text = pre + p_text + post;
- _shape();
- TextServer::Direction dir = TS->shaped_text_get_dominant_direciton_in_range(text_rid, cursor_pos, cursor_pos + p_text.length());
- if (dir != TextServer::DIRECTION_AUTO) {
- input_direction = (TextDirection)dir;
+void LineEdit::insert_text_at_caret(String p_text) {
+ if (max_length > 0) {
+ // Truncate text to append to fit in max_length, if needed.
+ int available_chars = max_length - text.length();
+ if (p_text.length() > available_chars) {
+ emit_signal("text_change_rejected", p_text.substr(available_chars));
+ p_text = p_text.substr(0, available_chars);
}
- set_cursor_position(cursor_pos + p_text.length());
- } else {
- emit_signal("text_change_rejected");
}
+ String pre = text.substr(0, caret_column);
+ String post = text.substr(caret_column, text.length() - caret_column);
+ text = pre + p_text + post;
+ _shape();
+ TextServer::Direction dir = TS->shaped_text_get_dominant_direciton_in_range(text_rid, caret_column, caret_column + p_text.length());
+ if (dir != TextServer::DIRECTION_AUTO) {
+ input_direction = (TextDirection)dir;
+ }
+ set_caret_column(caret_column + p_text.length());
}
void LineEdit::clear_internal() {
deselect();
_clear_undo_stack();
- cursor_pos = 0;
+ caret_column = 0;
scroll_offset = 0;
undo_text = "";
text = "";
@@ -1503,7 +1512,7 @@ Size2 LineEdit::get_minimum_size() const {
min_size.width = get_theme_constant("minimum_character_width") * em_space_size;
if (expand_to_text_length) {
- // Add a space because some fonts are too exact, and because cursor needs a bit more when at the end.
+ // Add a space because some fonts are too exact, and because caret needs a bit more when at the end.
min_size.width = MAX(min_size.width, full_width + em_space_size);
}
@@ -1524,10 +1533,10 @@ Size2 LineEdit::get_minimum_size() const {
void LineEdit::deselect() {
selection.begin = 0;
selection.end = 0;
- selection.cursor_start = 0;
+ selection.start_column = 0;
selection.enabled = false;
selection.creating = false;
- selection.doubleclick = false;
+ selection.double_click = false;
update();
}
@@ -1549,13 +1558,13 @@ int LineEdit::get_max_length() const {
return max_length;
}
-void LineEdit::selection_fill_at_cursor() {
+void LineEdit::selection_fill_at_caret() {
if (!selecting_enabled) {
return;
}
- selection.begin = cursor_pos;
- selection.end = selection.cursor_start;
+ selection.begin = caret_column;
+ selection.end = selection.start_column;
if (selection.end < selection.begin) {
int aux = selection.end;
@@ -1654,7 +1663,7 @@ void LineEdit::select(int p_from, int p_to) {
selection.begin = p_from;
selection.end = p_to;
selection.creating = false;
- selection.doubleclick = false;
+ selection.double_click = false;
update();
}
@@ -1712,82 +1721,82 @@ void LineEdit::menu_option(int p_option) {
} break;
case MENU_INSERT_LRM: {
if (editable) {
- append_at_cursor(String::chr(0x200E));
+ insert_text_at_caret(String::chr(0x200E));
}
} break;
case MENU_INSERT_RLM: {
if (editable) {
- append_at_cursor(String::chr(0x200F));
+ insert_text_at_caret(String::chr(0x200F));
}
} break;
case MENU_INSERT_LRE: {
if (editable) {
- append_at_cursor(String::chr(0x202A));
+ insert_text_at_caret(String::chr(0x202A));
}
} break;
case MENU_INSERT_RLE: {
if (editable) {
- append_at_cursor(String::chr(0x202B));
+ insert_text_at_caret(String::chr(0x202B));
}
} break;
case MENU_INSERT_LRO: {
if (editable) {
- append_at_cursor(String::chr(0x202D));
+ insert_text_at_caret(String::chr(0x202D));
}
} break;
case MENU_INSERT_RLO: {
if (editable) {
- append_at_cursor(String::chr(0x202E));
+ insert_text_at_caret(String::chr(0x202E));
}
} break;
case MENU_INSERT_PDF: {
if (editable) {
- append_at_cursor(String::chr(0x202C));
+ insert_text_at_caret(String::chr(0x202C));
}
} break;
case MENU_INSERT_ALM: {
if (editable) {
- append_at_cursor(String::chr(0x061C));
+ insert_text_at_caret(String::chr(0x061C));
}
} break;
case MENU_INSERT_LRI: {
if (editable) {
- append_at_cursor(String::chr(0x2066));
+ insert_text_at_caret(String::chr(0x2066));
}
} break;
case MENU_INSERT_RLI: {
if (editable) {
- append_at_cursor(String::chr(0x2067));
+ insert_text_at_caret(String::chr(0x2067));
}
} break;
case MENU_INSERT_FSI: {
if (editable) {
- append_at_cursor(String::chr(0x2068));
+ insert_text_at_caret(String::chr(0x2068));
}
} break;
case MENU_INSERT_PDI: {
if (editable) {
- append_at_cursor(String::chr(0x2069));
+ insert_text_at_caret(String::chr(0x2069));
}
} break;
case MENU_INSERT_ZWJ: {
if (editable) {
- append_at_cursor(String::chr(0x200D));
+ insert_text_at_caret(String::chr(0x200D));
}
} break;
case MENU_INSERT_ZWNJ: {
if (editable) {
- append_at_cursor(String::chr(0x200C));
+ insert_text_at_caret(String::chr(0x200C));
}
} break;
case MENU_INSERT_WJ: {
if (editable) {
- append_at_cursor(String::chr(0x2060));
+ insert_text_at_caret(String::chr(0x2060));
}
} break;
case MENU_INSERT_SHY: {
if (editable) {
- append_at_cursor(String::chr(0x00AD));
+ insert_text_at_caret(String::chr(0x00AD));
}
}
}
@@ -1807,18 +1816,18 @@ PopupMenu *LineEdit::get_menu() const {
void LineEdit::_editor_settings_changed() {
#ifdef TOOLS_ENABLED
- cursor_set_blink_enabled(EDITOR_DEF("text_editor/cursor/caret_blink", false));
- cursor_set_blink_speed(EDITOR_DEF("text_editor/cursor/caret_blink_speed", 0.65));
+ set_caret_blink_enabled(EDITOR_DEF("text_editor/cursor/caret_blink", false));
+ set_caret_blink_speed(EDITOR_DEF("text_editor/cursor/caret_blink_speed", 0.65));
#endif
}
-void LineEdit::set_expand_to_text_length(bool p_enabled) {
+void LineEdit::set_expand_to_text_length_enabled(bool p_enabled) {
expand_to_text_length = p_enabled;
minimum_size_changed();
- set_cursor_position(cursor_pos);
+ set_caret_column(caret_column);
}
-bool LineEdit::get_expand_to_text_length() const {
+bool LineEdit::is_expand_to_text_length_enabled() const {
return expand_to_text_length;
}
@@ -1903,7 +1912,7 @@ void LineEdit::_shape() {
t = secret_character.repeat(text.length() + ime_text.length());
} else {
if (ime_text.length() > 0) {
- t = text.substr(0, cursor_pos) + ime_text + text.substr(cursor_pos, text.length());
+ t = text.substr(0, caret_column) + ime_text + text.substr(caret_column, text.length());
} else {
t = text;
}
@@ -1968,7 +1977,7 @@ void LineEdit::_clear_undo_stack() {
void LineEdit::_create_undo_state() {
TextOperation op;
op.text = text;
- op.cursor_pos = cursor_pos;
+ op.caret_column = caret_column;
op.scroll_offset = scroll_offset;
undo_stack.push_back(op);
}
@@ -2113,23 +2122,23 @@ void LineEdit::_bind_methods() {
ClassDB::bind_method(D_METHOD("get_placeholder"), &LineEdit::get_placeholder);
ClassDB::bind_method(D_METHOD("set_placeholder_alpha", "alpha"), &LineEdit::set_placeholder_alpha);
ClassDB::bind_method(D_METHOD("get_placeholder_alpha"), &LineEdit::get_placeholder_alpha);
- ClassDB::bind_method(D_METHOD("set_cursor_position", "position"), &LineEdit::set_cursor_position);
- ClassDB::bind_method(D_METHOD("get_cursor_position"), &LineEdit::get_cursor_position);
+ ClassDB::bind_method(D_METHOD("set_caret_column", "position"), &LineEdit::set_caret_column);
+ ClassDB::bind_method(D_METHOD("get_caret_column"), &LineEdit::get_caret_column);
ClassDB::bind_method(D_METHOD("get_scroll_offset"), &LineEdit::get_scroll_offset);
- ClassDB::bind_method(D_METHOD("set_expand_to_text_length", "enabled"), &LineEdit::set_expand_to_text_length);
- ClassDB::bind_method(D_METHOD("get_expand_to_text_length"), &LineEdit::get_expand_to_text_length);
- ClassDB::bind_method(D_METHOD("cursor_set_blink_enabled", "enabled"), &LineEdit::cursor_set_blink_enabled);
- ClassDB::bind_method(D_METHOD("cursor_get_blink_enabled"), &LineEdit::cursor_get_blink_enabled);
- ClassDB::bind_method(D_METHOD("set_mid_grapheme_caret_enabled", "enabled"), &LineEdit::set_mid_grapheme_caret_enabled);
- ClassDB::bind_method(D_METHOD("get_mid_grapheme_caret_enabled"), &LineEdit::get_mid_grapheme_caret_enabled);
- ClassDB::bind_method(D_METHOD("cursor_set_force_displayed", "enabled"), &LineEdit::cursor_set_force_displayed);
- ClassDB::bind_method(D_METHOD("cursor_get_force_displayed"), &LineEdit::cursor_get_force_displayed);
- ClassDB::bind_method(D_METHOD("cursor_set_blink_speed", "blink_speed"), &LineEdit::cursor_set_blink_speed);
- ClassDB::bind_method(D_METHOD("cursor_get_blink_speed"), &LineEdit::cursor_get_blink_speed);
+ ClassDB::bind_method(D_METHOD("set_expand_to_text_length_enabled", "enabled"), &LineEdit::set_expand_to_text_length_enabled);
+ ClassDB::bind_method(D_METHOD("is_expand_to_text_length_enabled"), &LineEdit::is_expand_to_text_length_enabled);
+ ClassDB::bind_method(D_METHOD("set_caret_blink_enabled", "enabled"), &LineEdit::set_caret_blink_enabled);
+ ClassDB::bind_method(D_METHOD("is_caret_blink_enabled"), &LineEdit::is_caret_blink_enabled);
+ ClassDB::bind_method(D_METHOD("set_caret_mid_grapheme_enabled", "enabled"), &LineEdit::set_caret_mid_grapheme_enabled);
+ ClassDB::bind_method(D_METHOD("is_caret_mid_grapheme_enabled"), &LineEdit::is_caret_mid_grapheme_enabled);
+ ClassDB::bind_method(D_METHOD("set_caret_force_displayed", "enabled"), &LineEdit::set_caret_force_displayed);
+ ClassDB::bind_method(D_METHOD("is_caret_force_displayed"), &LineEdit::is_caret_force_displayed);
+ ClassDB::bind_method(D_METHOD("set_caret_blink_speed", "blink_speed"), &LineEdit::set_caret_blink_speed);
+ ClassDB::bind_method(D_METHOD("get_caret_blink_speed"), &LineEdit::get_caret_blink_speed);
ClassDB::bind_method(D_METHOD("set_max_length", "chars"), &LineEdit::set_max_length);
ClassDB::bind_method(D_METHOD("get_max_length"), &LineEdit::get_max_length);
- ClassDB::bind_method(D_METHOD("append_at_cursor", "text"), &LineEdit::append_at_cursor);
- ClassDB::bind_method(D_METHOD("delete_char_at_cursor"), &LineEdit::delete_char);
+ ClassDB::bind_method(D_METHOD("insert_text_at_caret", "text"), &LineEdit::insert_text_at_caret);
+ ClassDB::bind_method(D_METHOD("delete_char_at_caret"), &LineEdit::delete_char);
ClassDB::bind_method(D_METHOD("delete_text", "from_column", "to_column"), &LineEdit::delete_text);
ClassDB::bind_method(D_METHOD("set_editable", "enabled"), &LineEdit::set_editable);
ClassDB::bind_method(D_METHOD("is_editable"), &LineEdit::is_editable);
@@ -2153,8 +2162,8 @@ void LineEdit::_bind_methods() {
ClassDB::bind_method(D_METHOD("get_right_icon"), &LineEdit::get_right_icon);
ADD_SIGNAL(MethodInfo("text_changed", PropertyInfo(Variant::STRING, "new_text")));
- ADD_SIGNAL(MethodInfo("text_change_rejected"));
- ADD_SIGNAL(MethodInfo("text_entered", 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")));
BIND_ENUM_CONSTANT(ALIGN_LEFT);
BIND_ENUM_CONSTANT(ALIGN_CENTER);
@@ -2193,18 +2202,18 @@ void LineEdit::_bind_methods() {
ADD_PROPERTY(PropertyInfo(Variant::STRING, "text"), "set_text", "get_text");
ADD_PROPERTY(PropertyInfo(Variant::INT, "align", PROPERTY_HINT_ENUM, "Left,Center,Right,Fill"), "set_align", "get_align");
- ADD_PROPERTY(PropertyInfo(Variant::INT, "max_length"), "set_max_length", "get_max_length");
+ ADD_PROPERTY(PropertyInfo(Variant::INT, "max_length", PROPERTY_HINT_RANGE, "0,1000,1,or_greater"), "set_max_length", "get_max_length");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "editable"), "set_editable", "is_editable");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "secret"), "set_secret", "is_secret");
ADD_PROPERTY(PropertyInfo(Variant::STRING, "secret_character"), "set_secret_character", "get_secret_character");
- ADD_PROPERTY(PropertyInfo(Variant::BOOL, "expand_to_text_length"), "set_expand_to_text_length", "get_expand_to_text_length");
+ ADD_PROPERTY(PropertyInfo(Variant::BOOL, "expand_to_text_length"), "set_expand_to_text_length_enabled", "is_expand_to_text_length_enabled");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "context_menu_enabled"), "set_context_menu_enabled", "is_context_menu_enabled");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "virtual_keyboard_enabled"), "set_virtual_keyboard_enabled", "is_virtual_keyboard_enabled");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "clear_button_enabled"), "set_clear_button_enabled", "is_clear_button_enabled");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "shortcut_keys_enabled"), "set_shortcut_keys_enabled", "is_shortcut_keys_enabled");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "selecting_enabled"), "set_selecting_enabled", "is_selecting_enabled");
ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "right_icon", PROPERTY_HINT_RESOURCE_TYPE, "Texture"), "set_right_icon", "get_right_icon");
- ADD_PROPERTY(PropertyInfo(Variant::INT, "text_direction", PROPERTY_HINT_ENUM, "Auto,LTR,RTL,Inherited"), "set_text_direction", "get_text_direction");
+ ADD_PROPERTY(PropertyInfo(Variant::INT, "text_direction", PROPERTY_HINT_ENUM, "Auto,Left-to-Right,Right-to-Left,Inherited"), "set_text_direction", "get_text_direction");
ADD_PROPERTY(PropertyInfo(Variant::STRING, "language"), "set_language", "get_language");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "draw_control_chars"), "set_draw_control_chars", "get_draw_control_chars");
ADD_GROUP("Structured Text", "structured_text_");
@@ -2214,11 +2223,11 @@ void LineEdit::_bind_methods() {
ADD_PROPERTY(PropertyInfo(Variant::STRING, "placeholder_text"), "set_placeholder", "get_placeholder");
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "placeholder_alpha", PROPERTY_HINT_RANGE, "0,1,0.001"), "set_placeholder_alpha", "get_placeholder_alpha");
ADD_GROUP("Caret", "caret_");
- ADD_PROPERTY(PropertyInfo(Variant::BOOL, "caret_blink"), "cursor_set_blink_enabled", "cursor_get_blink_enabled");
- ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "caret_blink_speed", PROPERTY_HINT_RANGE, "0.1,10,0.01"), "cursor_set_blink_speed", "cursor_get_blink_speed");
- ADD_PROPERTY(PropertyInfo(Variant::INT, "caret_position"), "set_cursor_position", "get_cursor_position");
- ADD_PROPERTY(PropertyInfo(Variant::BOOL, "caret_force_displayed"), "cursor_set_force_displayed", "cursor_get_force_displayed");
- ADD_PROPERTY(PropertyInfo(Variant::BOOL, "caret_mid_grapheme"), "set_mid_grapheme_caret_enabled", "get_mid_grapheme_caret_enabled");
+ ADD_PROPERTY(PropertyInfo(Variant::BOOL, "caret_blink"), "set_caret_blink_enabled", "is_caret_blink_enabled");
+ ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "caret_blink_speed", PROPERTY_HINT_RANGE, "0.1,10,0.01"), "set_caret_blink_speed", "get_caret_blink_speed");
+ ADD_PROPERTY(PropertyInfo(Variant::INT, "caret_column", PROPERTY_HINT_RANGE, "0,1000,1,or_greater"), "set_caret_column", "get_caret_column");
+ ADD_PROPERTY(PropertyInfo(Variant::BOOL, "caret_force_displayed"), "set_caret_force_displayed", "is_caret_force_displayed");
+ ADD_PROPERTY(PropertyInfo(Variant::BOOL, "caret_mid_grapheme"), "set_caret_mid_grapheme_enabled", "is_caret_mid_grapheme_enabled");
}
LineEdit::LineEdit() {
@@ -2234,7 +2243,7 @@ LineEdit::LineEdit() {
add_child(caret_blink_timer);
caret_blink_timer->set_wait_time(0.65);
caret_blink_timer->connect("timeout", callable_mp(this, &LineEdit::_toggle_draw_caret));
- cursor_set_blink_enabled(false);
+ set_caret_blink_enabled(false);
menu = memnew(PopupMenu);
add_child(menu);
diff --git a/scene/gui/line_edit.h b/scene/gui/line_edit.h
index ef36377f2e..12fec2f98b 100644
--- a/scene/gui/line_edit.h
+++ b/scene/gui/line_edit.h
@@ -103,9 +103,9 @@ private:
PopupMenu *menu_dir = nullptr;
PopupMenu *menu_ctl = nullptr;
- bool mid_grapheme_caret_enabled = false;
+ bool caret_mid_grapheme_enabled = false;
- int cursor_pos = 0;
+ int caret_column = 0;
int scroll_offset = 0;
int max_length = 0; // 0 for no maximum.
@@ -131,16 +131,16 @@ private:
struct Selection {
int begin = 0;
int end = 0;
- int cursor_start = 0;
+ int start_column = 0;
bool enabled = false;
bool creating = false;
- bool doubleclick = false;
+ bool double_click = false;
bool drag_attempt = false;
uint64_t last_dblclk = 0;
} selection;
struct TextOperation {
- int cursor_pos = 0;
+ int caret_column = 0;
int scroll_offset = 0;
int cached_width = 0;
String text;
@@ -175,12 +175,12 @@ private:
void shift_selection_check_pre(bool);
void shift_selection_check_post(bool);
- void selection_fill_at_cursor();
+ void selection_fill_at_caret();
void set_scroll_offset(int p_pos);
int get_scroll_offset() const;
- void set_cursor_at_pixel_pos(int p_x);
- Vector2i get_cursor_pixel_pos();
+ void set_caret_at_pixel_pos(int p_x);
+ Vector2i get_caret_pixel_pos();
void _reset_caret_blink_timer();
void _toggle_draw_caret();
@@ -191,10 +191,10 @@ private:
void _editor_settings_changed();
void _swap_current_input_direction();
- void _move_cursor_left(bool p_select, bool p_move_by_word = false);
- void _move_cursor_right(bool p_select, bool p_move_by_word = false);
- void _move_cursor_start(bool p_select);
- void _move_cursor_end(bool p_select);
+ void _move_caret_left(bool p_select, bool p_move_by_word = false);
+ void _move_caret_right(bool p_select, bool p_move_by_word = false);
+ void _move_caret_start(bool p_select);
+ void _move_caret_end(bool p_select);
void _backspace(bool p_word = false, bool p_all_to_left = false);
void _delete(bool p_word = false, bool p_all_to_right = false);
@@ -259,26 +259,26 @@ public:
void set_placeholder_alpha(float p_alpha);
float get_placeholder_alpha() const;
- void set_cursor_position(int p_pos);
- int get_cursor_position() const;
+ void set_caret_column(int p_column);
+ int get_caret_column() const;
void set_max_length(int p_max_length);
int get_max_length() const;
- void append_at_cursor(String p_text);
+ void insert_text_at_caret(String p_text);
void clear();
- void set_mid_grapheme_caret_enabled(const bool p_enabled);
- bool get_mid_grapheme_caret_enabled() const;
+ void set_caret_mid_grapheme_enabled(const bool p_enabled);
+ bool is_caret_mid_grapheme_enabled() const;
- bool cursor_get_blink_enabled() const;
- void cursor_set_blink_enabled(const bool p_enabled);
+ bool is_caret_blink_enabled() const;
+ void set_caret_blink_enabled(const bool p_enabled);
- float cursor_get_blink_speed() const;
- void cursor_set_blink_speed(const float p_speed);
+ float get_caret_blink_speed() const;
+ void set_caret_blink_speed(const float p_speed);
- bool cursor_get_force_displayed() const;
- void cursor_set_force_displayed(const bool p_enabled);
+ void set_caret_force_displayed(const bool p_enabled);
+ bool is_caret_force_displayed() const;
void copy_text();
void cut_text();
@@ -297,8 +297,8 @@ public:
virtual Size2 get_minimum_size() const override;
- void set_expand_to_text_length(bool p_enabled);
- bool get_expand_to_text_length() const;
+ void set_expand_to_text_length_enabled(bool p_enabled);
+ bool is_expand_to_text_length_enabled() const;
void set_clear_button_enabled(bool p_enabled);
bool is_clear_button_enabled() const;
diff --git a/scene/gui/link_button.cpp b/scene/gui/link_button.cpp
index 1f7b61e3d1..ee0618a991 100644
--- a/scene/gui/link_button.cpp
+++ b/scene/gui/link_button.cpp
@@ -292,7 +292,7 @@ void LinkButton::_bind_methods() {
BIND_ENUM_CONSTANT(UNDERLINE_MODE_NEVER);
ADD_PROPERTY(PropertyInfo(Variant::STRING, "text"), "set_text", "get_text");
- ADD_PROPERTY(PropertyInfo(Variant::INT, "text_direction", PROPERTY_HINT_ENUM, "Auto,LTR,RTL,Inherited"), "set_text_direction", "get_text_direction");
+ ADD_PROPERTY(PropertyInfo(Variant::INT, "text_direction", PROPERTY_HINT_ENUM, "Auto,Left-to-Right,Right-to-Left,Inherited"), "set_text_direction", "get_text_direction");
ADD_PROPERTY(PropertyInfo(Variant::STRING, "language"), "set_language", "get_language");
ADD_PROPERTY(PropertyInfo(Variant::INT, "underline", PROPERTY_HINT_ENUM, "Always,On Hover,Never"), "set_underline_mode", "get_underline_mode");
ADD_GROUP("Structured Text", "structured_text_");
@@ -301,7 +301,7 @@ void LinkButton::_bind_methods() {
}
LinkButton::LinkButton() {
- text_buf.instance();
+ text_buf.instantiate();
set_focus_mode(FOCUS_NONE);
set_default_cursor_shape(CURSOR_POINTING_HAND);
}
diff --git a/scene/gui/menu_button.cpp b/scene/gui/menu_button.cpp
index 5acc7e808a..1e9baa77fc 100644
--- a/scene/gui/menu_button.cpp
+++ b/scene/gui/menu_button.cpp
@@ -34,6 +34,8 @@
#include "scene/main/window.h"
void MenuButton::_unhandled_key_input(Ref<InputEvent> p_event) {
+ ERR_FAIL_COND(p_event.is_null());
+
if (!_is_focus_owner_in_shorcut_context()) {
return;
}
diff --git a/scene/gui/nine_patch_rect.cpp b/scene/gui/nine_patch_rect.cpp
index 29a38ad5e3..8bf25ac915 100644
--- a/scene/gui/nine_patch_rect.cpp
+++ b/scene/gui/nine_patch_rect.cpp
@@ -30,6 +30,7 @@
#include "nine_patch_rect.h"
+#include "scene/scene_string_names.h"
#include "servers/rendering_server.h"
void NinePatchRect::_notification(int p_what) {
@@ -97,7 +98,7 @@ void NinePatchRect::set_texture(const Ref<Texture2D> &p_tex) {
texture->set_flags(texture->get_flags()&(~Texture::FLAG_REPEAT)); //remove repeat from texture, it looks bad in sprites
*/
minimum_size_changed();
- emit_signal("texture_changed");
+ emit_signal(SceneStringNames::get_singleton()->texture_changed);
}
Ref<Texture2D> NinePatchRect::get_texture() const {
diff --git a/scene/gui/popup_menu.cpp b/scene/gui/popup_menu.cpp
index bfbd46a9f0..2100707d2d 100644
--- a/scene/gui/popup_menu.cpp
+++ b/scene/gui/popup_menu.cpp
@@ -252,6 +252,8 @@ void PopupMenu::_submenu_timeout() {
}
void PopupMenu::_gui_input(const Ref<InputEvent> &p_event) {
+ ERR_FAIL_COND(p_event.is_null());
+
if (p_event->is_action("ui_down") && p_event->is_pressed()) {
int search_from = mouse_over + 1;
if (search_from >= items.size()) {
@@ -1280,16 +1282,16 @@ bool PopupMenu::activate_item_by_event(const Ref<InputEvent> &p_event, bool p_fo
if (code == 0) {
code = k->get_unicode();
}
- if (k->get_control()) {
+ if (k->is_ctrl_pressed()) {
code |= KEY_MASK_CTRL;
}
- if (k->get_alt()) {
+ if (k->is_alt_pressed()) {
code |= KEY_MASK_ALT;
}
- if (k->get_metakey()) {
+ if (k->is_meta_pressed()) {
code |= KEY_MASK_META;
}
- if (k->get_shift()) {
+ if (k->is_shift_pressed()) {
code |= KEY_MASK_SHIFT;
}
}
diff --git a/scene/gui/popup_menu.h b/scene/gui/popup_menu.h
index e4cbe984c9..74718395d3 100644
--- a/scene/gui/popup_menu.h
+++ b/scene/gui/popup_menu.h
@@ -80,8 +80,8 @@ class PopupMenu : public Popup {
}
Item() {
- text_buf.instance();
- accel_text_buf.instance();
+ text_buf.instantiate();
+ accel_text_buf.instantiate();
checkable_type = CHECKABLE_TYPE_NONE;
}
};
diff --git a/scene/gui/range.cpp b/scene/gui/range.cpp
index 86b775e795..4ea1e1eb9f 100644
--- a/scene/gui/range.cpp
+++ b/scene/gui/range.cpp
@@ -30,17 +30,14 @@
#include "range.h"
-String Range::get_configuration_warning() const {
- String warning = Control::get_configuration_warning();
+TypedArray<String> Range::get_configuration_warnings() const {
+ TypedArray<String> warnings = Node::get_configuration_warnings();
if (shared->exp_ratio && shared->min <= 0) {
- if (!warning.is_empty()) {
- warning += "\n\n";
- }
- warning += TTR("If \"Exp Edit\" is enabled, \"Min Value\" must be greater than 0.");
+ warnings.push_back(TTR("If \"Exp Edit\" is enabled, \"Min Value\" must be greater than 0."));
}
- return warning;
+ return warnings;
}
void Range::_value_changed_notify() {
@@ -106,7 +103,7 @@ void Range::set_min(double p_min) {
shared->emit_changed("min");
- update_configuration_warning();
+ update_configuration_warnings();
}
void Range::set_max(double p_max) {
@@ -181,7 +178,6 @@ double Range::get_as_ratio() const {
double v = Math::log(value) / Math::log((double)2);
return CLAMP((v - exp_min) / (exp_max - exp_min), 0, 1);
-
} else {
float value = CLAMP(get_value(), shared->min, shared->max);
return CLAMP((value - get_min()) / (get_max() - get_min()), 0, 1);
@@ -269,7 +265,7 @@ void Range::_bind_methods() {
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "step"), "set_step", "get_step");
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "page"), "set_page", "get_page");
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "value"), "set_value", "get_value");
- ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "ratio", PROPERTY_HINT_RANGE, "0,1,0.01", 0), "set_as_ratio", "get_as_ratio");
+ ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "ratio", PROPERTY_HINT_RANGE, "0,1,0.01", PROPERTY_USAGE_NONE), "set_as_ratio", "get_as_ratio");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "exp_edit"), "set_exp_ratio", "is_ratio_exp");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "rounded"), "set_use_rounded_values", "is_using_rounded_values");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "allow_greater"), "set_allow_greater", "is_greater_allowed");
@@ -287,7 +283,7 @@ bool Range::is_using_rounded_values() const {
void Range::set_exp_ratio(bool p_enable) {
shared->exp_ratio = p_enable;
- update_configuration_warning();
+ update_configuration_warnings();
}
bool Range::is_ratio_exp() const {
diff --git a/scene/gui/range.h b/scene/gui/range.h
index 1072a109c6..7a129e88d6 100644
--- a/scene/gui/range.h
+++ b/scene/gui/range.h
@@ -97,7 +97,7 @@ public:
void share(Range *p_range);
void unshare();
- virtual String get_configuration_warning() const override;
+ TypedArray<String> get_configuration_warnings() const override;
Range();
~Range();
diff --git a/scene/gui/rich_text_effect.h b/scene/gui/rich_text_effect.h
index f2e2823eff..d809fd502f 100644
--- a/scene/gui/rich_text_effect.h
+++ b/scene/gui/rich_text_effect.h
@@ -47,8 +47,8 @@ public:
RichTextEffect();
};
-class CharFXTransform : public Reference {
- GDCLASS(CharFXTransform, Reference);
+class CharFXTransform : public RefCounted {
+ GDCLASS(CharFXTransform, RefCounted);
protected:
static void _bind_methods();
diff --git a/scene/gui/rich_text_label.cpp b/scene/gui/rich_text_label.cpp
index 13b57ece6c..f32ad2144a 100644
--- a/scene/gui/rich_text_label.cpp
+++ b/scene/gui/rich_text_label.cpp
@@ -760,7 +760,7 @@ int RichTextLabel::_draw_line(ItemFrame *p_frame, int p_line, const Vector2 &p_o
//draw_rect(Rect2(p_ofs + off, TS->shaped_text_get_size(rid)), Color(1,0,0), false, 2); //DEBUG_RECTS
- off.y += TS->shaped_text_get_ascent(rid);
+ off.y += TS->shaped_text_get_ascent(rid) + l.text_buf->get_spacing_top();
// Draw inlined objects.
Array objects = TS->shaped_text_get_objects(rid);
for (int i = 0; i < objects.size(); i++) {
@@ -934,6 +934,11 @@ int RichTextLabel::_draw_line(ItemFrame *p_frame, int p_line, const Vector2 &p_o
}
}
+ Vector2 fbg_line_off = off + p_ofs;
+ // Draw background color box
+ Vector2i chr_range = TS->shaped_text_get_range(rid);
+ _draw_fbg_boxes(ci, rid, fbg_line_off, it_from, it_to, chr_range.x, chr_range.y, 0);
+
// Draw main text.
Color selection_fg = get_theme_color("font_selected_color");
Color selection_bg = get_theme_color("selection_color");
@@ -1079,7 +1084,10 @@ int RichTextLabel::_draw_line(ItemFrame *p_frame, int p_line, const Vector2 &p_o
off.x += glyphs[i].advance;
}
}
- off.y += TS->shaped_text_get_descent(rid);
+ // Draw foreground color box
+ _draw_fbg_boxes(ci, rid, fbg_line_off, it_from, it_to, chr_range.x, chr_range.y, 1);
+
+ off.y += TS->shaped_text_get_descent(rid) + l.text_buf->get_spacing_bottom();
}
return line_count;
@@ -1174,7 +1182,7 @@ float RichTextLabel::_find_click_in_line(ItemFrame *p_frame, int p_line, const V
} break;
}
- off.y += TS->shaped_text_get_ascent(rid);
+ off.y += TS->shaped_text_get_ascent(rid) + l.text_buf->get_spacing_top();
Array objects = TS->shaped_text_get_objects(rid);
for (int i = 0; i < objects.size(); i++) {
@@ -1238,7 +1246,7 @@ float RichTextLabel::_find_click_in_line(ItemFrame *p_frame, int p_line, const V
if (rect.has_point(p_click) && !table_hit) {
char_pos = TS->shaped_text_hit_test_position(rid, p_click.x - rect.position.x);
}
- off.y += TS->shaped_text_get_descent(rid);
+ off.y += TS->shaped_text_get_descent(rid) + l.text_buf->get_spacing_bottom();
}
if (char_pos >= 0) {
@@ -1432,10 +1440,11 @@ void RichTextLabel::_notification(int p_what) {
}
} break;
case NOTIFICATION_INTERNAL_PROCESS: {
- float dt = get_process_delta_time();
-
- _update_fx(main, dt);
- update();
+ if (is_visible_in_tree()) {
+ float dt = get_process_delta_time();
+ _update_fx(main, dt);
+ update();
+ }
}
}
}
@@ -1469,6 +1478,8 @@ Control::CursorShape RichTextLabel::get_cursor_shape(const Point2 &p_pos) const
}
void RichTextLabel::_gui_input(Ref<InputEvent> p_event) {
+ ERR_FAIL_COND(p_event.is_null());
+
Ref<InputEventMouseButton> b = p_event;
if (b.is_valid()) {
@@ -1480,7 +1491,7 @@ void RichTextLabel::_gui_input(Ref<InputEvent> p_event) {
}
if (b->get_button_index() == MOUSE_BUTTON_LEFT) {
- if (b->is_pressed() && !b->is_doubleclick()) {
+ if (b->is_pressed() && !b->is_double_click()) {
scroll_updated = false;
ItemFrame *c_frame = nullptr;
int c_line = 0;
@@ -1512,8 +1523,8 @@ void RichTextLabel::_gui_input(Ref<InputEvent> p_event) {
}
}
}
- } else if (b->is_pressed() && b->is_doubleclick() && selection.enabled) {
- //doubleclick: select word
+ } else if (b->is_pressed() && b->is_double_click() && selection.enabled) {
+ //double_click: select word
ItemFrame *c_frame = nullptr;
int c_line = 0;
@@ -1547,7 +1558,7 @@ void RichTextLabel::_gui_input(Ref<InputEvent> p_event) {
} else if (!b->is_pressed()) {
selection.click_item = nullptr;
- if (!b->is_doubleclick() && !scroll_updated) {
+ if (!b->is_double_click() && !scroll_updated) {
Item *c_item = nullptr;
bool outside = true;
@@ -2033,6 +2044,36 @@ bool RichTextLabel::_find_meta(Item *p_item, Variant *r_meta, ItemMeta **r_item)
return false;
}
+Color RichTextLabel::_find_bgcolor(Item *p_item) {
+ Item *item = p_item;
+
+ while (item) {
+ if (item->type == ITEM_BGCOLOR) {
+ ItemBGColor *color = static_cast<ItemBGColor *>(item);
+ return color->color;
+ }
+
+ item = item->parent;
+ }
+
+ return Color(0, 0, 0, 0);
+}
+
+Color RichTextLabel::_find_fgcolor(Item *p_item) {
+ Item *item = p_item;
+
+ while (item) {
+ if (item->type == ITEM_FGCOLOR) {
+ ItemFGColor *color = static_cast<ItemFGColor *>(item);
+ return color->color;
+ }
+
+ item = item->parent;
+ }
+
+ return Color(0, 0, 0, 0);
+}
+
bool RichTextLabel::_find_layout_subitem(Item *from, Item *to) {
if (from && from != to) {
if (from->type != ITEM_FONT && from->type != ITEM_COLOR && from->type != ITEM_UNDERLINE && from->type != ITEM_STRIKETHROUGH) {
@@ -2229,18 +2270,22 @@ void RichTextLabel::_remove_item(Item *p_item, const int p_line, const int p_sub
int size = p_item->subitems.size();
if (size == 0) {
p_item->parent->subitems.erase(p_item);
+ // If a newline was erased, all lines AFTER the newline need to be decremented.
if (p_item->type == ITEM_NEWLINE) {
current_frame->lines.remove(p_line);
- for (int i = p_subitem_line; i < current->subitems.size(); i++) {
- if (current->subitems[i]->line > 0) {
+ for (int i = 0; i < current->subitems.size(); i++) {
+ if (current->subitems[i]->line > p_subitem_line) {
current->subitems[i]->line--;
}
}
}
} else {
+ // First, remove all child items for the provided item.
for (int i = 0; i < size; i++) {
_remove_item(p_item->subitems.front()->get(), p_line, p_subitem_line);
}
+ // Then remove the provided item itself.
+ p_item->parent->subitems.erase(p_item);
}
}
@@ -2300,21 +2345,23 @@ bool RichTextLabel::remove_line(const int p_line) {
return false;
}
- int i = 0;
- while (i < current->subitems.size() && current->subitems[i]->line < p_line) {
- i++;
+ // Remove all subitems with the same line as that provided.
+ Vector<int> subitem_indices_to_remove;
+ for (int i = 0; i < current->subitems.size(); i++) {
+ if (current->subitems[i]->line == p_line) {
+ subitem_indices_to_remove.push_back(i);
+ }
}
- bool was_newline = false;
- while (i < current->subitems.size()) {
- was_newline = current->subitems[i]->type == ITEM_NEWLINE;
- _remove_item(current->subitems[i], current->subitems[i]->line, p_line);
- if (was_newline) {
- break;
- }
+ bool had_newline = false;
+ // Reverse for loop to remove items from the end first.
+ for (int i = subitem_indices_to_remove.size() - 1; i >= 0; i--) {
+ int subitem_idx = subitem_indices_to_remove[i];
+ had_newline = had_newline || current->subitems[subitem_idx]->type == ITEM_NEWLINE;
+ _remove_item(current->subitems[subitem_idx], current->subitems[subitem_idx]->line, p_line);
}
- if (!was_newline) {
+ if (!had_newline) {
current_frame->lines.remove(p_line);
if (current_frame->lines.size() == 0) {
current_frame->lines.resize(1);
@@ -2326,6 +2373,7 @@ bool RichTextLabel::remove_line(const int p_line) {
}
main->first_invalid_line = 0; // p_line ???
+ update();
return true;
}
@@ -2536,6 +2584,22 @@ void RichTextLabel::push_rainbow(float p_saturation, float p_value, float p_freq
_add_item(item, true);
}
+void RichTextLabel::push_bgcolor(const Color &p_color) {
+ ERR_FAIL_COND(current->type == ITEM_TABLE);
+ ItemBGColor *item = memnew(ItemBGColor);
+
+ item->color = p_color;
+ _add_item(item, true);
+}
+
+void RichTextLabel::push_fgcolor(const Color &p_color) {
+ ERR_FAIL_COND(current->type == ITEM_TABLE);
+ ItemFGColor *item = memnew(ItemFGColor);
+
+ item->color = p_color;
+ _add_item(item, true);
+}
+
void RichTextLabel::push_customfx(Ref<RichTextEffect> p_custom_effect, Dictionary p_environment) {
ItemCustomFX *item = memnew(ItemCustomFX);
item->custom_effect = p_custom_effect;
@@ -3342,6 +3406,23 @@ Error RichTextLabel::append_bbcode(const String &p_bbcode) {
pos = brk_end + 1;
tag_stack.push_front("rainbow");
set_process_internal(true);
+
+ } else if (tag.begins_with("bgcolor=")) {
+ String color_str = tag.substr(8, tag.length());
+ Color color = Color::from_string(color_str, base_color);
+
+ push_bgcolor(color);
+ pos = brk_end + 1;
+ tag_stack.push_front("bgcolor");
+
+ } else if (tag.begins_with("fgcolor=")) {
+ String color_str = tag.substr(8, tag.length());
+ Color color = Color::from_string(color_str, base_color);
+
+ push_fgcolor(color);
+ pos = brk_end + 1;
+ tag_stack.push_front("fgcolor");
+
} else {
Vector<String> &expr = split_tag_block;
if (expr.size() < 1) {
@@ -3442,7 +3523,30 @@ void RichTextLabel::set_selection_enabled(bool p_enabled) {
}
}
-bool RichTextLabel::_search_line(ItemFrame *p_frame, int p_line, const String &p_string, Item *p_from, Item *p_to) {
+bool RichTextLabel::_search_table(ItemTable *p_table, List<Item *>::Element *p_from, const String &p_string, bool p_reverse_search) {
+ List<Item *>::Element *E = p_from;
+ while (E != nullptr) {
+ ERR_CONTINUE(E->get()->type != ITEM_FRAME); // Children should all be frames.
+ ItemFrame *frame = static_cast<ItemFrame *>(E->get());
+ if (p_reverse_search) {
+ for (int i = frame->lines.size() - 1; i >= 0; i--) {
+ if (_search_line(frame, i, p_string, -1, p_reverse_search)) {
+ return true;
+ }
+ }
+ } else {
+ for (int i = 0; i < frame->lines.size(); i++) {
+ if (_search_line(frame, i, p_string, 0, p_reverse_search)) {
+ return true;
+ }
+ }
+ }
+ E = p_reverse_search ? E->prev() : E->next();
+ }
+ return false;
+}
+
+bool RichTextLabel::_search_line(ItemFrame *p_frame, int p_line, const String &p_string, int p_char_idx, bool p_reverse_search) {
ERR_FAIL_COND_V(p_frame == nullptr, false);
ERR_FAIL_COND_V(p_line < 0 || p_line >= p_frame->lines.size(), false);
@@ -3464,24 +3568,23 @@ bool RichTextLabel::_search_line(ItemFrame *p_frame, int p_line, const String &p
} break;
case ITEM_TABLE: {
ItemTable *table = static_cast<ItemTable *>(it);
- int idx = 0;
- for (List<Item *>::Element *E = table->subitems.front(); E; E = E->next()) {
- ERR_CONTINUE(E->get()->type != ITEM_FRAME); // Children should all be frames.
- ItemFrame *frame = static_cast<ItemFrame *>(E->get());
-
- for (int i = 0; i < frame->lines.size(); i++) {
- if (_search_line(frame, i, p_string, p_from, p_to)) {
- return true;
- }
- }
- idx++;
+ List<Item *>::Element *E = p_reverse_search ? table->subitems.back() : table->subitems.front();
+ if (_search_table(table, E, p_string, p_reverse_search)) {
+ return true;
}
} break;
default:
break;
}
}
- int sp = text.findn(p_string, 0);
+
+ int sp = -1;
+ if (p_reverse_search) {
+ sp = text.rfindn(p_string, p_char_idx);
+ } else {
+ sp = text.findn(p_string, p_char_idx);
+ }
+
if (sp != -1) {
selection.from_frame = p_frame;
selection.from_line = p_line;
@@ -3489,8 +3592,8 @@ bool RichTextLabel::_search_line(ItemFrame *p_frame, int p_line, const String &p
selection.from_char = sp;
selection.to_frame = p_frame;
selection.to_line = p_line;
- selection.to_item = _get_item_at_pos(l.from, it_to, sp + p_string.length() - 1);
- selection.to_char = sp + p_string.length() - 1;
+ selection.to_item = _get_item_at_pos(l.from, it_to, sp + p_string.length());
+ selection.to_char = sp + p_string.length();
selection.active = true;
return true;
}
@@ -3501,23 +3604,81 @@ bool RichTextLabel::_search_line(ItemFrame *p_frame, int p_line, const String &p
bool RichTextLabel::search(const String &p_string, bool p_from_selection, bool p_search_previous) {
ERR_FAIL_COND_V(!selection.enabled, false);
+ if (p_string.size() == 0) {
+ selection.active = false;
+ return false;
+ }
+
+ int char_idx = p_search_previous ? -1 : 0;
+ int current_line = 0;
+ int ending_line = main->lines.size() - 1;
if (p_from_selection && selection.active) {
- for (int i = 0; i < main->lines.size(); i++) {
- if (_search_line(main, i, p_string, selection.from_item, selection.to_item)) {
- update();
- return true;
- }
+ // First check to see if other results exist in current line
+ char_idx = p_search_previous ? selection.from_char - 1 : selection.to_char;
+ if (!(p_search_previous && char_idx < 0) &&
+ _search_line(selection.from_frame, selection.from_line, p_string, char_idx, p_search_previous)) {
+ scroll_to_line(selection.from_frame->line + selection.from_line);
+ update();
+ return true;
}
- } else {
- for (int i = 0; i < main->lines.size(); i++) {
- if (_search_line(main, i, p_string, nullptr, nullptr)) {
- update();
- return true;
+ char_idx = p_search_previous ? -1 : 0;
+
+ // Next, check to see if the current search result is in a table
+ if (selection.from_frame->parent != nullptr && selection.from_frame->parent->type == ITEM_TABLE) {
+ // Find last search result in table
+ ItemTable *parent_table = static_cast<ItemTable *>(selection.from_frame->parent);
+ List<Item *>::Element *parent_element = p_search_previous ? parent_table->subitems.back() : parent_table->subitems.front();
+
+ while (parent_element->get() != selection.from_frame) {
+ parent_element = p_search_previous ? parent_element->prev() : parent_element->next();
+ ERR_FAIL_COND_V(parent_element == nullptr, false);
+ }
+
+ // Search remainder of table
+ if (!(p_search_previous && parent_element == parent_table->subitems.front()) &&
+ parent_element != parent_table->subitems.back()) {
+ parent_element = p_search_previous ? parent_element->prev() : parent_element->next(); // Don't want to search current item
+ ERR_FAIL_COND_V(parent_element == nullptr, false);
+
+ // Search for next element
+ if (_search_table(parent_table, parent_element, p_string, p_search_previous)) {
+ scroll_to_line(selection.from_frame->line + selection.from_line);
+ update();
+ return true;
+ }
}
}
+
+ ending_line = selection.from_frame->line + selection.from_line;
+ current_line = p_search_previous ? ending_line - 1 : ending_line + 1;
+ } else if (p_search_previous) {
+ current_line = ending_line;
+ ending_line = 0;
}
- return false;
+ // Search remainder of the file
+ while (current_line != ending_line) {
+ // Wrap around
+ if (current_line < 0) {
+ current_line = main->lines.size() - 1;
+ } else if (current_line >= main->lines.size()) {
+ current_line = 0;
+ }
+
+ if (_search_line(main, current_line, p_string, char_idx, p_search_previous)) {
+ scroll_to_line(current_line);
+ update();
+ return true;
+ }
+ p_search_previous ? current_line-- : current_line++;
+ }
+
+ if (p_from_selection && selection.active) {
+ // Check contents of selection
+ return _search_line(main, current_line, p_string, char_idx, p_search_previous);
+ } else {
+ return false;
+ }
}
String RichTextLabel::_get_line_text(ItemFrame *p_frame, int p_line, Selection p_selection) const {
@@ -3756,7 +3917,9 @@ void RichTextLabel::set_effects(const Vector<Variant> &effects) {
custom_effects.push_back(effect);
}
- parse_bbcode(bbcode);
+ if ((bbcode != "") && use_bbcode) {
+ parse_bbcode(bbcode);
+ }
}
Vector<Variant> RichTextLabel::get_effects() {
@@ -3773,7 +3936,9 @@ void RichTextLabel::install_effect(const Variant effect) {
if (rteffect.is_valid()) {
custom_effects.push_back(effect);
- parse_bbcode(bbcode);
+ if ((bbcode != "") && use_bbcode) {
+ parse_bbcode(bbcode);
+ }
}
}
@@ -3824,6 +3989,8 @@ void RichTextLabel::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_cell_size_override", "min_size", "max_size"), &RichTextLabel::set_cell_size_override);
ClassDB::bind_method(D_METHOD("set_cell_padding", "padding"), &RichTextLabel::set_cell_padding);
ClassDB::bind_method(D_METHOD("push_cell"), &RichTextLabel::push_cell);
+ ClassDB::bind_method(D_METHOD("push_fgcolor", "fgcolor"), &RichTextLabel::push_fgcolor);
+ ClassDB::bind_method(D_METHOD("push_bgcolor", "bgcolor"), &RichTextLabel::push_bgcolor);
ClassDB::bind_method(D_METHOD("pop"), &RichTextLabel::pop);
ClassDB::bind_method(D_METHOD("clear"), &RichTextLabel::clear);
@@ -3918,9 +4085,9 @@ void RichTextLabel::_bind_methods() {
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "selection_enabled"), "set_selection_enabled", "is_selection_enabled");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "override_selected_font_color"), "set_override_selected_font_color", "is_overriding_selected_font_color");
- ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "custom_effects", PROPERTY_HINT_ARRAY_TYPE, "RichTextEffect", (PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_SCRIPT_VARIABLE)), "set_effects", "get_effects");
+ ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "custom_effects", PROPERTY_HINT_ARRAY_TYPE, vformat("%s/%s:%s", Variant::OBJECT, PROPERTY_HINT_RESOURCE_TYPE, "RichTextEffect"), (PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_SCRIPT_VARIABLE)), "set_effects", "get_effects");
- ADD_PROPERTY(PropertyInfo(Variant::INT, "text_direction", PROPERTY_HINT_ENUM, "Auto,LTR,RTL,Inherited"), "set_text_direction", "get_text_direction");
+ ADD_PROPERTY(PropertyInfo(Variant::INT, "text_direction", PROPERTY_HINT_ENUM, "Auto,Left-to-Right,Right-to-Left,Inherited"), "set_text_direction", "get_text_direction");
ADD_PROPERTY(PropertyInfo(Variant::STRING, "language"), "set_language", "get_language");
ADD_GROUP("Structured Text", "structured_text_");
@@ -3962,6 +4129,8 @@ void RichTextLabel::_bind_methods() {
BIND_ENUM_CONSTANT(ITEM_WAVE);
BIND_ENUM_CONSTANT(ITEM_TORNADO);
BIND_ENUM_CONSTANT(ITEM_RAINBOW);
+ BIND_ENUM_CONSTANT(ITEM_BGCOLOR);
+ BIND_ENUM_CONSTANT(ITEM_FGCOLOR);
BIND_ENUM_CONSTANT(ITEM_META);
BIND_ENUM_CONSTANT(ITEM_DROPCAP);
BIND_ENUM_CONSTANT(ITEM_CUSTOMFX);
@@ -3999,19 +4168,80 @@ void RichTextLabel::set_fixed_size_to_width(int p_width) {
}
Size2 RichTextLabel::get_minimum_size() const {
- Size2 size(0, 0);
+ Ref<StyleBox> style = get_theme_stylebox("normal");
+ Size2 size = style->get_minimum_size();
+
if (fixed_width != -1) {
- size.x = fixed_width;
+ size.x += fixed_width;
}
if (fixed_width != -1 || fit_content_height) {
const_cast<RichTextLabel *>(this)->_validate_line_caches(main);
- size.y = get_content_height();
+ size.y += get_content_height();
}
return size;
}
+void RichTextLabel::_draw_fbg_boxes(RID p_ci, RID p_rid, Vector2 line_off, Item *it_from, Item *it_to, int start, int end, int fbg_flag) {
+ Vector2i fbg_index = Vector2i(end, start);
+ Color last_color = Color(0, 0, 0, 0);
+ bool draw_box = false;
+ // Draw a box based on color tags associated with glyphs
+ for (int i = start; i < end; i++) {
+ Item *it = _get_item_at_pos(it_from, it_to, i);
+ Color color = Color(0, 0, 0, 0);
+
+ if (fbg_flag == 0) {
+ color = _find_bgcolor(it);
+ } else {
+ color = _find_fgcolor(it);
+ }
+
+ bool change_to_color = ((color.a > 0) && ((last_color.a - 0.0) < 0.01));
+ bool change_from_color = (((color.a - 0.0) < 0.01) && (last_color.a > 0.0));
+ bool change_color = (((color.a > 0) == (last_color.a > 0)) && (color != last_color));
+
+ if (change_to_color) {
+ fbg_index.x = MIN(i, fbg_index.x);
+ fbg_index.y = MAX(i, fbg_index.y);
+ }
+
+ if (change_from_color || change_color) {
+ fbg_index.x = MIN(i, fbg_index.x);
+ fbg_index.y = MAX(i, fbg_index.y);
+ draw_box = true;
+ }
+
+ if (draw_box) {
+ Vector<Vector2> sel = TS->shaped_text_get_selection(p_rid, fbg_index.x, fbg_index.y);
+ for (int j = 0; j < sel.size(); j++) {
+ Vector2 rect_off = line_off + Vector2(sel[j].x, -TS->shaped_text_get_ascent(p_rid));
+ Vector2 rect_size = Vector2(sel[j].y - sel[j].x, TS->shaped_text_get_size(p_rid).y);
+ RenderingServer::get_singleton()->canvas_item_add_rect(p_ci, Rect2(rect_off, rect_size), last_color);
+ }
+ fbg_index = Vector2i(end, start);
+ draw_box = false;
+ }
+
+ if (change_color) {
+ fbg_index.x = MIN(i, fbg_index.x);
+ fbg_index.y = MAX(i, fbg_index.y);
+ }
+
+ last_color = color;
+ }
+
+ if (last_color.a > 0) {
+ Vector<Vector2> sel = TS->shaped_text_get_selection(p_rid, fbg_index.x, end);
+ for (int i = 0; i < sel.size(); i++) {
+ Vector2 rect_off = line_off + Vector2(sel[i].x, -TS->shaped_text_get_ascent(p_rid));
+ Vector2 rect_size = Vector2(sel[i].y - sel[i].x, TS->shaped_text_get_size(p_rid).y);
+ RenderingServer::get_singleton()->canvas_item_add_rect(p_ci, Rect2(rect_off, rect_size), last_color);
+ }
+ }
+}
+
Ref<RichTextEffect> RichTextLabel::_get_custom_effect_by_code(String p_bbcode_identifier) {
for (int i = 0; i < custom_effects.size(); i++) {
if (!custom_effects[i].is_valid()) {
diff --git a/scene/gui/rich_text_label.h b/scene/gui/rich_text_label.h
index e3e457d1f2..999d8b05fd 100644
--- a/scene/gui/rich_text_label.h
+++ b/scene/gui/rich_text_label.h
@@ -75,6 +75,8 @@ public:
ITEM_WAVE,
ITEM_TORNADO,
ITEM_RAINBOW,
+ ITEM_BGCOLOR,
+ ITEM_FGCOLOR,
ITEM_META,
ITEM_DROPCAP,
ITEM_CUSTOMFX
@@ -100,7 +102,7 @@ private:
int char_offset = 0;
int char_count = 0;
- Line() { text_buf.instance(); }
+ Line() { text_buf.instantiate(); }
};
struct Item {
@@ -307,13 +309,23 @@ private:
ItemRainbow() { type = ITEM_RAINBOW; }
};
+ struct ItemBGColor : public Item {
+ Color color;
+ ItemBGColor() { type = ITEM_BGCOLOR; }
+ };
+
+ struct ItemFGColor : public Item {
+ Color color;
+ ItemFGColor() { type = ITEM_FGCOLOR; }
+ };
+
struct ItemCustomFX : public ItemFX {
Ref<CharFXTransform> char_fx_transform;
Ref<RichTextEffect> custom_effect;
ItemCustomFX() {
type = ITEM_CUSTOMFX;
- char_fx_transform.instance();
+ char_fx_transform.instantiate();
}
virtual ~ItemCustomFX() {
@@ -392,7 +404,8 @@ private:
void _find_click(ItemFrame *p_frame, const Point2i &p_click, ItemFrame **r_click_frame = nullptr, int *r_click_line = nullptr, Item **r_click_item = nullptr, int *r_click_char = nullptr, bool *r_outside = nullptr);
String _get_line_text(ItemFrame *p_frame, int p_line, Selection p_sel) const;
- bool _search_line(ItemFrame *p_frame, int p_line, const String &p_string, Item *p_from, Item *p_to);
+ bool _search_line(ItemFrame *p_frame, int p_line, const String &p_string, int p_char_idx, bool p_reverse_search);
+ bool _search_table(ItemTable *p_table, List<Item *>::Element *p_from, const String &p_string, bool p_reverse_search);
void _shape_line(ItemFrame *p_frame, int p_line, const Ref<Font> &p_base_font, int p_base_font_size, int p_width, int *r_char_offset);
void _resize_line(ItemFrame *p_frame, int p_line, const Ref<Font> &p_base_font, int p_base_font_size, int p_width);
@@ -421,6 +434,8 @@ private:
bool _find_underline(Item *p_item);
bool _find_strikethrough(Item *p_item);
bool _find_meta(Item *p_item, Variant *r_meta, ItemMeta **r_item = nullptr);
+ Color _find_bgcolor(Item *p_item);
+ Color _find_fgcolor(Item *p_item);
bool _find_layout_subitem(Item *from, Item *to);
void _fetch_item_fx_stack(Item *p_item, Vector<ItemFX *> &r_stack);
@@ -436,6 +451,8 @@ private:
Ref<RichTextEffect> _get_custom_effect_by_code(String p_bbcode_identifier);
virtual Dictionary parse_expressions_for_values(Vector<String> p_expressions);
+ void _draw_fbg_boxes(RID p_ci, RID p_rid, Vector2 line_off, Item *it_from, Item *it_to, int start, int end, int fbg_flag);
+
bool use_bbcode = false;
String bbcode;
@@ -473,6 +490,8 @@ public:
void push_wave(float p_frequency, float p_amplitude);
void push_tornado(float p_frequency, float p_radius);
void push_rainbow(float p_saturation, float p_value, float p_frequency);
+ void push_bgcolor(const Color &p_color);
+ void push_fgcolor(const Color &p_color);
void push_customfx(Ref<RichTextEffect> p_custom_effect, Dictionary p_environment);
void set_table_column_expand(int p_column, bool p_expand, int p_ratio = 1);
void set_cell_row_background_color(const Color &p_odd_row_bg, const Color &p_even_row_bg);
diff --git a/scene/gui/scroll_bar.cpp b/scene/gui/scroll_bar.cpp
index a56bf15507..62276e3af0 100644
--- a/scene/gui/scroll_bar.cpp
+++ b/scene/gui/scroll_bar.cpp
@@ -42,6 +42,8 @@ void ScrollBar::set_can_focus_by_default(bool p_can_focus) {
}
void ScrollBar::_gui_input(Ref<InputEvent> p_event) {
+ ERR_FAIL_COND(p_event.is_null());
+
Ref<InputEventMouseMotion> m = p_event;
if (!m.is_valid() || drag.active) {
emit_signal("scrolling");
diff --git a/scene/gui/scroll_container.cpp b/scene/gui/scroll_container.cpp
index 90a528482f..177f146b6a 100644
--- a/scene/gui/scroll_container.cpp
+++ b/scene/gui/scroll_container.cpp
@@ -32,10 +32,6 @@
#include "core/os/os.h"
#include "scene/main/window.h"
-bool ScrollContainer::clips_input() const {
- return true;
-}
-
Size2 ScrollContainer::get_minimum_size() const {
Ref<StyleBox> sb = get_theme_stylebox("bg");
Size2 min_size;
@@ -88,6 +84,8 @@ void ScrollContainer::_cancel_drag() {
}
void ScrollContainer::_gui_input(const Ref<InputEvent> &p_gui_input) {
+ ERR_FAIL_COND(p_gui_input.is_null());
+
double prev_v_scroll = v_scroll->get_value();
double prev_h_scroll = h_scroll->get_value();
@@ -96,7 +94,7 @@ void ScrollContainer::_gui_input(const Ref<InputEvent> &p_gui_input) {
if (mb.is_valid()) {
if (mb->get_button_index() == MOUSE_BUTTON_WHEEL_UP && mb->is_pressed()) {
// only horizontal is enabled, scroll horizontally
- if (h_scroll->is_visible() && (!v_scroll->is_visible() || mb->get_shift())) {
+ if (h_scroll->is_visible() && (!v_scroll->is_visible() || mb->is_shift_pressed())) {
h_scroll->set_value(h_scroll->get_value() - h_scroll->get_page() / 8 * mb->get_factor());
} else if (v_scroll->is_visible_in_tree()) {
v_scroll->set_value(v_scroll->get_value() - v_scroll->get_page() / 8 * mb->get_factor());
@@ -105,7 +103,7 @@ void ScrollContainer::_gui_input(const Ref<InputEvent> &p_gui_input) {
if (mb->get_button_index() == MOUSE_BUTTON_WHEEL_DOWN && mb->is_pressed()) {
// only horizontal is enabled, scroll horizontally
- if (h_scroll->is_visible() && (!v_scroll->is_visible() || mb->get_shift())) {
+ if (h_scroll->is_visible() && (!v_scroll->is_visible() || mb->is_shift_pressed())) {
h_scroll->set_value(h_scroll->get_value() + h_scroll->get_page() / 8 * mb->get_factor());
} else if (v_scroll->is_visible()) {
v_scroll->set_value(v_scroll->get_value() + v_scroll->get_page() / 8 * mb->get_factor());
@@ -236,32 +234,25 @@ void ScrollContainer::_update_scrollbar_position() {
_updating_scrollbars = false;
}
-void ScrollContainer::_ensure_focused_visible(Control *p_control) {
- if (!follow_focus) {
- return;
+void ScrollContainer::_gui_focus_changed(Control *p_control) {
+ if (follow_focus && is_ancestor_of(p_control)) {
+ ensure_control_visible(p_control);
}
+}
- if (is_a_parent_of(p_control)) {
- Rect2 global_rect = get_global_rect();
- Rect2 other_rect = p_control->get_global_rect();
- float right_margin = 0.0;
- if (v_scroll->is_visible()) {
- right_margin += v_scroll->get_size().x;
- }
- float bottom_margin = 0.0;
- if (h_scroll->is_visible()) {
- bottom_margin += h_scroll->get_size().y;
- }
+void ScrollContainer::ensure_control_visible(Control *p_control) {
+ ERR_FAIL_COND_MSG(!is_ancestor_of(p_control), "Must be an ancestor of the control.");
- float diff = MAX(MIN(other_rect.position.y, global_rect.position.y), other_rect.position.y + other_rect.size.y - global_rect.size.y + bottom_margin);
- set_v_scroll(get_v_scroll() + (diff - global_rect.position.y));
- if (is_layout_rtl()) {
- diff = MAX(MIN(other_rect.position.x, global_rect.position.x), other_rect.position.x + other_rect.size.x - global_rect.size.x);
- } else {
- diff = MAX(MIN(other_rect.position.x, global_rect.position.x), other_rect.position.x + other_rect.size.x - global_rect.size.x + right_margin);
- }
- set_h_scroll(get_h_scroll() + (diff - global_rect.position.x));
- }
+ Rect2 global_rect = get_global_rect();
+ Rect2 other_rect = p_control->get_global_rect();
+ float right_margin = v_scroll->is_visible() ? v_scroll->get_size().x : 0.0f;
+ float bottom_margin = h_scroll->is_visible() ? h_scroll->get_size().y : 0.0f;
+
+ Vector2 diff = Vector2(MAX(MIN(other_rect.position.x, global_rect.position.x), other_rect.position.x + other_rect.size.x - global_rect.size.x + (!is_layout_rtl() ? right_margin : 0.0f)),
+ MAX(MIN(other_rect.position.y, global_rect.position.y), other_rect.position.y + other_rect.size.y - global_rect.size.y + bottom_margin));
+
+ set_h_scroll(get_h_scroll() + (diff.x - global_rect.position.x));
+ set_v_scroll(get_v_scroll() + (diff.y - global_rect.position.y));
}
void ScrollContainer::_update_dimensions() {
@@ -297,7 +288,7 @@ void ScrollContainer::_update_dimensions() {
child_max_size.x = MAX(child_max_size.x, minsize.x);
child_max_size.y = MAX(child_max_size.y, minsize.y);
- Rect2 r = Rect2(-scroll, minsize);
+ Rect2 r = Rect2(-Size2(get_h_scroll(), get_v_scroll()), minsize);
if (!scroll_h || (!h_scroll->is_visible_in_tree() && c->get_h_size_flags() & SIZE_EXPAND)) {
r.position.x = 0;
if (c->get_h_size_flags() & SIZE_EXPAND) {
@@ -332,7 +323,7 @@ void ScrollContainer::_notification(int p_what) {
};
if (p_what == NOTIFICATION_READY) {
- get_viewport()->connect("gui_focus_changed", callable_mp(this, &ScrollContainer::_ensure_focused_visible));
+ get_viewport()->connect("gui_focus_changed", callable_mp(this, &ScrollContainer::_gui_focus_changed));
_update_dimensions();
}
@@ -432,40 +423,16 @@ void ScrollContainer::update_scrollbars() {
Size2 min = child_max_size;
- bool hide_scroll_v = !scroll_v || min.height <= size.height;
- bool hide_scroll_h = !scroll_h || min.width <= size.width;
-
- v_scroll->set_max(min.height);
- if (hide_scroll_v) {
- v_scroll->set_page(size.height);
- v_scroll->hide();
- scroll.y = 0;
- } else {
- v_scroll->show();
- if (hide_scroll_h) {
- v_scroll->set_page(size.height);
- } else {
- v_scroll->set_page(size.height - hmin.height);
- }
-
- scroll.y = v_scroll->get_value();
- }
+ bool hide_scroll_h = !scroll_h || min.width <= size.width || !h_scroll_visible;
+ bool hide_scroll_v = !scroll_v || min.height <= size.height || !v_scroll_visible;
h_scroll->set_max(min.width);
- if (hide_scroll_h) {
- h_scroll->set_page(size.width);
- h_scroll->hide();
- scroll.x = 0;
- } else {
- h_scroll->show();
- if (hide_scroll_v) {
- h_scroll->set_page(size.width);
- } else {
- h_scroll->set_page(size.width - vmin.width);
- }
+ h_scroll->set_page(size.width - (hide_scroll_v ? 0 : vmin.width));
+ h_scroll->set_visible(!hide_scroll_h);
- scroll.x = h_scroll->get_value();
- }
+ v_scroll->set_max(min.height);
+ v_scroll->set_page(size.height - (hide_scroll_h ? 0 : hmin.height));
+ v_scroll->set_visible(!hide_scroll_v);
// Avoid scrollbar overlapping.
h_scroll->set_anchor_and_offset(SIDE_RIGHT, ANCHOR_END, hide_scroll_v ? 0 : -vmin.width);
@@ -473,13 +440,28 @@ void ScrollContainer::update_scrollbars() {
}
void ScrollContainer::_scroll_moved(float) {
- scroll.x = h_scroll->get_value();
- scroll.y = v_scroll->get_value();
queue_sort();
-
update();
};
+void ScrollContainer::set_h_scroll(int p_pos) {
+ h_scroll->set_value(p_pos);
+ _cancel_drag();
+}
+
+int ScrollContainer::get_h_scroll() const {
+ return h_scroll->get_value();
+}
+
+void ScrollContainer::set_v_scroll(int p_pos) {
+ v_scroll->set_value(p_pos);
+ _cancel_drag();
+}
+
+int ScrollContainer::get_v_scroll() const {
+ return v_scroll->get_value();
+}
+
void ScrollContainer::set_enable_h_scroll(bool p_enable) {
if (scroll_h == p_enable) {
return;
@@ -508,22 +490,30 @@ bool ScrollContainer::is_v_scroll_enabled() const {
return scroll_v;
}
-int ScrollContainer::get_v_scroll() const {
- return v_scroll->get_value();
+void ScrollContainer::set_h_scroll_visible(bool p_visible) {
+ if (h_scroll_visible == p_visible) {
+ return;
+ }
+
+ h_scroll_visible = p_visible;
+ update_scrollbars();
}
-void ScrollContainer::set_v_scroll(int p_pos) {
- v_scroll->set_value(p_pos);
- _cancel_drag();
+bool ScrollContainer::is_h_scroll_visible() const {
+ return h_scroll_visible;
}
-int ScrollContainer::get_h_scroll() const {
- return h_scroll->get_value();
+void ScrollContainer::set_v_scroll_visible(bool p_visible) {
+ if (v_scroll_visible == p_visible) {
+ return;
+ }
+
+ v_scroll_visible = p_visible;
+ update_scrollbars();
}
-void ScrollContainer::set_h_scroll(int p_pos) {
- h_scroll->set_value(p_pos);
- _cancel_drag();
+bool ScrollContainer::is_v_scroll_visible() const {
+ return v_scroll_visible;
}
int ScrollContainer::get_deadzone() const {
@@ -542,8 +532,8 @@ void ScrollContainer::set_follow_focus(bool p_follow) {
follow_focus = p_follow;
}
-String ScrollContainer::get_configuration_warning() const {
- String warning = Container::get_configuration_warning();
+TypedArray<String> ScrollContainer::get_configuration_warnings() const {
+ TypedArray<String> warnings = Container::get_configuration_warnings();
int found = 0;
@@ -563,12 +553,10 @@ String ScrollContainer::get_configuration_warning() const {
}
if (found != 1) {
- if (!warning.is_empty()) {
- warning += "\n\n";
- }
- warning += TTR("ScrollContainer is intended to work with a single child control.\nUse a container as child (VBox, HBox, etc.), or a Control and set the custom minimum size manually.");
+ warnings.push_back(TTR("ScrollContainer is intended to work with a single child control.\nUse a container as child (VBox, HBox, etc.), or a Control and set the custom minimum size manually."));
}
- return warning;
+
+ return warnings;
}
HScrollBar *ScrollContainer::get_h_scrollbar() {
@@ -581,22 +569,35 @@ VScrollBar *ScrollContainer::get_v_scrollbar() {
void ScrollContainer::_bind_methods() {
ClassDB::bind_method(D_METHOD("_gui_input"), &ScrollContainer::_gui_input);
- ClassDB::bind_method(D_METHOD("set_enable_h_scroll", "enable"), &ScrollContainer::set_enable_h_scroll);
- ClassDB::bind_method(D_METHOD("is_h_scroll_enabled"), &ScrollContainer::is_h_scroll_enabled);
- ClassDB::bind_method(D_METHOD("set_enable_v_scroll", "enable"), &ScrollContainer::set_enable_v_scroll);
- ClassDB::bind_method(D_METHOD("is_v_scroll_enabled"), &ScrollContainer::is_v_scroll_enabled);
ClassDB::bind_method(D_METHOD("_update_scrollbar_position"), &ScrollContainer::_update_scrollbar_position);
+
ClassDB::bind_method(D_METHOD("set_h_scroll", "value"), &ScrollContainer::set_h_scroll);
ClassDB::bind_method(D_METHOD("get_h_scroll"), &ScrollContainer::get_h_scroll);
+
ClassDB::bind_method(D_METHOD("set_v_scroll", "value"), &ScrollContainer::set_v_scroll);
ClassDB::bind_method(D_METHOD("get_v_scroll"), &ScrollContainer::get_v_scroll);
+
+ ClassDB::bind_method(D_METHOD("set_enable_h_scroll", "enable"), &ScrollContainer::set_enable_h_scroll);
+ ClassDB::bind_method(D_METHOD("is_h_scroll_enabled"), &ScrollContainer::is_h_scroll_enabled);
+
+ ClassDB::bind_method(D_METHOD("set_enable_v_scroll", "enable"), &ScrollContainer::set_enable_v_scroll);
+ ClassDB::bind_method(D_METHOD("is_v_scroll_enabled"), &ScrollContainer::is_v_scroll_enabled);
+
+ ClassDB::bind_method(D_METHOD("set_h_scroll_visible", "visible"), &ScrollContainer::set_h_scroll_visible);
+ ClassDB::bind_method(D_METHOD("is_h_scroll_visible"), &ScrollContainer::is_h_scroll_visible);
+
+ ClassDB::bind_method(D_METHOD("set_v_scroll_visible", "visible"), &ScrollContainer::set_v_scroll_visible);
+ ClassDB::bind_method(D_METHOD("is_v_scroll_visible"), &ScrollContainer::is_v_scroll_visible);
+
ClassDB::bind_method(D_METHOD("set_deadzone", "deadzone"), &ScrollContainer::set_deadzone);
ClassDB::bind_method(D_METHOD("get_deadzone"), &ScrollContainer::get_deadzone);
+
ClassDB::bind_method(D_METHOD("set_follow_focus", "enabled"), &ScrollContainer::set_follow_focus);
ClassDB::bind_method(D_METHOD("is_following_focus"), &ScrollContainer::is_following_focus);
ClassDB::bind_method(D_METHOD("get_h_scrollbar"), &ScrollContainer::get_h_scrollbar);
ClassDB::bind_method(D_METHOD("get_v_scrollbar"), &ScrollContainer::get_v_scrollbar);
+ ClassDB::bind_method(D_METHOD("ensure_control_visible", "control"), &ScrollContainer::ensure_control_visible);
ADD_SIGNAL(MethodInfo("scroll_started"));
ADD_SIGNAL(MethodInfo("scroll_ended"));
@@ -604,10 +605,12 @@ void ScrollContainer::_bind_methods() {
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "follow_focus"), "set_follow_focus", "is_following_focus");
ADD_GROUP("Scroll", "scroll_");
- ADD_PROPERTY(PropertyInfo(Variant::BOOL, "scroll_horizontal_enabled"), "set_enable_h_scroll", "is_h_scroll_enabled");
ADD_PROPERTY(PropertyInfo(Variant::INT, "scroll_horizontal"), "set_h_scroll", "get_h_scroll");
- ADD_PROPERTY(PropertyInfo(Variant::BOOL, "scroll_vertical_enabled"), "set_enable_v_scroll", "is_v_scroll_enabled");
ADD_PROPERTY(PropertyInfo(Variant::INT, "scroll_vertical"), "set_v_scroll", "get_v_scroll");
+ ADD_PROPERTY(PropertyInfo(Variant::BOOL, "scroll_horizontal_enabled"), "set_enable_h_scroll", "is_h_scroll_enabled");
+ ADD_PROPERTY(PropertyInfo(Variant::BOOL, "scroll_vertical_enabled"), "set_enable_v_scroll", "is_v_scroll_enabled");
+ ADD_PROPERTY(PropertyInfo(Variant::BOOL, "scroll_horizontal_visible"), "set_h_scroll_visible", "is_h_scroll_visible");
+ ADD_PROPERTY(PropertyInfo(Variant::BOOL, "scroll_vertical_visible"), "set_v_scroll_visible", "is_v_scroll_visible");
ADD_PROPERTY(PropertyInfo(Variant::INT, "scroll_deadzone"), "set_deadzone", "get_deadzone");
GLOBAL_DEF("gui/common/default_scroll_deadzone", 0);
diff --git a/scene/gui/scroll_container.h b/scene/gui/scroll_container.h
index 9d3ce39345..4733fdabca 100644
--- a/scene/gui/scroll_container.h
+++ b/scene/gui/scroll_container.h
@@ -42,7 +42,6 @@ class ScrollContainer : public Container {
VScrollBar *v_scroll;
Size2 child_max_size;
- Size2 scroll;
void update_scrollbars();
@@ -50,16 +49,17 @@ class ScrollContainer : public Container {
Vector2 drag_accum;
Vector2 drag_from;
Vector2 last_drag_accum;
- float last_drag_time = 0.0;
- float time_since_motion = 0.0;
+ float time_since_motion = 0.0f;
bool drag_touching = false;
bool drag_touching_deaccel = false;
- bool click_handled = false;
bool beyond_deadzone = false;
bool scroll_h = true;
bool scroll_v = true;
+ bool h_scroll_visible = true;
+ bool v_scroll_visible = true;
+
int deadzone = 0;
bool follow_focus = false;
@@ -69,6 +69,7 @@ protected:
Size2 get_minimum_size() const override;
void _gui_input(const Ref<InputEvent> &p_gui_input);
+ void _gui_focus_changed(Control *p_control);
void _update_dimensions();
void _notification(int p_what);
@@ -77,14 +78,13 @@ protected:
bool _updating_scrollbars = false;
void _update_scrollbar_position();
- void _ensure_focused_visible(Control *p_node);
public:
- int get_v_scroll() const;
- void set_v_scroll(int p_pos);
-
- int get_h_scroll() const;
void set_h_scroll(int p_pos);
+ int get_h_scroll() const;
+
+ void set_v_scroll(int p_pos);
+ int get_v_scroll() const;
void set_enable_h_scroll(bool p_enable);
bool is_h_scroll_enabled() const;
@@ -92,6 +92,12 @@ public:
void set_enable_v_scroll(bool p_enable);
bool is_v_scroll_enabled() const;
+ void set_h_scroll_visible(bool p_visible);
+ bool is_h_scroll_visible() const;
+
+ void set_v_scroll_visible(bool p_visible);
+ bool is_v_scroll_visible() const;
+
int get_deadzone() const;
void set_deadzone(int p_deadzone);
@@ -100,10 +106,9 @@ public:
HScrollBar *get_h_scrollbar();
VScrollBar *get_v_scrollbar();
+ void ensure_control_visible(Control *p_control);
- virtual bool clips_input() const override;
-
- virtual String get_configuration_warning() const override;
+ TypedArray<String> get_configuration_warnings() const override;
ScrollContainer();
};
diff --git a/scene/gui/shortcut.cpp b/scene/gui/shortcut.cpp
index cbbcf9e069..962c6dcc60 100644
--- a/scene/gui/shortcut.cpp
+++ b/scene/gui/shortcut.cpp
@@ -42,7 +42,7 @@ Ref<InputEvent> Shortcut::get_shortcut() const {
}
bool Shortcut::is_shortcut(const Ref<InputEvent> &p_event) const {
- return shortcut.is_valid() && shortcut->shortcut_match(p_event);
+ return shortcut.is_valid() && shortcut->is_match(p_event, true);
}
String Shortcut::get_as_text() const {
diff --git a/scene/gui/slider.cpp b/scene/gui/slider.cpp
index 7f1d19a87a..5947f3b99e 100644
--- a/scene/gui/slider.cpp
+++ b/scene/gui/slider.cpp
@@ -46,6 +46,8 @@ Size2 Slider::get_minimum_size() const {
}
void Slider::_gui_input(Ref<InputEvent> p_event) {
+ ERR_FAIL_COND(p_event.is_null());
+
if (!editable) {
return;
}
@@ -170,7 +172,7 @@ void Slider::_notification(int p_what) {
int widget_width = style->get_minimum_size().width + style->get_center_size().width;
float areasize = size.height - grabber->get_size().height;
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_size().height / 2), Size2i(widget_width, areasize * ratio + grabber->get_size().width / 2)));
+ grabber_area->draw(ci, Rect2i(Point2i((size.width - widget_width) / 2, size.height - areasize * ratio - grabber->get_size().height / 2), Size2i(widget_width, areasize * ratio + grabber->get_size().height / 2)));
if (ticks > 1) {
int grabber_offset = (grabber->get_size().height / 2 - tick->get_height() / 2);
diff --git a/scene/gui/spin_box.cpp b/scene/gui/spin_box.cpp
index 50b25fa7b4..941dd30057 100644
--- a/scene/gui/spin_box.cpp
+++ b/scene/gui/spin_box.cpp
@@ -50,9 +50,9 @@ void SpinBox::_value_changed(double) {
line_edit->set_text(value);
}
-void SpinBox::_text_entered(const String &p_string) {
+void SpinBox::_text_submitted(const String &p_string) {
Ref<Expression> expr;
- expr.instance();
+ expr.instantiate();
String num = TS->parse_number(p_string);
// Ignore the prefix and suffix in the expression
@@ -100,6 +100,8 @@ void SpinBox::_release_mouse() {
}
void SpinBox::_gui_input(const Ref<InputEvent> &p_event) {
+ ERR_FAIL_COND(p_event.is_null());
+
if (!is_editable()) {
return;
}
@@ -138,6 +140,8 @@ void SpinBox::_gui_input(const Ref<InputEvent> &p_event) {
accept_event();
}
} break;
+ default:
+ break;
}
}
@@ -170,7 +174,7 @@ void SpinBox::_line_edit_focus_exit() {
return;
}
- _text_entered(line_edit->get_text());
+ _text_submitted(line_edit->get_text());
}
inline void SpinBox::_adjust_width_for_icon(const Ref<Texture2D> &icon) {
@@ -249,7 +253,7 @@ bool SpinBox::is_editable() const {
}
void SpinBox::apply() {
- _text_entered(line_edit->get_text());
+ _text_submitted(line_edit->get_text());
}
void SpinBox::_bind_methods() {
@@ -281,7 +285,7 @@ SpinBox::SpinBox() {
line_edit->set_align(LineEdit::ALIGN_LEFT);
//connect("value_changed",this,"_value_changed");
- line_edit->connect("text_entered", callable_mp(this, &SpinBox::_text_entered), Vector<Variant>(), CONNECT_DEFERRED);
+ line_edit->connect("text_submitted", callable_mp(this, &SpinBox::_text_submitted), Vector<Variant>(), CONNECT_DEFERRED);
line_edit->connect("focus_exited", callable_mp(this, &SpinBox::_line_edit_focus_exit), Vector<Variant>(), CONNECT_DEFERRED);
line_edit->connect("gui_input", callable_mp(this, &SpinBox::_line_edit_input));
diff --git a/scene/gui/spin_box.h b/scene/gui/spin_box.h
index e116adb64c..fb10379296 100644
--- a/scene/gui/spin_box.h
+++ b/scene/gui/spin_box.h
@@ -45,7 +45,7 @@ class SpinBox : public Range {
void _range_click_timeout();
void _release_mouse();
- void _text_entered(const String &p_string);
+ void _text_submitted(const String &p_string);
virtual void _value_changed(double) override;
String prefix;
String suffix;
diff --git a/scene/gui/split_container.cpp b/scene/gui/split_container.cpp
index c80120f87d..9796b32c6b 100644
--- a/scene/gui/split_container.cpp
+++ b/scene/gui/split_container.cpp
@@ -38,7 +38,7 @@ Control *SplitContainer::_getch(int p_idx) const {
for (int i = 0; i < get_child_count(); i++) {
Control *c = Object::cast_to<Control>(get_child(i));
- if (!c || !c->is_visible_in_tree()) {
+ if (!c || !c->is_visible()) {
continue;
}
if (c->is_set_as_top_level()) {
@@ -207,6 +207,8 @@ void SplitContainer::_notification(int p_what) {
}
void SplitContainer::_gui_input(const Ref<InputEvent> &p_event) {
+ ERR_FAIL_COND(p_event.is_null());
+
if (collapsed || !_getch(0) || !_getch(1) || dragger_visibility != DRAGGER_VISIBLE) {
return;
}
@@ -351,7 +353,7 @@ void SplitContainer::_bind_methods() {
ADD_PROPERTY(PropertyInfo(Variant::INT, "split_offset"), "set_split_offset", "get_split_offset");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "collapsed"), "set_collapsed", "is_collapsed");
- ADD_PROPERTY(PropertyInfo(Variant::INT, "dragger_visibility", PROPERTY_HINT_ENUM, "Visible,Hidden,Hidden & Collapsed"), "set_dragger_visibility", "get_dragger_visibility");
+ ADD_PROPERTY(PropertyInfo(Variant::INT, "dragger_visibility", PROPERTY_HINT_ENUM, "Visible,Hidden,Hidden and Collapsed"), "set_dragger_visibility", "get_dragger_visibility");
BIND_ENUM_CONSTANT(DRAGGER_VISIBLE);
BIND_ENUM_CONSTANT(DRAGGER_HIDDEN);
diff --git a/scene/gui/subviewport_container.cpp b/scene/gui/subviewport_container.cpp
index 8ffdd269a4..bfc7e29f9c 100644
--- a/scene/gui/subviewport_container.cpp
+++ b/scene/gui/subviewport_container.cpp
@@ -140,6 +140,8 @@ void SubViewportContainer::_notification(int p_what) {
}
void SubViewportContainer::_input(const Ref<InputEvent> &p_event) {
+ ERR_FAIL_COND(p_event.is_null());
+
if (Engine::get_singleton()->is_editor_hint()) {
return;
}
@@ -165,6 +167,8 @@ void SubViewportContainer::_input(const Ref<InputEvent> &p_event) {
}
void SubViewportContainer::_unhandled_input(const Ref<InputEvent> &p_event) {
+ ERR_FAIL_COND(p_event.is_null());
+
if (Engine::get_singleton()->is_editor_hint()) {
return;
}
diff --git a/scene/gui/tab_container.cpp b/scene/gui/tab_container.cpp
index 1e31f9e206..133966013b 100644
--- a/scene/gui/tab_container.cpp
+++ b/scene/gui/tab_container.cpp
@@ -72,6 +72,8 @@ int TabContainer::_get_top_margin() const {
}
void TabContainer::_gui_input(const Ref<InputEvent> &p_event) {
+ ERR_FAIL_COND(p_event.is_null());
+
Ref<InputEventMouseButton> mb = p_event;
Popup *popup = get_popup();
@@ -392,6 +394,7 @@ void TabContainer::_notification(int p_what) {
Vector<int> tab_widths;
for (int i = first_tab_cache; i < tabs.size(); i++) {
if (get_tab_hidden(i)) {
+ tab_widths.push_back(0);
continue;
}
int tab_width = _get_tab_width(i);
@@ -583,7 +586,7 @@ void TabContainer::_refresh_texts() {
Control *control = Object::cast_to<Control>(tabs[i]);
String text = control->has_meta("_tab_name") ? String(tr(String(control->get_meta("_tab_name")))) : String(tr(control->get_name()));
Ref<TextLine> name;
- name.instance();
+ name.instantiate();
name->set_direction(rtl ? TextServer::DIRECTION_RTL : TextServer::DIRECTION_LTR);
name->add_string(text, font, font_size, Dictionary(), TranslationServer::get_singleton()->get_tool_locale());
text_buf.push_back(name);
diff --git a/scene/gui/tabs.cpp b/scene/gui/tabs.cpp
index 4a7285a154..6f1cff9ec8 100644
--- a/scene/gui/tabs.cpp
+++ b/scene/gui/tabs.cpp
@@ -83,11 +83,16 @@ Size2 Tabs::get_minimum_size() const {
}
}
- ms.width = 0; //TODO: should make this optional
+ if (clip_tabs) {
+ ms.width = 0;
+ }
+
return ms;
}
void Tabs::_gui_input(const Ref<InputEvent> &p_event) {
+ ERR_FAIL_COND(p_event.is_null());
+
Ref<InputEventMouseMotion> mm = p_event;
if (mm.is_valid()) {
@@ -105,10 +110,10 @@ void Tabs::_gui_input(const Ref<InputEvent> &p_event) {
highlight_arrow = 0;
}
} else {
- int limit = get_size().width - incr->get_width() - decr->get_width();
- if (pos.x > limit + decr->get_width()) {
+ int limit_minus_buttons = get_size().width - incr->get_width() - decr->get_width();
+ if (pos.x > limit_minus_buttons + decr->get_width()) {
highlight_arrow = 1;
- } else if (pos.x > limit) {
+ } else if (pos.x > limit_minus_buttons) {
highlight_arrow = 0;
}
}
@@ -122,7 +127,7 @@ void Tabs::_gui_input(const Ref<InputEvent> &p_event) {
Ref<InputEventMouseButton> mb = p_event;
if (mb.is_valid()) {
- if (mb->is_pressed() && mb->get_button_index() == MOUSE_BUTTON_WHEEL_UP && !mb->get_command()) {
+ if (mb->is_pressed() && mb->get_button_index() == MOUSE_BUTTON_WHEEL_UP && !mb->is_command_pressed()) {
if (scrolling_enabled && buttons_visible) {
if (offset > 0) {
offset--;
@@ -131,7 +136,7 @@ void Tabs::_gui_input(const Ref<InputEvent> &p_event) {
}
}
- if (mb->is_pressed() && mb->get_button_index() == MOUSE_BUTTON_WHEEL_DOWN && !mb->get_command()) {
+ if (mb->is_pressed() && mb->get_button_index() == MOUSE_BUTTON_WHEEL_DOWN && !mb->is_command_pressed()) {
if (scrolling_enabled && buttons_visible) {
if (missing_right) {
offset++;
@@ -251,6 +256,7 @@ void Tabs::_notification(int p_what) {
_update_cache();
update();
} break;
+ case NOTIFICATION_THEME_CHANGED:
case NOTIFICATION_TRANSLATION_CHANGED: {
for (int i = 0; i < tabs.size(); ++i) {
_shape(i);
@@ -305,7 +311,8 @@ void Tabs::_notification(int p_what) {
Ref<Texture2D> incr_hl = get_theme_icon("increment_highlight");
Ref<Texture2D> decr_hl = get_theme_icon("decrement_highlight");
- int limit = get_size().width - incr->get_size().width - decr->get_size().width;
+ int limit = get_size().width;
+ int limit_minus_buttons = get_size().width - incr->get_width() - decr->get_width();
missing_right = false;
@@ -328,7 +335,8 @@ void Tabs::_notification(int p_what) {
col = font_unselected_color;
}
- if (w + lsize > limit) {
+ int new_width = w + lsize;
+ if (new_width > limit || (i < tabs.size() - 1 && new_width > limit_minus_buttons)) { // For the last tab, we accept if the tab covers the buttons.
max_drawn_tab = i - 1;
missing_right = true;
break;
@@ -459,15 +467,15 @@ void Tabs::_notification(int p_what) {
}
} else {
if (offset > 0) {
- draw_texture(highlight_arrow == 0 ? decr_hl : decr, Point2(limit, vofs));
+ draw_texture(highlight_arrow == 0 ? decr_hl : decr, Point2(limit_minus_buttons, vofs));
} else {
- draw_texture(decr, Point2(limit, vofs), Color(1, 1, 1, 0.5));
+ draw_texture(decr, Point2(limit_minus_buttons, vofs), Color(1, 1, 1, 0.5));
}
if (missing_right) {
- draw_texture(highlight_arrow == 1 ? incr_hl : incr, Point2(limit + decr->get_size().width, vofs));
+ draw_texture(highlight_arrow == 1 ? incr_hl : incr, Point2(limit_minus_buttons + decr->get_size().width, vofs));
} else {
- draw_texture(incr, Point2(limit + decr->get_size().width, vofs), Color(1, 1, 1, 0.5));
+ draw_texture(incr, Point2(limit_minus_buttons + decr->get_size().width, vofs), Color(1, 1, 1, 0.5));
}
}
@@ -666,7 +674,7 @@ void Tabs::_update_cache() {
Ref<StyleBox> tab_selected = get_theme_stylebox("tab_selected");
Ref<Texture2D> incr = get_theme_icon("increment");
Ref<Texture2D> decr = get_theme_icon("decrement");
- int limit = get_size().width - incr->get_width() - decr->get_width();
+ int limit_minus_buttons = get_size().width - incr->get_width() - decr->get_width();
int w = 0;
int mw = 0;
@@ -686,7 +694,7 @@ void Tabs::_update_cache() {
}
int m_width = min_width;
if (count_resize > 0) {
- m_width = MAX((limit - size_fixed) / count_resize, min_width);
+ m_width = MAX((limit_minus_buttons - size_fixed) / count_resize, min_width);
}
for (int i = offset; i < tabs.size(); i++) {
Ref<StyleBox> sb;
@@ -699,7 +707,7 @@ void Tabs::_update_cache() {
}
int lsize = tabs[i].size_cache;
int slen = tabs[i].size_text;
- if (min_width > 0 && mw > limit && i != current) {
+ if (min_width > 0 && mw > limit_minus_buttons && i != current) {
if (lsize > m_width) {
slen = m_width - (sb->get_margin(SIDE_LEFT) + sb->get_margin(SIDE_RIGHT));
if (tabs[i].icon.is_valid()) {
@@ -735,7 +743,7 @@ void Tabs::add_tab(const String &p_str, const Ref<Texture2D> &p_icon) {
Tab t;
t.text = p_str;
t.xl_text = tr(p_str);
- t.text_buf.instance();
+ t.text_buf.instantiate();
t.text_buf->set_direction(is_layout_rtl() ? TextServer::DIRECTION_RTL : TextServer::DIRECTION_LTR);
t.text_buf->add_string(t.xl_text, get_theme_font("font"), get_theme_font_size("font_size"), Dictionary(), TranslationServer::get_singleton()->get_tool_locale());
t.icon = p_icon;
@@ -909,6 +917,19 @@ Tabs::TabAlign Tabs::get_tab_align() const {
return tab_align;
}
+void Tabs::set_clip_tabs(bool p_clip_tabs) {
+ if (clip_tabs == p_clip_tabs) {
+ return;
+ }
+ clip_tabs = p_clip_tabs;
+ update();
+ minimum_size_changed();
+}
+
+bool Tabs::get_clip_tabs() const {
+ return clip_tabs;
+}
+
void Tabs::move_tab(int from, int to) {
if (from == to) {
return;
@@ -975,7 +996,8 @@ void Tabs::_ensure_no_over_offset() {
Ref<Texture2D> incr = get_theme_icon("increment");
Ref<Texture2D> decr = get_theme_icon("decrement");
- int limit = get_size().width - incr->get_width() - decr->get_width();
+ int limit = get_size().width;
+ int limit_minus_buttons = get_size().width - incr->get_width() - decr->get_width();
while (offset > 0) {
int total_w = 0;
@@ -983,7 +1005,7 @@ void Tabs::_ensure_no_over_offset() {
total_w += tabs[i].size_cache;
}
- if (total_w < limit) {
+ if ((buttons_visible && total_w < limit_minus_buttons) || total_w < limit) { // For the last tab, we accept if the tab covers the buttons.
offset--;
update();
} else {
@@ -1014,9 +1036,12 @@ void Tabs::ensure_tab_visible(int p_idx) {
int prev_offset = offset;
Ref<Texture2D> incr = get_theme_icon("increment");
Ref<Texture2D> decr = get_theme_icon("decrement");
- int limit = get_size().width - incr->get_width() - decr->get_width();
+ int limit = get_size().width;
+ int limit_minus_buttons = get_size().width - incr->get_width() - decr->get_width();
+
for (int i = offset; i <= p_idx; i++) {
- if (tabs[i].ofs_cache + tabs[i].size_cache > limit) {
+ int total_w = tabs[i].ofs_cache + tabs[i].size_cache;
+ if (total_w > limit || (buttons_visible && total_w > limit_minus_buttons)) {
offset++;
}
}
@@ -1105,6 +1130,8 @@ void Tabs::_bind_methods() {
ClassDB::bind_method(D_METHOD("add_tab", "title", "icon"), &Tabs::add_tab, DEFVAL(""), DEFVAL(Ref<Texture2D>()));
ClassDB::bind_method(D_METHOD("set_tab_align", "align"), &Tabs::set_tab_align);
ClassDB::bind_method(D_METHOD("get_tab_align"), &Tabs::get_tab_align);
+ ClassDB::bind_method(D_METHOD("set_clip_tabs", "clip_tabs"), &Tabs::set_clip_tabs);
+ ClassDB::bind_method(D_METHOD("get_clip_tabs"), &Tabs::get_clip_tabs);
ClassDB::bind_method(D_METHOD("get_tab_offset"), &Tabs::get_tab_offset);
ClassDB::bind_method(D_METHOD("get_offset_buttons_visible"), &Tabs::get_offset_buttons_visible);
ClassDB::bind_method(D_METHOD("ensure_tab_visible", "idx"), &Tabs::ensure_tab_visible);
@@ -1131,6 +1158,7 @@ void Tabs::_bind_methods() {
ADD_PROPERTY(PropertyInfo(Variant::INT, "current_tab", PROPERTY_HINT_RANGE, "-1,4096,1", PROPERTY_USAGE_EDITOR), "set_current_tab", "get_current_tab");
ADD_PROPERTY(PropertyInfo(Variant::INT, "tab_align", PROPERTY_HINT_ENUM, "Left,Center,Right"), "set_tab_align", "get_tab_align");
+ ADD_PROPERTY(PropertyInfo(Variant::BOOL, "clip_tabs"), "set_clip_tabs", "get_clip_tabs");
ADD_PROPERTY(PropertyInfo(Variant::INT, "tab_close_display_policy", PROPERTY_HINT_ENUM, "Show Never,Show Active Only,Show Always"), "set_tab_close_display_policy", "get_tab_close_display_policy");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "scrolling_enabled"), "set_scrolling_enabled", "get_scrolling_enabled");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "drag_to_rearrange_enabled"), "set_drag_to_rearrange_enabled", "get_drag_to_rearrange_enabled");
diff --git a/scene/gui/tabs.h b/scene/gui/tabs.h
index 86877f4d80..61c9a5d96a 100644
--- a/scene/gui/tabs.h
+++ b/scene/gui/tabs.h
@@ -85,6 +85,7 @@ private:
int previous = 0;
int _get_top_margin() const;
TabAlign tab_align = ALIGN_CENTER;
+ bool clip_tabs = true;
int rb_hover = -1;
bool rb_pressing = false;
@@ -148,6 +149,9 @@ public:
void set_tab_align(TabAlign p_align);
TabAlign get_tab_align() const;
+ void set_clip_tabs(bool p_clip_tabs);
+ bool get_clip_tabs() const;
+
void move_tab(int from, int to);
void set_tab_close_display_policy(CloseButtonDisplayPolicy p_policy);
diff --git a/scene/gui/text_edit.cpp b/scene/gui/text_edit.cpp
index 74c530f1b0..6f96b530a6 100644
--- a/scene/gui/text_edit.cpp
+++ b/scene/gui/text_edit.cpp
@@ -102,14 +102,6 @@ static char32_t _get_right_pair_symbol(char32_t c) {
return 0;
}
-static int _find_first_non_whitespace_column_of_line(const String &line) {
- int left = 0;
- while (left < line.length() && _is_whitespace(line[left])) {
- left++;
- }
- return left;
-}
-
///////////////////////////////////////////////////////////////////////////////
void TextEdit::Text::set_font(const Ref<Font> &p_font) {
@@ -120,8 +112,12 @@ void TextEdit::Text::set_font_size(int p_font_size) {
font_size = p_font_size;
}
-void TextEdit::Text::set_indent_size(int p_indent_size) {
- indent_size = p_indent_size;
+void TextEdit::Text::set_tab_size(int p_tab_size) {
+ tab_size = p_tab_size;
+}
+
+int TextEdit::Text::get_tab_size() const {
+ return tab_size;
}
void TextEdit::Text::set_font_features(const Dictionary &p_features) {
@@ -137,8 +133,11 @@ void TextEdit::Text::set_draw_control_chars(bool p_draw_control_chars) {
draw_control_chars = p_draw_control_chars;
}
-int TextEdit::Text::get_line_width(int p_line) const {
+int TextEdit::Text::get_line_width(int p_line, int p_wrap_index) const {
ERR_FAIL_INDEX_V(p_line, text.size(), 0);
+ if (p_wrap_index != -1) {
+ return text[p_line].data_buf->get_line_width(p_wrap_index);
+ }
return text[p_line].data_buf->get_size().x;
}
@@ -201,9 +200,9 @@ void TextEdit::Text::invalidate_cache(int p_line, int p_column, const String &p_
}
// Apply tab align.
- if (indent_size > 0) {
+ if (tab_size > 0) {
Vector<float> tabs;
- tabs.push_back(font->get_char_size(' ', 0, font_size).width * indent_size);
+ tabs.push_back(font->get_char_size(' ', 0, font_size).width * tab_size);
text.write[p_line].data_buf->tab_align(tabs);
}
}
@@ -211,9 +210,9 @@ void TextEdit::Text::invalidate_cache(int p_line, int p_column, const String &p_
void TextEdit::Text::invalidate_all_lines() {
for (int i = 0; i < text.size(); i++) {
text.write[i].data_buf->set_width(width);
- if (indent_size > 0) {
+ if (tab_size > 0) {
Vector<float> tabs;
- tabs.push_back(font->get_char_size(' ', 0, font_size).width * indent_size);
+ tabs.push_back(font->get_char_size(' ', 0, font_size).width * tab_size);
text.write[i].data_buf->tab_align(tabs);
}
}
@@ -253,7 +252,6 @@ void TextEdit::Text::set(int p_line, const String &p_text, const Vector<Vector2i
void TextEdit::Text::insert(int p_at, const String &p_text, const Vector<Vector2i> &p_bidi_override) {
Line line;
line.gutters.resize(gutter_count);
- line.marked = false;
line.hidden = false;
line.data = p_text;
line.bidi_override = p_bidi_override;
@@ -802,9 +800,6 @@ void TextEdit::_notification(int p_what) {
}
}
- bool is_cursor_line_visible = false;
- Point2 cursor_pos;
-
// Get the highlighted words.
String highlighted_text = get_selection_text();
@@ -823,7 +818,7 @@ void TextEdit::_notification(int p_what) {
if (draw_minimap) {
int minimap_visible_lines = _get_minimap_visible_rows();
int minimap_line_height = (minimap_char_size.y + minimap_line_spacing);
- int minimap_tab_size = minimap_char_size.x * indent_size;
+ int minimap_tab_size = minimap_char_size.x * text.get_tab_size();
// calculate viewport size and y offset
int viewport_height = (draw_amount - 1) * minimap_line_height;
@@ -867,6 +862,8 @@ void TextEdit::_notification(int p_what) {
Dictionary color_map = _get_line_syntax_highlighting(minimap_line);
+ Color line_background_color = text.get_line_background_color(minimap_line);
+ line_background_color.a *= 0.6;
Color current_color = cache.font_color;
if (readonly) {
current_color = cache.font_readonly_color;
@@ -901,6 +898,12 @@ void TextEdit::_notification(int p_what) {
} else {
RenderingServer::get_singleton()->canvas_item_add_rect(ci, Rect2((xmargin_end + 2), i * 3, cache.minimap_width, 2), cache.current_line_color);
}
+ } else if (line_background_color != Color(0, 0, 0, 0)) {
+ if (rtl) {
+ RenderingServer::get_singleton()->canvas_item_add_rect(ci, Rect2(size.width - (xmargin_end + 2) - cache.minimap_width, i * 3, cache.minimap_width, 2), line_background_color);
+ } else {
+ RenderingServer::get_singleton()->canvas_item_add_rect(ci, Rect2((xmargin_end + 2), i * 3, cache.minimap_width, 2), line_background_color);
+ }
}
Color previous_color;
@@ -980,6 +983,8 @@ void TextEdit::_notification(int p_what) {
}
// draw main text
+ cursor.visible = false;
+ const int caret_wrap_index = get_cursor_wrap_index();
int row_height = get_row_height();
int line = first_visible_line;
for (int i = 0; i < draw_amount; i++) {
@@ -1048,11 +1053,11 @@ void TextEdit::_notification(int p_what) {
break;
}
- if (text.is_marked(line)) {
+ if (text.get_line_background_color(line) != Color(0, 0, 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), cache.mark_color);
+ 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));
} else {
- RenderingServer::get_singleton()->canvas_item_add_rect(ci, Rect2(xmargin_beg + ofs_x, ofs_y, xmargin_end - xmargin_beg, row_height), cache.mark_color);
+ 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));
}
}
@@ -1105,7 +1110,7 @@ void TextEdit::_notification(int p_what) {
}
Ref<TextLine> tl;
- tl.instance();
+ tl.instantiate();
tl->add_string(text, cache.font, cache.font_size);
int yofs = ofs_y + (row_height - tl->get_size().y) / 2;
@@ -1180,7 +1185,8 @@ void TextEdit::_notification(int p_what) {
if (rect.position.x < xmargin_beg) {
rect.size.x -= (xmargin_beg - rect.position.x);
rect.position.x = xmargin_beg;
- } else if (rect.position.x + rect.size.x > xmargin_end) {
+ }
+ if (rect.position.x + rect.size.x > xmargin_end) {
rect.size.x = xmargin_end - rect.position.x;
}
draw_rect(rect, cache.selection_color, true);
@@ -1259,7 +1265,6 @@ void TextEdit::_notification(int p_what) {
}
}
- const int line_top_offset_y = ofs_y;
ofs_y += (row_height - text_height) / 2;
const Vector<TextServer::Glyph> visual = TS->shaped_text_get_glyphs(rid);
@@ -1348,7 +1353,8 @@ void TextEdit::_notification(int p_what) {
}
}
- if (line_wrap_index == line_wrap_amount && is_folded(line)) {
+ // is_line_folded
+ if (line_wrap_index == line_wrap_amount && line < text.size() - 1 && is_line_hidden(line + 1)) {
int xofs = char_ofs + char_margin + ofs_x + (cache.folded_eol_icon->get_width() / 2);
if (xofs >= xmargin_beg && xofs < xmargin_end) {
int yofs = (text_height - cache.folded_eol_icon->get_height()) / 2 - ldata->get_line_ascent(line_wrap_index);
@@ -1364,9 +1370,9 @@ void TextEdit::_notification(int p_what) {
#else
int caret_width = 1;
#endif
- if (!clipped && cursor.line == line && ((line_wrap_index == line_wrap_amount) || (cursor.column != TS->shaped_text_get_range(rid).y))) {
- is_cursor_line_visible = true;
- cursor_pos.y = line_top_offset_y;
+
+ if (!clipped && cursor.line == line && line_wrap_index == caret_wrap_index) {
+ cursor.draw_pos.y = ofs_y + ldata->get_line_descent(line_wrap_index);
if (ime_text.length() == 0) {
Rect2 l_caret, t_caret;
@@ -1387,57 +1393,60 @@ void TextEdit::_notification(int p_what) {
}
if ((l_caret != Rect2() && (l_dir == TextServer::DIRECTION_AUTO || l_dir == (TextServer::Direction)input_direction)) || (t_caret == Rect2())) {
- cursor_pos.x = char_margin + ofs_x + l_caret.position.x;
+ cursor.draw_pos.x = char_margin + ofs_x + l_caret.position.x;
} else {
- cursor_pos.x = char_margin + ofs_x + t_caret.position.x;
+ cursor.draw_pos.x = char_margin + ofs_x + t_caret.position.x;
}
- if (draw_caret && cursor_pos.x >= xmargin_beg && cursor_pos.x < xmargin_end) {
- if (block_caret || insert_mode) {
- //Block or underline caret, draw trailing carets at full height.
- int h = cache.font->get_height(cache.font_size);
-
- if (t_caret != Rect2()) {
- if (insert_mode) {
- t_caret.position.y = TS->shaped_text_get_descent(rid);
- t_caret.size.y = caret_width;
- } else {
- t_caret.position.y = -TS->shaped_text_get_ascent(rid);
- t_caret.size.y = h;
- }
- t_caret.position += Vector2(char_margin + ofs_x, ofs_y);
+ if (cursor.draw_pos.x >= xmargin_beg && cursor.draw_pos.x < xmargin_end) {
+ cursor.visible = true;
+ if (draw_caret) {
+ if (block_caret || insert_mode) {
+ //Block or underline caret, draw trailing carets at full height.
+ int h = cache.font->get_height(cache.font_size);
+
+ if (t_caret != Rect2()) {
+ if (insert_mode) {
+ t_caret.position.y = TS->shaped_text_get_descent(rid);
+ t_caret.size.y = caret_width;
+ } else {
+ t_caret.position.y = -TS->shaped_text_get_ascent(rid);
+ t_caret.size.y = h;
+ }
+ t_caret.position += Vector2(char_margin + ofs_x, ofs_y);
+
+ draw_rect(t_caret, cache.caret_color, false);
+ } else { // End of the line.
+ if (insert_mode) {
+ l_caret.position.y = TS->shaped_text_get_descent(rid);
+ l_caret.size.y = caret_width;
+ } else {
+ l_caret.position.y = -TS->shaped_text_get_ascent(rid);
+ l_caret.size.y = h;
+ }
+ l_caret.position += Vector2(char_margin + ofs_x, ofs_y);
+ l_caret.size.x = cache.font->get_char_size('M', 0, cache.font_size).x;
- draw_rect(t_caret, cache.caret_color, false);
- } else { // End of the line.
- if (insert_mode) {
- l_caret.position.y = TS->shaped_text_get_descent(rid);
- l_caret.size.y = caret_width;
- } else {
- l_caret.position.y = -TS->shaped_text_get_ascent(rid);
- l_caret.size.y = h;
+ draw_rect(l_caret, cache.caret_color, false);
+ }
+ } else {
+ // Normal caret.
+ if (l_caret != Rect2() && l_dir == TextServer::DIRECTION_AUTO) {
+ // Draw extra marker on top of mid caret.
+ Rect2 trect = Rect2(l_caret.position.x - 3 * caret_width, 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, cache.caret_color);
}
l_caret.position += Vector2(char_margin + ofs_x, ofs_y);
- l_caret.size.x = cache.font->get_char_size('M', 0, cache.font_size).x;
-
- draw_rect(l_caret, cache.caret_color, false);
- }
- } else {
- // Normal caret.
- if (l_caret != Rect2() && l_dir == TextServer::DIRECTION_AUTO) {
- // Draw extra marker on top of mid caret.
- Rect2 trect = Rect2(l_caret.position.x - 3 * caret_width, 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, cache.caret_color);
- }
- l_caret.position += Vector2(char_margin + ofs_x, ofs_y);
- l_caret.size.x = caret_width;
+ l_caret.size.x = caret_width;
- draw_rect(l_caret, cache.caret_color);
+ draw_rect(l_caret, cache.caret_color);
- t_caret.position += Vector2(char_margin + ofs_x, ofs_y);
- t_caret.size.x = caret_width;
+ t_caret.position += Vector2(char_margin + ofs_x, ofs_y);
+ t_caret.size.x = caret_width;
- draw_rect(t_caret, cache.caret_color);
+ draw_rect(t_caret, cache.caret_color);
+ }
}
}
} else {
@@ -1457,7 +1466,7 @@ void TextEdit::_notification(int p_what) {
}
rect.size.y = caret_width;
draw_rect(rect, cache.caret_color);
- cursor_pos.x = rect.position.x;
+ cursor.draw_pos.x = rect.position.x;
}
}
{
@@ -1476,7 +1485,7 @@ void TextEdit::_notification(int p_what) {
}
rect.size.y = caret_width * 3;
draw_rect(rect, cache.caret_color);
- cursor_pos.x = rect.position.x;
+ cursor.draw_pos.x = rect.position.x;
}
}
}
@@ -1484,227 +1493,10 @@ void TextEdit::_notification(int p_what) {
}
}
- bool completion_below = false;
- if (completion_active && is_cursor_line_visible && completion_options.size() > 0) {
- // Completion panel
-
- const Ref<StyleBox> csb = get_theme_stylebox("completion");
- const int maxlines = get_theme_constant("completion_lines");
- const int cmax_width = get_theme_constant("completion_max_width") * cache.font->get_char_size('x', 0, cache.font_size).x;
- const Color scrollc = get_theme_color("completion_scroll_color");
-
- const int completion_options_size = completion_options.size();
- const int row_count = MIN(completion_options_size, maxlines);
- const int completion_rows_height = row_count * row_height;
- const int completion_base_width = cache.font->get_string_size(completion_base, cache.font_size).width;
-
- int scroll_rectangle_width = get_theme_constant("completion_scroll_width");
- int width = 0;
-
- // Compute max width of the panel based on the longest completion option
- if (completion_options_size < 50) {
- for (int i = 0; i < completion_options_size; i++) {
- int line_width = MIN(cache.font->get_string_size(completion_options[i].display, cache.font_size).x, cmax_width);
- if (line_width > width) {
- width = line_width;
- }
- }
- } else {
- width = cmax_width;
- }
-
- // Add space for completion icons.
- const int icon_hsep = get_theme_constant("hseparation", "ItemList");
- const Size2 icon_area_size(row_height, row_height);
- const int icon_area_width = icon_area_size.width + icon_hsep;
- width += icon_area_width;
-
- const int line_from = CLAMP(completion_index - row_count / 2, 0, completion_options_size - row_count);
-
- for (int i = 0; i < row_count; i++) {
- int l = line_from + i;
- ERR_CONTINUE(l < 0 || l >= completion_options_size);
- if (completion_options[l].default_value.get_type() == Variant::COLOR) {
- width += icon_area_size.width;
- break;
- }
- }
-
- // Position completion panel
- completion_rect.size.width = width + 2;
- completion_rect.size.height = completion_rows_height;
-
- if (completion_options_size <= maxlines) {
- scroll_rectangle_width = 0;
- }
-
- const Point2 csb_offset = csb->get_offset();
-
- const int total_width = completion_rect.size.width + csb->get_minimum_size().x + scroll_rectangle_width;
- const int total_height = completion_rect.size.height + csb->get_minimum_size().y;
-
- const int rect_left_border_x = cursor_pos.x - completion_base_width - icon_area_width - csb_offset.x;
- const int rect_right_border_x = rect_left_border_x + total_width;
-
- if (rect_left_border_x < 0) {
- // Anchor the completion panel to the left
- completion_rect.position.x = 0;
- } else if (rect_right_border_x > get_size().width) {
- // Anchor the completion panel to the right
- completion_rect.position.x = get_size().width - total_width;
- } else {
- // Let the completion panel float with the cursor
- completion_rect.position.x = rect_left_border_x;
- }
-
- if (cursor_pos.y + row_height + total_height > get_size().height && cursor_pos.y > total_height) {
- // Completion panel above the cursor line
- completion_rect.position.y = cursor_pos.y - total_height;
- } else {
- // Completion panel below the cursor line
- completion_rect.position.y = cursor_pos.y + row_height;
- completion_below = true;
- }
-
- draw_style_box(csb, Rect2(completion_rect.position - csb_offset, completion_rect.size + csb->get_minimum_size() + Size2(scroll_rectangle_width, 0)));
-
- if (cache.completion_background_color.a > 0.01) {
- RenderingServer::get_singleton()->canvas_item_add_rect(ci, Rect2(completion_rect.position, completion_rect.size + Size2(scroll_rectangle_width, 0)), cache.completion_background_color);
- }
- RenderingServer::get_singleton()->canvas_item_add_rect(ci, Rect2(Point2(completion_rect.position.x, completion_rect.position.y + (completion_index - line_from) * get_row_height()), Size2(completion_rect.size.width, get_row_height())), cache.completion_selected_color);
-
- draw_rect(Rect2(completion_rect.position + Vector2(icon_area_size.x + icon_hsep, 0), Size2(MIN(completion_base_width, completion_rect.size.width - (icon_area_size.x + icon_hsep)), completion_rect.size.height)), cache.completion_existing_color);
-
- for (int i = 0; i < row_count; i++) {
- int l = line_from + i;
- ERR_CONTINUE(l < 0 || l >= completion_options_size);
-
- Ref<TextLine> tl;
- tl.instance();
- tl->add_string(completion_options[l].display, cache.font, cache.font_size);
-
- int yofs = (row_height - tl->get_size().y) / 2;
- Point2 title_pos(completion_rect.position.x, completion_rect.position.y + i * row_height + yofs);
-
- // Draw completion icon if it is valid.
- Ref<Texture2D> icon = completion_options[l].icon;
- Rect2 icon_area(completion_rect.position.x, completion_rect.position.y + i * row_height, icon_area_size.width, icon_area_size.height);
- if (icon.is_valid()) {
- const real_t max_scale = 0.7f;
- const real_t side = max_scale * icon_area.size.width;
- real_t scale = MIN(side / icon->get_width(), side / icon->get_height());
- Size2 icon_size = icon->get_size() * scale;
- draw_texture_rect(icon, Rect2(icon_area.position + (icon_area.size - icon_size) / 2, icon_size));
- }
-
- title_pos.x = icon_area.position.x + icon_area.size.width + icon_hsep;
-
- tl->set_width(completion_rect.size.width - (icon_area_size.x + icon_hsep));
-
- if (rtl) {
- if (completion_options[l].default_value.get_type() == Variant::COLOR) {
- draw_rect(Rect2(Point2(completion_rect.position.x, icon_area.position.y), icon_area_size), (Color)completion_options[l].default_value);
- }
- tl->set_align(HALIGN_RIGHT);
- } else {
- if (completion_options[l].default_value.get_type() == Variant::COLOR) {
- draw_rect(Rect2(Point2(completion_rect.position.x + completion_rect.size.width - icon_area_size.x, icon_area.position.y), icon_area_size), (Color)completion_options[l].default_value);
- }
- tl->set_align(HALIGN_LEFT);
- }
- if (cache.outline_size > 0 && cache.outline_color.a > 0) {
- tl->draw_outline(ci, title_pos, cache.outline_size, cache.outline_color);
- }
- tl->draw(ci, title_pos, completion_options[l].font_color);
- }
-
- if (scroll_rectangle_width) {
- // Draw a small scroll rectangle to show a position in the options.
- float r = (float)maxlines / completion_options_size;
- float o = (float)line_from / completion_options_size;
- draw_rect(Rect2(completion_rect.position.x + completion_rect.size.width, completion_rect.position.y + o * completion_rect.size.y, scroll_rectangle_width, completion_rect.size.y * r), scrollc);
- }
-
- completion_line_ofs = line_from;
- }
-
- // Check to see if the hint should be drawn.
- bool show_hint = false;
- if (is_cursor_line_visible && completion_hint != "") {
- if (completion_active) {
- if (completion_below && !callhint_below) {
- show_hint = true;
- } else if (!completion_below && callhint_below) {
- show_hint = true;
- }
- } else {
- show_hint = true;
- }
- }
-
- if (show_hint) {
- Ref<StyleBox> sb = get_theme_stylebox("panel", "TooltipPanel");
- Ref<Font> font = cache.font;
- Color font_color = get_theme_color("font_color", "TooltipLabel");
-
- int max_w = 0;
- int sc = completion_hint.get_slice_count("\n");
- int offset = 0;
- int spacing = 0;
- for (int i = 0; i < sc; i++) {
- String l = completion_hint.get_slice("\n", i);
- int len = font->get_string_size(l, cache.font_size).x;
- max_w = MAX(len, max_w);
- if (i == 0) {
- offset = font->get_string_size(l.substr(0, l.find(String::chr(0xFFFF))), cache.font_size).x;
- } else {
- spacing += cache.line_spacing;
- }
- }
-
- Size2 size2 = Size2(max_w, sc * font->get_height(cache.font_size) + spacing);
- Size2 minsize = size2 + sb->get_minimum_size();
-
- if (completion_hint_offset == -0xFFFF) {
- completion_hint_offset = cursor_pos.x - offset;
- }
-
- Point2 hint_ofs = Vector2(completion_hint_offset, cursor_pos.y) + callhint_offset;
-
- if (callhint_below) {
- hint_ofs.y += row_height + sb->get_offset().y;
- } else {
- hint_ofs.y -= minsize.y + sb->get_offset().y;
- }
-
- draw_style_box(sb, Rect2(hint_ofs, minsize));
-
- spacing = 0;
- for (int i = 0; i < sc; i++) {
- int begin = 0;
- int end = 0;
- String l = completion_hint.get_slice("\n", i);
-
- if (l.find(String::chr(0xFFFF)) != -1) {
- begin = font->get_string_size(l.substr(0, l.find(String::chr(0xFFFF))), cache.font_size).x;
- end = font->get_string_size(l.substr(0, l.rfind(String::chr(0xFFFF))), cache.font_size).x;
- }
-
- Point2 round_ofs = hint_ofs + sb->get_offset() + Vector2(0, font->get_ascent(cache.font_size) + font->get_height(cache.font_size) * i + spacing);
- round_ofs = round_ofs.round();
- draw_string(font, round_ofs, l.replace(String::chr(0xFFFF), ""), HALIGN_LEFT, -1, cache.font_size, font_color);
- if (end > 0) {
- Vector2 b = hint_ofs + sb->get_offset() + Vector2(begin, font->get_height(cache.font_size) + font->get_height(cache.font_size) * i + spacing - 1);
- draw_line(b, b + Vector2(end - begin, 0), font_color);
- }
- spacing += cache.line_spacing;
- }
- }
-
if (has_focus()) {
if (get_viewport()->get_window_id() != DisplayServer::INVALID_WINDOW_ID && DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_IME)) {
DisplayServer::get_singleton()->window_set_ime_active(true, get_viewport()->get_window_id());
- DisplayServer::get_singleton()->window_set_ime_position(get_global_position() + cursor_pos, get_viewport()->get_window_id());
+ DisplayServer::get_singleton()->window_set_ime_position(get_global_position() + cursor.draw_pos, get_viewport()->get_window_id());
}
}
} break;
@@ -1899,7 +1691,13 @@ void TextEdit::_consume_backspace_for_pair_symbol(int prev_line, int prev_column
}
}
-void TextEdit::backspace_at_cursor() {
+void TextEdit::backspace() {
+ ScriptInstance *si = get_script_instance();
+ if (si && si->has_method("_backspace")) {
+ si->call("_backspace");
+ return;
+ }
+
if (readonly) {
return;
}
@@ -1908,34 +1706,15 @@ void TextEdit::backspace_at_cursor() {
return;
}
+ if (is_selection_active()) {
+ delete_selection();
+ return;
+ }
+
int prev_line = cursor.column ? cursor.line : cursor.line - 1;
int prev_column = cursor.column ? (cursor.column - 1) : (text[cursor.line - 1].length());
- if (cursor.line != prev_line) {
- for (int i = 0; i < gutters.size(); i++) {
- if (!gutters[i].overwritable) {
- continue;
- }
-
- if (text.get_line_gutter_text(cursor.line, i) != "") {
- text.set_line_gutter_text(prev_line, i, text.get_line_gutter_text(cursor.line, i));
- text.set_line_gutter_item_color(prev_line, i, text.get_line_gutter_item_color(cursor.line, i));
- }
-
- if (text.get_line_gutter_icon(cursor.line, i).is_valid()) {
- text.set_line_gutter_icon(prev_line, i, text.get_line_gutter_icon(cursor.line, i));
- text.set_line_gutter_item_color(prev_line, i, text.get_line_gutter_item_color(cursor.line, i));
- }
-
- if (text.get_line_gutter_metadata(cursor.line, i) != "") {
- text.set_line_gutter_metadata(prev_line, i, text.get_line_gutter_metadata(cursor.line, i));
- }
-
- if (text.is_line_gutter_clickable(cursor.line, i)) {
- text.set_line_gutter_clickable(prev_line, i, true);
- }
- }
- }
+ merge_gutters(cursor.line, prev_line);
if (is_line_hidden(cursor.line)) {
set_line_as_hidden(prev_line, true);
@@ -1946,160 +1725,13 @@ void TextEdit::backspace_at_cursor() {
_is_pair_left_symbol(text[cursor.line][cursor.column - 1])) {
_consume_backspace_for_pair_symbol(prev_line, prev_column);
} else {
- // Handle space indentation.
- if (cursor.column != 0 && indent_using_spaces) {
- // Check if there are no other chars before cursor, just indentation.
- bool unindent = true;
- int i = 0;
- while (i < cursor.column && i < text[cursor.line].length()) {
- if (!_is_whitespace(text[cursor.line][i])) {
- unindent = false;
- break;
- }
- i++;
- }
-
- // Then we can remove all spaces as a single character.
- if (unindent) {
- // We want to remove spaces up to closest indent, or whole indent if cursor is pointing at it.
- int spaces_to_delete = _calculate_spaces_till_next_left_indent(cursor.column);
- prev_column = cursor.column - spaces_to_delete;
- _remove_text(cursor.line, prev_column, cursor.line, cursor.column);
- } else {
- _remove_text(prev_line, prev_column, cursor.line, cursor.column);
- }
- } else {
- _remove_text(prev_line, prev_column, cursor.line, cursor.column);
- }
+ _remove_text(prev_line, prev_column, cursor.line, cursor.column);
}
- cursor_set_line(prev_line, true, true);
+ cursor_set_line(prev_line, false, true);
cursor_set_column(prev_column);
}
-void TextEdit::indent_selected_lines_right() {
- int start_line;
- int end_line;
-
- // This value informs us by how much we changed selection position by indenting right.
- // Default is 1 for tab indentation.
- int selection_offset = 1;
- begin_complex_operation();
-
- if (is_selection_active()) {
- start_line = get_selection_from_line();
- end_line = get_selection_to_line();
- } else {
- start_line = cursor.line;
- end_line = start_line;
- }
-
- // Ignore if the cursor is not past the first column.
- if (is_selection_active() && get_selection_to_column() == 0) {
- selection_offset = 0;
- end_line--;
- }
-
- for (int i = start_line; i <= end_line; i++) {
- String line_text = get_line(i);
- if (line_text.size() == 0 && is_selection_active()) {
- continue;
- }
- if (indent_using_spaces) {
- // We don't really care where selection is - we just need to know indentation level at the beginning of the line.
- int left = _find_first_non_whitespace_column_of_line(line_text);
- int spaces_to_add = _calculate_spaces_till_next_right_indent(left);
- // Since we will add these many spaces, we want to move the whole selection and cursor by this much.
- selection_offset = spaces_to_add;
- for (int j = 0; j < spaces_to_add; j++) {
- line_text = ' ' + line_text;
- }
- } else {
- line_text = '\t' + line_text;
- }
- set_line(i, line_text);
- }
-
- // Fix selection and cursor being off after shifting selection right.
- if (is_selection_active()) {
- select(selection.from_line, selection.from_column + selection_offset, selection.to_line, selection.to_column + selection_offset);
- }
- cursor_set_column(cursor.column + selection_offset, false);
- end_complex_operation();
- update();
-}
-
-void TextEdit::indent_selected_lines_left() {
- int start_line;
- int end_line;
-
- // Moving cursor and selection after unindenting can get tricky because
- // changing content of line can move cursor and selection on its own (if new line ends before previous position of either),
- // therefore we just remember initial values and at the end of the operation offset them by number of removed characters.
- int removed_characters = 0;
- int initial_selection_end_column = selection.to_column;
- int initial_cursor_column = cursor.column;
-
- begin_complex_operation();
-
- if (is_selection_active()) {
- start_line = get_selection_from_line();
- end_line = get_selection_to_line();
- } else {
- start_line = cursor.line;
- end_line = start_line;
- }
-
- // Ignore if the cursor is not past the first column.
- if (is_selection_active() && get_selection_to_column() == 0) {
- end_line--;
- }
- String last_line_text = get_line(end_line);
-
- for (int i = start_line; i <= end_line; i++) {
- String line_text = get_line(i);
-
- if (line_text.begins_with("\t")) {
- line_text = line_text.substr(1, line_text.length());
- set_line(i, line_text);
- removed_characters = 1;
- } else if (line_text.begins_with(" ")) {
- // When unindenting we aim to remove spaces before line that has selection no matter what is selected,
- // so we start of by finding first non whitespace character of line
- int left = _find_first_non_whitespace_column_of_line(line_text);
-
- // Here we remove only enough spaces to align text to nearest full multiple of indentation_size.
- // In case where selection begins at the start of indentation_size multiple we remove whole indentation level.
- int spaces_to_remove = _calculate_spaces_till_next_left_indent(left);
-
- line_text = line_text.substr(spaces_to_remove, line_text.length());
- set_line(i, line_text);
- removed_characters = spaces_to_remove;
- }
- }
-
- // Fix selection and cursor being off by one on the last line.
- if (is_selection_active() && last_line_text != get_line(end_line)) {
- select(selection.from_line, selection.from_column - removed_characters,
- selection.to_line, initial_selection_end_column - removed_characters);
- }
- cursor_set_column(initial_cursor_column - removed_characters, false);
- end_complex_operation();
- update();
-}
-
-int TextEdit::_calculate_spaces_till_next_left_indent(int column) {
- int spaces_till_indent = column % indent_size;
- if (spaces_till_indent == 0) {
- spaces_till_indent = indent_size;
- }
- return spaces_till_indent;
-}
-
-int TextEdit::_calculate_spaces_till_next_right_indent(int column) {
- return indent_size - column % indent_size;
-}
-
void TextEdit::_swap_current_input_direction() {
if (input_direction == TEXT_DIRECTION_LTR) {
input_direction = TEXT_DIRECTION_RTL;
@@ -2115,99 +1747,13 @@ void TextEdit::_new_line(bool p_split_current_line, bool p_above) {
return;
}
- String ins = "\n";
-
- // Keep indentation.
- int space_count = 0;
- for (int i = 0; i < cursor.column; i++) {
- if (text[cursor.line][i] == '\t') {
- if (indent_using_spaces) {
- ins += space_indent;
- } else {
- ins += "\t";
- }
- space_count = 0;
- } else if (text[cursor.line][i] == ' ') {
- space_count++;
-
- if (space_count == indent_size) {
- if (indent_using_spaces) {
- ins += space_indent;
- } else {
- ins += "\t";
- }
- space_count = 0;
- }
- } else {
- break;
- }
- }
-
- if (is_folded(cursor.line)) {
- unfold_line(cursor.line);
- }
-
- bool brace_indent = false;
-
- // No need to indent if we are going upwards.
- if (auto_indent && !p_above) {
- // Indent once again if previous line will end with ':','{','[','(' and the line is not a comment
- // (i.e. colon/brace precedes current cursor position).
- if (cursor.column > 0) {
- bool indent_char_found = false;
- bool should_indent = false;
- char indent_char = ':';
- char c = text[cursor.line][cursor.column];
-
- for (int i = 0; i < cursor.column; i++) {
- c = text[cursor.line][i];
- switch (c) {
- case ':':
- case '{':
- case '[':
- case '(':
- indent_char_found = true;
- should_indent = true;
- indent_char = c;
- continue;
- }
-
- if (indent_char_found && is_line_comment(cursor.line)) {
- should_indent = true;
- break;
- } else if (indent_char_found && !_is_whitespace(c)) {
- should_indent = false;
- indent_char_found = false;
- }
- }
-
- if (!is_line_comment(cursor.line) && should_indent) {
- if (indent_using_spaces) {
- ins += space_indent;
- } else {
- ins += "\t";
- }
-
- // No need to move the brace below if we are not taking the text with us.
- char32_t closing_char = _get_right_pair_symbol(indent_char);
- if ((closing_char != 0) && (closing_char == text[cursor.line][cursor.column])) {
- if (p_split_current_line) {
- brace_indent = true;
- ins += "\n" + ins.substr(1, ins.length() - 2);
- } else {
- brace_indent = false;
- ins = "\n" + ins.substr(1, ins.length() - 2);
- }
- }
- }
- }
- }
begin_complex_operation();
+
bool first_line = false;
if (!p_split_current_line) {
if (p_above) {
if (cursor.line > 0) {
- cursor_set_line(cursor.line - 1);
+ cursor_set_line(cursor.line - 1, false);
cursor_set_column(text[cursor.line].length());
} else {
cursor_set_column(0);
@@ -2218,83 +1764,13 @@ void TextEdit::_new_line(bool p_split_current_line, bool p_above) {
}
}
- insert_text_at_cursor(ins);
+ insert_text_at_cursor("\n");
if (first_line) {
cursor_set_line(0);
- } else if (brace_indent) {
- cursor_set_line(cursor.line - 1);
- cursor_set_column(text[cursor.line].length());
- }
- end_complex_operation();
-}
-
-void TextEdit::_indent_right() {
- if (readonly) {
- return;
}
- if (is_selection_active()) {
- indent_selected_lines_right();
- } else {
- // Simple indent.
- if (indent_using_spaces) {
- // Insert only as much spaces as needed till next indentation level.
- int spaces_to_add = _calculate_spaces_till_next_right_indent(cursor.column);
- String indent_to_insert = String();
- for (int i = 0; i < spaces_to_add; i++) {
- indent_to_insert = ' ' + indent_to_insert;
- }
- _insert_text_at_cursor(indent_to_insert);
- } else {
- _insert_text_at_cursor("\t");
- }
- }
-}
-
-void TextEdit::_indent_left() {
- if (readonly) {
- return;
- }
-
- if (is_selection_active()) {
- indent_selected_lines_left();
- } else {
- // Simple unindent.
- int cc = cursor.column;
- const String &line = text[cursor.line];
-
- int left = _find_first_non_whitespace_column_of_line(line);
- cc = MIN(cc, left);
-
- while (cc < indent_size && cc < left && line[cc] == ' ') {
- cc++;
- }
-
- if (cc > 0 && cc <= text[cursor.line].length()) {
- if (text[cursor.line][cc - 1] == '\t') {
- // Tabs unindentation.
- _remove_text(cursor.line, cc - 1, cursor.line, cc);
- if (cursor.column >= left) {
- cursor_set_column(MAX(0, cursor.column - 1));
- }
- update();
- } else {
- // Spaces unindentation.
- int spaces_to_remove = _calculate_spaces_till_next_left_indent(cc);
- if (spaces_to_remove > 0) {
- _remove_text(cursor.line, cc - spaces_to_remove, cursor.line, cc);
- if (cursor.column > left - spaces_to_remove) { // Inside text?
- cursor_set_column(MAX(0, cursor.column - spaces_to_remove));
- }
- update();
- }
- }
- } else if (cc == 0 && line.length() > 0 && line[0] == '\t') {
- _remove_text(cursor.line, 0, cursor.line, 1);
- update();
- }
- }
+ end_complex_operation();
}
void TextEdit::_move_cursor_left(bool p_select, bool p_move_by_word) {
@@ -2411,8 +1887,6 @@ void TextEdit::_move_cursor_up(bool p_select) {
if (p_select) {
_post_shift_selection();
}
-
- _cancel_code_hint();
}
void TextEdit::_move_cursor_down(bool p_select) {
@@ -2435,8 +1909,6 @@ void TextEdit::_move_cursor_down(bool p_select) {
if (p_select) {
_post_shift_selection();
}
-
- _cancel_code_hint();
}
void TextEdit::_move_cursor_to_line_start(bool p_select) {
@@ -2476,9 +1948,6 @@ void TextEdit::_move_cursor_to_line_start(bool p_select) {
if (p_select) {
_post_shift_selection();
}
-
- _cancel_completion();
- completion_hint = "";
}
void TextEdit::_move_cursor_to_line_end(bool p_select) {
@@ -2504,8 +1973,6 @@ void TextEdit::_move_cursor_to_line_end(bool p_select) {
if (p_select) {
_post_shift_selection();
}
- _cancel_completion();
- completion_hint = "";
}
void TextEdit::_move_cursor_page_up(bool p_select) {
@@ -2522,9 +1989,6 @@ void TextEdit::_move_cursor_page_up(bool p_select) {
if (p_select) {
_post_shift_selection();
}
-
- _cancel_completion();
- completion_hint = "";
}
void TextEdit::_move_cursor_page_down(bool p_select) {
@@ -2541,25 +2005,26 @@ void TextEdit::_move_cursor_page_down(bool p_select) {
if (p_select) {
_post_shift_selection();
}
-
- _cancel_completion();
- completion_hint = "";
}
-void TextEdit::_backspace(bool p_word, bool p_all_to_left) {
+void TextEdit::_do_backspace(bool p_word, bool p_all_to_left) {
if (readonly) {
return;
}
- if (is_selection_active()) {
- _delete_selection();
+ if (is_selection_active() || (!p_all_to_left && !p_word)) {
+ backspace();
return;
}
+
if (p_all_to_left) {
int cursor_current_column = cursor.column;
cursor.column = 0;
_remove_text(cursor.line, 0, cursor.line, cursor_current_column);
- } else if (p_word) {
+ return;
+ }
+
+ if (p_word) {
int line = cursor.line;
int column = cursor.column;
@@ -2573,14 +2038,9 @@ void TextEdit::_backspace(bool p_word, bool p_all_to_left) {
_remove_text(line, column, cursor.line, cursor.column);
- cursor_set_line(line);
+ cursor_set_line(line, false);
cursor_set_column(column);
- } else {
- // One character.
- if (cursor.line > 0 && is_line_hidden(cursor.line - 1)) {
- unfold_line(cursor.line - 1);
- }
- backspace_at_cursor();
+ return;
}
}
@@ -2590,7 +2050,7 @@ void TextEdit::_delete(bool p_word, bool p_all_to_right) {
}
if (is_selection_active()) {
- _delete_selection();
+ delete_selection();
return;
}
int curline_len = text[cursor.line].length();
@@ -2635,15 +2095,16 @@ void TextEdit::_delete(bool p_word, bool p_all_to_right) {
update();
}
-void TextEdit::_delete_selection() {
- if (is_selection_active()) {
- selection.active = false;
- update();
- _remove_text(selection.from_line, selection.from_column, selection.to_line, selection.to_column);
- cursor_set_line(selection.from_line, true, false);
- cursor_set_column(selection.from_column);
- update();
+void TextEdit::delete_selection() {
+ if (!is_selection_active()) {
+ return;
}
+
+ selection.active = false;
+ _remove_text(selection.from_line, selection.from_column, selection.to_line, selection.to_column);
+ cursor_set_line(selection.from_line, false, false);
+ cursor_set_column(selection.from_column);
+ update();
}
void TextEdit::_move_cursor_document_start(bool p_select) {
@@ -2676,13 +2137,9 @@ void TextEdit::_move_cursor_document_end(bool p_select) {
}
}
-void TextEdit::_handle_unicode_character(uint32_t unicode, bool p_had_selection, bool p_update_auto_complete) {
- if (p_update_auto_complete) {
- _reset_caret_blink_timer();
- }
-
+void TextEdit::_handle_unicode_character(uint32_t unicode, bool p_had_selection) {
if (p_had_selection) {
- _delete_selection();
+ delete_selection();
}
// Remove the old character if in insert mode and no selection.
@@ -2697,11 +2154,6 @@ void TextEdit::_handle_unicode_character(uint32_t unicode, bool p_had_selection,
const char32_t chr[2] = { (char32_t)unicode, 0 };
- // Clear completion hint when function closed
- if (completion_hint != "" && unicode == ')') {
- completion_hint = "";
- }
-
if (auto_brace_completion_enabled && _is_pair_symbol(chr[0])) {
_consume_pair_symbol(chr[0]);
} else {
@@ -2711,10 +2163,6 @@ void TextEdit::_handle_unicode_character(uint32_t unicode, bool p_had_selection,
if ((insert_mode && !p_had_selection) || (selection.active != p_had_selection)) {
end_complex_operation();
}
-
- if (p_update_auto_complete) {
- _update_completion_candidates();
- }
}
void TextEdit::_get_mouse_pos(const Point2i &p_mouse, int &r_row, int &r_col) const {
@@ -2854,6 +2302,8 @@ void TextEdit::_get_minimap_mouse_row(const Point2i &p_mouse, int &r_row) const
}
void TextEdit::_gui_input(const Ref<InputEvent> &p_gui_input) {
+ ERR_FAIL_COND(p_gui_input.is_null());
+
double prev_v_scroll = v_scroll->get_value();
double prev_h_scroll = h_scroll->get_value();
@@ -2868,53 +2318,27 @@ void TextEdit::_gui_input(const Ref<InputEvent> &p_gui_input) {
// Ignore mouse clicks in IME input mode.
return;
}
- if (completion_active && completion_rect.has_point(mpos)) {
- if (!mb->is_pressed()) {
- return;
- }
-
- if (mb->get_button_index() == MOUSE_BUTTON_WHEEL_UP) {
- if (completion_index > 0) {
- completion_index--;
- completion_current = completion_options[completion_index];
- update();
- }
- }
- if (mb->get_button_index() == MOUSE_BUTTON_WHEEL_DOWN) {
- if (completion_index < completion_options.size() - 1) {
- completion_index++;
- completion_current = completion_options[completion_index];
- update();
- }
- }
-
- if (mb->get_button_index() == MOUSE_BUTTON_LEFT) {
- completion_index = CLAMP(completion_line_ofs + (mpos.y - completion_rect.position.y) / get_row_height(), 0, completion_options.size() - 1);
-
- completion_current = completion_options[completion_index];
- update();
- if (mb->is_doubleclick()) {
- _confirm_completion();
- }
- }
- return;
- } else {
- _cancel_completion();
- _cancel_code_hint();
- }
if (mb->is_pressed()) {
- if (mb->get_button_index() == MOUSE_BUTTON_WHEEL_UP && !mb->get_command()) {
- if (mb->get_shift()) {
+ if (mb->get_button_index() == MOUSE_BUTTON_WHEEL_UP && !mb->is_command_pressed()) {
+ if (mb->is_shift_pressed()) {
h_scroll->set_value(h_scroll->get_value() - (100 * mb->get_factor()));
+ } else if (mb->is_alt_pressed()) {
+ // Scroll 5 times as fast as normal (like in Visual Studio Code).
+ _scroll_up(15 * mb->get_factor());
} else if (v_scroll->is_visible()) {
+ // Scroll 3 lines.
_scroll_up(3 * mb->get_factor());
}
}
- if (mb->get_button_index() == MOUSE_BUTTON_WHEEL_DOWN && !mb->get_command()) {
- if (mb->get_shift()) {
+ if (mb->get_button_index() == MOUSE_BUTTON_WHEEL_DOWN && !mb->is_command_pressed()) {
+ if (mb->is_shift_pressed()) {
h_scroll->set_value(h_scroll->get_value() + (100 * mb->get_factor()));
+ } else if (mb->is_alt_pressed()) {
+ // Scroll 5 times as fast as normal (like in Visual Studio Code).
+ _scroll_down(15 * mb->get_factor());
} else if (v_scroll->is_visible()) {
+ // Scroll 3 lines.
_scroll_down(3 * mb->get_factor());
}
}
@@ -2944,15 +2368,6 @@ void TextEdit::_gui_input(const Ref<InputEvent> &p_gui_input) {
left_margin += gutters[i].width;
}
- // Unfold on folded icon click.
- if (is_folded(row)) {
- left_margin += gutter_padding + text.get_line_width(row) - cursor.x_ofs;
- if (mpos.x > left_margin && mpos.x <= left_margin + cache.folded_eol_icon->get_width() + 3) {
- unfold_line(row);
- return;
- }
- }
-
// minimap
if (draw_minimap) {
_update_minimap_click();
@@ -2967,7 +2382,7 @@ void TextEdit::_gui_input(const Ref<InputEvent> &p_gui_input) {
cursor_set_line(row, false, false);
cursor_set_column(col);
- if (mb->get_shift() && (cursor.column != prev_col || cursor.line != prev_line)) {
+ if (mb->is_shift_pressed() && (cursor.column != prev_col || cursor.line != prev_line)) {
if (!selection.active) {
selection.active = true;
selection.selecting_mode = SelectionMode::SELECTION_MODE_POINTER;
@@ -3016,12 +2431,12 @@ void TextEdit::_gui_input(const Ref<InputEvent> &p_gui_input) {
selection.selecting_column = col;
}
- if (!mb->is_doubleclick() && (OS::get_singleton()->get_ticks_msec() - last_dblclk) < 600 && cursor.line == prev_line) {
+ if (!mb->is_double_click() && (OS::get_singleton()->get_ticks_msec() - last_dblclk) < 600 && cursor.line == prev_line) {
// Triple-click select line.
selection.selecting_mode = SelectionMode::SELECTION_MODE_LINE;
_update_selection_mode_line();
last_dblclk = 0;
- } else if (mb->is_doubleclick() && text[cursor.line].length()) {
+ } else if (mb->is_double_click() && text[cursor.line].length()) {
// Double-click select word.
selection.selecting_mode = SelectionMode::SELECTION_MODE_WORD;
_update_selection_mode_word();
@@ -3063,7 +2478,7 @@ void TextEdit::_gui_input(const Ref<InputEvent> &p_gui_input) {
}
} else {
if (mb->get_button_index() == MOUSE_BUTTON_LEFT) {
- if (mb->get_command() && highlighted_word != String()) {
+ if (mb->is_command_pressed() && highlighted_word != String()) {
int row, col;
_get_mouse_pos(Point2i(mpos.x, mpos.y), row, col);
@@ -3106,7 +2521,7 @@ void TextEdit::_gui_input(const Ref<InputEvent> &p_gui_input) {
mpos.x = get_size().x - mpos.x;
}
if (select_identifiers_enabled) {
- if (!dragging_minimap && !dragging_selection && mm->get_command() && mm->get_button_mask() == 0) {
+ if (!dragging_minimap && !dragging_selection && mm->is_command_pressed() && mm->get_button_mask() == 0) {
String new_word = get_word_at_pos(mpos);
if (new_word != highlighted_word) {
emit_signal("symbol_validate", new_word);
@@ -3155,7 +2570,7 @@ void TextEdit::_gui_input(const Ref<InputEvent> &p_gui_input) {
#ifdef OSX_ENABLED
if (k->get_keycode() == KEY_META) {
#else
- if (k->get_keycode() == KEY_CONTROL) {
+ if (k->get_keycode() == KEY_CTRL) {
#endif
if (select_identifiers_enabled) {
if (k->is_pressed() && !dragging_minimap && !dragging_selection) {
@@ -3173,7 +2588,7 @@ void TextEdit::_gui_input(const Ref<InputEvent> &p_gui_input) {
}
// If a modifier has been pressed, and nothing else, return.
- if (k->get_keycode() == KEY_CONTROL || k->get_keycode() == KEY_ALT || k->get_keycode() == KEY_SHIFT || k->get_keycode() == KEY_META) {
+ if (k->get_keycode() == KEY_CTRL || k->get_keycode() == KEY_ALT || k->get_keycode() == KEY_SHIFT || k->get_keycode() == KEY_META) {
return;
}
@@ -3181,7 +2596,7 @@ void TextEdit::_gui_input(const Ref<InputEvent> &p_gui_input) {
// Allow unicode handling if:
// * No Modifiers are pressed (except shift)
- bool allow_unicode_handling = !(k->get_command() || k->get_control() || k->get_alt() || k->get_metakey());
+ bool allow_unicode_handling = !(k->is_command_pressed() || k->is_ctrl_pressed() || k->is_alt_pressed() || k->is_meta_pressed());
// Save here for insert mode, just in case it is cleared in the following section.
bool had_selection = selection.active;
@@ -3190,96 +2605,6 @@ void TextEdit::_gui_input(const Ref<InputEvent> &p_gui_input) {
// Check and handle all built in shortcuts.
- // AUTO-COMPLETE
-
- if (k->is_action("ui_text_completion_query", true)) {
- query_code_comple();
- accept_event();
- return;
- }
-
- if (completion_active) {
- if (k->is_action("ui_up", true)) {
- if (completion_index > 0) {
- completion_index--;
- } else {
- completion_index = completion_options.size() - 1;
- }
- completion_current = completion_options[completion_index];
- update();
- accept_event();
- return;
- }
- if (k->is_action("ui_down", true)) {
- if (completion_index < completion_options.size() - 1) {
- completion_index++;
- } else {
- completion_index = 0;
- }
- completion_current = completion_options[completion_index];
- update();
- accept_event();
- return;
- }
- if (k->is_action("ui_page_up", true)) {
- completion_index -= get_theme_constant("completion_lines");
- if (completion_index < 0) {
- completion_index = 0;
- }
- completion_current = completion_options[completion_index];
- update();
- accept_event();
- return;
- }
- if (k->is_action("ui_page_down", true)) {
- completion_index += get_theme_constant("completion_lines");
- if (completion_index >= completion_options.size()) {
- completion_index = completion_options.size() - 1;
- }
- completion_current = completion_options[completion_index];
- update();
- accept_event();
- return;
- }
- if (k->is_action("ui_home", true)) {
- if (completion_index > 0) {
- completion_index = 0;
- completion_current = completion_options[completion_index];
- update();
- }
- accept_event();
- return;
- }
- if (k->is_action("ui_end", true)) {
- if (completion_index < completion_options.size() - 1) {
- completion_index = completion_options.size() - 1;
- completion_current = completion_options[completion_index];
- update();
- }
- accept_event();
- return;
- }
- if (k->is_action("ui_accept", true) || k->is_action("ui_text_completion_accept", true)) {
- _confirm_completion();
- accept_event();
- return;
- }
- if (k->is_action("ui_cancel", true)) {
- _cancel_completion();
- accept_event();
- return;
- }
-
- // Handle Unicode here (if no modifiers active) and update autocomplete.
- if (k->get_unicode() >= 32) {
- if (allow_unicode_handling && !readonly) {
- _handle_unicode_character(k->get_unicode(), had_selection, true);
- accept_event();
- return;
- }
- }
- }
-
// NEWLINES.
if (k->is_action("ui_text_newline_above", true)) {
_new_line(false, true);
@@ -3297,34 +2622,19 @@ void TextEdit::_gui_input(const Ref<InputEvent> &p_gui_input) {
return;
}
- // INDENTATION.
- if (k->is_action("ui_text_dedent", true)) {
- _indent_left();
- accept_event();
- return;
- }
- if (k->is_action("ui_text_indent", true)) {
- _indent_right();
- accept_event();
- return;
- }
-
// BACKSPACE AND DELETE.
if (k->is_action("ui_text_backspace_all_to_left", true)) {
- _backspace(false, true);
+ _do_backspace(false, true);
accept_event();
return;
}
if (k->is_action("ui_text_backspace_word", true)) {
- _backspace(true);
+ _do_backspace(true);
accept_event();
return;
}
if (k->is_action("ui_text_backspace", true)) {
- _backspace();
- if (completion_active) {
- _update_completion_candidates();
- }
+ _do_backspace();
accept_event();
return;
}
@@ -3356,13 +2666,18 @@ void TextEdit::_gui_input(const Ref<InputEvent> &p_gui_input) {
return;
}
- // SELECT ALL, CUT, COPY, PASTE.
+ // SELECT ALL, SELECT WORD UNDER CARET, CUT, COPY, PASTE.
if (k->is_action("ui_text_select_all", true)) {
select_all();
accept_event();
return;
}
+ if (k->is_action("ui_text_select_word_under_caret", true)) {
+ select_word_under_caret();
+ accept_event();
+ return;
+ }
if (k->is_action("ui_cut", true)) {
cut();
accept_event();
@@ -3392,7 +2707,6 @@ void TextEdit::_gui_input(const Ref<InputEvent> &p_gui_input) {
}
// MISC.
-
if (k->is_action("ui_menu", true)) {
if (context_menu_enabled) {
menu->set_position(get_screen_transform().xform(_get_cursor_pixel_pos()));
@@ -3409,14 +2723,6 @@ void TextEdit::_gui_input(const Ref<InputEvent> &p_gui_input) {
accept_event();
return;
}
- if (k->is_action("ui_cancel", true)) {
- if (completion_hint != "") {
- completion_hint = "";
- update();
- }
- accept_event();
- return;
- }
if (k->is_action("ui_swap_input_direction", true)) {
_swap_current_input_direction();
accept_event();
@@ -3426,9 +2732,9 @@ void TextEdit::_gui_input(const Ref<InputEvent> &p_gui_input) {
// CURSOR MOVEMENT
k = k->duplicate();
- bool shift_pressed = k->get_shift();
+ bool shift_pressed = k->is_shift_pressed();
// Remove shift or else actions will not match. Use above variable for selection.
- k->set_shift(false);
+ k->set_shift_pressed(false);
// CURSOR MOVEMENT - LEFT, RIGHT.
if (k->is_action("ui_text_caret_word_left", true)) {
@@ -3502,7 +2808,7 @@ void TextEdit::_gui_input(const Ref<InputEvent> &p_gui_input) {
if (allow_unicode_handling && !readonly && k->get_unicode() >= 32) {
// Handle Unicode (if no modifiers active).
- _handle_unicode_character(k->get_unicode(), had_selection, false);
+ _handle_unicode_character(k->get_unicode(), had_selection);
accept_event();
return;
}
@@ -3849,7 +3155,7 @@ void TextEdit::_insert_text_at_cursor(const String &p_text) {
int new_column, new_line;
_insert_text(cursor.line, cursor.column, p_text, &new_line, &new_column);
_update_scrollbars();
- cursor_set_line(new_line);
+ cursor_set_line(new_line, false);
cursor_set_column(new_column);
update();
@@ -4052,10 +3358,6 @@ void TextEdit::center_viewport_to_cursor() {
scrolling = false;
minimap_clicked = false;
- if (is_line_hidden(cursor.line)) {
- unfold_line(cursor.line);
- }
-
set_line_as_center_visible(cursor.line, get_cursor_wrap_index());
int visible_width = get_size().width - cache.style_normal->get_minimum_size().width - gutters_width - gutter_padding - cache.minimap_width;
if (v_scroll->is_visible_in_tree()) {
@@ -4263,6 +3565,14 @@ void TextEdit::cursor_set_line(int p_row, bool p_adjust_viewport, bool p_can_be_
}
}
+Point2 TextEdit::get_caret_draw_pos() const {
+ return cursor.draw_pos;
+}
+
+bool TextEdit::is_caret_visible() const {
+ return cursor.visible;
+}
+
int TextEdit::cursor_get_column() const {
return cursor.column;
}
@@ -4423,7 +3733,7 @@ int TextEdit::get_column_x_offset_for_line(int p_char, int p_line) const {
void TextEdit::insert_text_at_cursor(const String &p_text) {
if (selection.active) {
- cursor_set_line(selection.from_line);
+ cursor_set_line(selection.from_line, false);
cursor_set_column(selection.from_column);
_remove_text(selection.from_line, selection.from_column, selection.to_line, selection.to_column);
@@ -4440,10 +3750,6 @@ Control::CursorShape TextEdit::get_cursor_shape(const Point2 &p_pos) const {
return CURSOR_POINTING_HAND;
}
- if ((completion_active && completion_rect.has_point(p_pos))) {
- return CURSOR_ARROW;
- }
-
int row, col;
_get_mouse_pos(p_pos, row, col);
@@ -4469,15 +3775,6 @@ Control::CursorShape TextEdit::get_cursor_shape(const Point2 &p_pos) const {
if (draw_minimap && p_pos.x > xmargin_end - minimap_width && p_pos.x <= xmargin_end) {
return CURSOR_ARROW;
}
-
- // EOL fold icon.
- if (is_folded(row)) {
- gutter += gutter_padding + text.get_line_width(row) - cursor.x_ofs;
- if (p_pos.x > gutter - 3 && p_pos.x <= gutter + cache.folded_eol_icon->get_width() + 3) {
- return CURSOR_POINTING_HAND;
- }
- }
-
return get_default_cursor_shape();
}
@@ -4655,26 +3952,6 @@ String TextEdit::get_text_for_lookup_completion() {
return longthing;
}
-String TextEdit::get_text_for_completion() {
- String longthing;
- int len = text.size();
- for (int i = 0; i < len; i++) {
- if (i == cursor.line) {
- longthing += text[i].substr(0, cursor.column);
- longthing += String::chr(0xFFFF); // Not unicode, represents the cursor.
- longthing += text[i].substr(cursor.column, text[i].size());
- } else {
- longthing += text[i];
- }
-
- if (i != len - 1) {
- longthing += "\n";
- }
- }
-
- return longthing;
-};
-
String TextEdit::get_line(int line) const {
if (line < 0 || line >= text.size()) {
return "";
@@ -4683,6 +3960,10 @@ String TextEdit::get_line(int line) const {
return text[line];
};
+bool TextEdit::has_ime_text() const {
+ return !ime_text.is_empty();
+}
+
void TextEdit::_clear() {
clear_undo_history();
text.clear();
@@ -4727,14 +4008,6 @@ bool TextEdit::is_wrap_enabled() const {
return wrap_enabled;
}
-void TextEdit::set_max_chars(int p_max_chars) {
- max_chars = p_max_chars;
-}
-
-int TextEdit::get_max_chars() const {
- return max_chars;
-}
-
void TextEdit::_reset_caret_blink_timer() {
if (caret_blink_enabled) {
draw_caret = true;
@@ -4757,10 +4030,6 @@ void TextEdit::_update_caches() {
cache.style_normal = get_theme_stylebox("normal");
cache.style_focus = get_theme_stylebox("focus");
cache.style_readonly = get_theme_stylebox("read_only");
- cache.completion_background_color = get_theme_color("completion_background_color");
- cache.completion_selected_color = get_theme_color("completion_selected_color");
- cache.completion_existing_color = get_theme_color("completion_existing_color");
- cache.completion_font_color = get_theme_color("completion_font_color");
cache.font = get_theme_font("font");
cache.font_size = get_theme_font_size("font_size");
cache.outline_color = get_theme_color("font_outline_color");
@@ -4771,10 +4040,9 @@ void TextEdit::_update_caches() {
cache.font_selected_color = get_theme_color("font_selected_color");
cache.font_readonly_color = get_theme_color("font_readonly_color");
cache.selection_color = get_theme_color("selection_color");
- cache.mark_color = get_theme_color("mark_color");
cache.current_line_color = get_theme_color("current_line_color");
cache.line_length_guideline_color = get_theme_color("line_length_guideline_color");
- cache.code_folding_color = get_theme_color("code_folding_color");
+ cache.code_folding_color = get_theme_color("code_folding_color", "CodeEdit");
cache.brace_mismatch_color = get_theme_color("brace_mismatch_color");
cache.word_highlighted_color = get_theme_color("word_highlighted_color");
cache.search_result_color = get_theme_color("search_result_color");
@@ -4787,7 +4055,7 @@ void TextEdit::_update_caches() {
#endif
cache.tab_icon = get_theme_icon("tab");
cache.space_icon = get_theme_icon("space");
- cache.folded_eol_icon = get_theme_icon("GuiEllipsis", "EditorIcons");
+ cache.folded_eol_icon = get_theme_icon("folded_eol_icon", "CodeEdit");
TextServer::Direction dir;
if (text_direction == Control::TEXT_DIRECTION_INHERITED) {
@@ -4896,6 +4164,10 @@ int TextEdit::get_gutter_width(int p_gutter) const {
return gutters[p_gutter].width;
}
+int TextEdit::get_total_gutter_width() const {
+ return gutters_width + gutter_padding;
+}
+
void TextEdit::set_gutter_draw(int p_gutter, bool p_draw) {
ERR_FAIL_INDEX(p_gutter, gutters.size());
gutters.write[p_gutter].draw = p_draw;
@@ -4928,6 +4200,39 @@ bool TextEdit::is_gutter_overwritable(int p_gutter) const {
return gutters[p_gutter].overwritable;
}
+void TextEdit::merge_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());
+ if (p_from_line == p_to_line) {
+ return;
+ }
+
+ for (int i = 0; i < gutters.size(); i++) {
+ if (!gutters[i].overwritable) {
+ continue;
+ }
+
+ if (text.get_line_gutter_text(p_from_line, i) != "") {
+ text.set_line_gutter_text(p_to_line, i, text.get_line_gutter_text(p_from_line, i));
+ text.set_line_gutter_item_color(p_to_line, i, text.get_line_gutter_item_color(p_from_line, i));
+ }
+
+ if (text.get_line_gutter_icon(p_from_line, i).is_valid()) {
+ text.set_line_gutter_icon(p_to_line, i, text.get_line_gutter_icon(p_from_line, i));
+ text.set_line_gutter_item_color(p_to_line, i, text.get_line_gutter_item_color(p_from_line, i));
+ }
+
+ if (text.get_line_gutter_metadata(p_from_line, i) != "") {
+ text.set_line_gutter_metadata(p_to_line, i, text.get_line_gutter_metadata(p_from_line, i));
+ }
+
+ if (text.is_line_gutter_clickable(p_from_line, i)) {
+ text.set_line_gutter_clickable(p_to_line, i, true);
+ }
+ }
+ update();
+}
+
void TextEdit::set_gutter_custom_draw(int p_gutter, Object *p_object, const StringName &p_callback) {
ERR_FAIL_INDEX(p_gutter, gutters.size());
ERR_FAIL_NULL(p_object);
@@ -5001,16 +4306,16 @@ bool TextEdit::is_line_gutter_clickable(int p_line, int p_gutter) const {
return text.is_line_gutter_clickable(p_line, p_gutter);
}
-void TextEdit::add_keyword(const String &p_keyword) {
- keywords.insert(p_keyword);
-}
-
-void TextEdit::clear_keywords() {
- keywords.clear();
+// Line style
+void TextEdit::set_line_background_color(int p_line, const Color &p_color) {
+ ERR_FAIL_INDEX(p_line, text.size());
+ text.set_line_background_color(p_line, p_color);
+ update();
}
-void TextEdit::set_auto_indent(bool p_auto_indent) {
- auto_indent = p_auto_indent;
+Color TextEdit::get_line_background_color(int p_line) {
+ ERR_FAIL_INDEX_V(p_line, text.size(), Color());
+ return text.get_line_background_color(p_line);
}
void TextEdit::cut() {
@@ -5028,7 +4333,7 @@ void TextEdit::cut() {
_remove_text(cursor.line, 0, cursor.line + 1, 0);
} else {
_remove_text(cursor.line, 0, cursor.line, text[cursor.line].length());
- backspace_at_cursor();
+ backspace();
cursor_set_line(cursor.line + 1);
}
@@ -5040,7 +4345,7 @@ void TextEdit::cut() {
DisplayServer::get_singleton()->clipboard_set(clipboard);
_remove_text(selection.from_line, selection.from_column, selection.to_line, selection.to_column);
- cursor_set_line(selection.from_line); // Set afterwards else it causes the view to be offset.
+ cursor_set_line(selection.from_line, false); // Set afterwards else it causes the view to be offset.
cursor_set_column(selection.from_column);
selection.active = false;
@@ -5076,7 +4381,7 @@ void TextEdit::paste() {
selection.active = false;
selection.selecting_mode = SelectionMode::SELECTION_MODE_NONE;
_remove_text(selection.from_line, selection.from_column, selection.to_line, selection.to_column);
- cursor_set_line(selection.from_line);
+ cursor_set_line(selection.from_line, false);
cursor_set_column(selection.from_column);
} else if (!cut_copy_line.is_empty() && cut_copy_line == clipboard) {
@@ -5113,6 +4418,39 @@ void TextEdit::select_all() {
update();
}
+void TextEdit::select_word_under_caret() {
+ if (!selecting_enabled) {
+ return;
+ }
+
+ if (text.size() == 1 && text[0].length() == 0) {
+ return;
+ }
+
+ if (selection.active) {
+ // Allow toggling selection by pressing the shortcut a second time.
+ // This is also usable as a general-purpose "deselect" shortcut after
+ // selecting anything.
+ deselect();
+ return;
+ }
+
+ int begin = 0;
+ int end = 0;
+ const Vector<Vector2i> words = TS->shaped_text_get_word_breaks(text.get_line_data(cursor.line)->get_rid());
+ for (int i = 0; i < words.size(); i++) {
+ if (words[i].x <= cursor.column && words[i].y >= cursor.column) {
+ begin = words[i].x;
+ end = words[i].y;
+ break;
+ }
+ }
+
+ select(cursor.line, begin, cursor.line, end);
+ // Move the cursor to the end of the word for easier editing.
+ cursor_set_column(end, false);
+}
+
void TextEdit::deselect() {
selection.active = false;
update();
@@ -5419,12 +4757,6 @@ void TextEdit::_text_changed_emit() {
text_changed_dirty = false;
}
-void TextEdit::set_line_as_marked(int p_line, bool p_marked) {
- ERR_FAIL_INDEX(p_line, text.size());
- text.set_marked(p_line, p_marked);
- update();
-}
-
void TextEdit::set_line_as_hidden(int p_line, bool p_hidden) {
ERR_FAIL_INDEX(p_line, text.size());
if (is_hiding_enabled() || !p_hidden) {
@@ -5438,14 +4770,6 @@ bool TextEdit::is_line_hidden(int p_line) const {
return text.is_hidden(p_line);
}
-void TextEdit::fold_all_lines() {
- for (int i = 0; i < text.size(); i++) {
- fold_line(i);
- }
- _update_scrollbars();
- update();
-}
-
void TextEdit::unhide_all_lines() {
for (int i = 0; i < text.size(); i++) {
text.set_hidden(i, false);
@@ -5556,7 +4880,6 @@ int TextEdit::get_last_unhidden_line() const {
int TextEdit::get_indent_level(int p_line) const {
ERR_FAIL_INDEX_V(p_line, text.size(), 0);
- // Counts number of tabs and spaces before line starts.
int tab_count = 0;
int whitespace_count = 0;
int line_length = text[p_line].size();
@@ -5569,175 +4892,27 @@ int TextEdit::get_indent_level(int p_line) const {
break;
}
}
- return tab_count * indent_size + whitespace_count;
-}
-
-bool TextEdit::is_line_comment(int p_line) const {
- // Checks to see if this line is the start of a comment.
- ERR_FAIL_INDEX_V(p_line, text.size(), false);
-
- int line_length = text[p_line].size();
- for (int i = 0; i < line_length - 1; i++) {
- if (_is_whitespace(text[p_line][i])) {
- continue;
- }
- if (_is_symbol(text[p_line][i])) {
- if (text[p_line][i] == '\\') {
- i++; // Skip quoted anything.
- continue;
- }
- return text[p_line][i] == '#' || (i + 1 < line_length && text[p_line][i] == '/' && text[p_line][i + 1] == '/');
- }
- break;
- }
- return false;
-}
-
-bool TextEdit::can_fold(int p_line) const {
- ERR_FAIL_INDEX_V(p_line, text.size(), false);
- if (!is_hiding_enabled()) {
- return false;
- }
- if (p_line + 1 >= text.size()) {
- return false;
- }
- if (text[p_line].strip_edges().size() == 0) {
- return false;
- }
- if (is_folded(p_line)) {
- return false;
- }
- if (is_line_hidden(p_line)) {
- return false;
- }
- if (is_line_comment(p_line)) {
- return false;
- }
-
- int start_indent = get_indent_level(p_line);
-
- for (int i = p_line + 1; i < text.size(); i++) {
- if (text[i].strip_edges().size() == 0) {
- continue;
- }
- int next_indent = get_indent_level(i);
- if (is_line_comment(i)) {
- continue;
- } else if (next_indent > start_indent) {
- return true;
- } else {
- return false;
- }
- }
-
- return false;
-}
-
-bool TextEdit::is_folded(int p_line) const {
- ERR_FAIL_INDEX_V(p_line, text.size(), false);
- if (p_line + 1 >= text.size()) {
- return false;
- }
- return !is_line_hidden(p_line) && is_line_hidden(p_line + 1);
+ return tab_count * text.get_tab_size() + whitespace_count;
}
-Vector<int> TextEdit::get_folded_lines() const {
- Vector<int> folded_lines;
-
- for (int i = 0; i < text.size(); i++) {
- if (is_folded(i)) {
- folded_lines.push_back(i);
- }
- }
- return folded_lines;
-}
-
-void TextEdit::fold_line(int p_line) {
- ERR_FAIL_INDEX(p_line, text.size());
- if (!is_hiding_enabled()) {
- return;
- }
- if (!can_fold(p_line)) {
- return;
- }
-
- // Hide lines below this one.
- int start_indent = get_indent_level(p_line);
- int last_line = start_indent;
- for (int i = p_line + 1; i < text.size(); i++) {
- if (text[i].strip_edges().size() != 0) {
- if (is_line_comment(i)) {
- continue;
- } else if (get_indent_level(i) > start_indent) {
- last_line = i;
- } else {
- break;
- }
- }
- }
- for (int i = p_line + 1; i <= last_line; i++) {
- set_line_as_hidden(i, true);
- }
-
- // Fix selection.
- if (is_selection_active()) {
- if (is_line_hidden(selection.from_line) && is_line_hidden(selection.to_line)) {
- deselect();
- } else if (is_line_hidden(selection.from_line)) {
- select(p_line, 9999, selection.to_line, selection.to_column);
- } else if (is_line_hidden(selection.to_line)) {
- select(selection.from_line, selection.from_column, p_line, 9999);
- }
- }
-
- // Reset cursor.
- if (is_line_hidden(cursor.line)) {
- cursor_set_line(p_line, false, false);
- cursor_set_column(get_line(p_line).length(), false);
- }
- _update_scrollbars();
- update();
-}
-
-void TextEdit::unfold_line(int p_line) {
- ERR_FAIL_INDEX(p_line, text.size());
-
- if (!is_folded(p_line) && !is_line_hidden(p_line)) {
- return;
- }
- int fold_start;
- for (fold_start = p_line; fold_start > 0; fold_start--) {
- if (is_folded(fold_start)) {
- break;
- }
- }
- fold_start = is_folded(fold_start) ? fold_start : p_line;
-
- for (int i = fold_start + 1; i < text.size(); i++) {
- if (is_line_hidden(i)) {
- set_line_as_hidden(i, false);
- } else {
- break;
- }
- }
- _update_scrollbars();
- update();
-}
-
-void TextEdit::toggle_fold_line(int p_line) {
- ERR_FAIL_INDEX(p_line, text.size());
+int TextEdit::get_first_non_whitespace_column(int p_line) const {
+ ERR_FAIL_INDEX_V(p_line, text.size(), 0);
- if (!is_folded(p_line)) {
- fold_line(p_line);
- } else {
- unfold_line(p_line);
+ int col = 0;
+ while (col < text[p_line].length() && _is_whitespace(text[p_line][col])) {
+ col++;
}
+ return col;
}
int TextEdit::get_line_count() const {
return text.size();
}
+int TextEdit::get_line_width(int p_line, int p_wrap_offset) const {
+ return text.get_line_width(p_line, p_wrap_offset);
+}
+
void TextEdit::_do_text_op(const TextOperation &p_op, bool p_reverse) {
ERR_FAIL_COND(p_op.type == TextOperation::TYPE_NONE);
@@ -5815,11 +4990,10 @@ void TextEdit::undo() {
_update_scrollbars();
if (undo_stack_pos->get().type == TextOperation::TYPE_REMOVE) {
- cursor_set_line(undo_stack_pos->get().to_line);
+ cursor_set_line(undo_stack_pos->get().to_line, false);
cursor_set_column(undo_stack_pos->get().to_column);
- _cancel_code_hint();
} else {
- cursor_set_line(undo_stack_pos->get().from_line);
+ cursor_set_line(undo_stack_pos->get().from_line, false);
cursor_set_column(undo_stack_pos->get().from_column);
}
update();
@@ -5854,7 +5028,7 @@ void TextEdit::redo() {
}
_update_scrollbars();
- cursor_set_line(undo_stack_pos->get().to_line);
+ cursor_set_line(undo_stack_pos->get().to_line, false);
cursor_set_column(undo_stack_pos->get().to_column);
undo_stack_pos = undo_stack_pos->next();
update();
@@ -5904,32 +5078,18 @@ void TextEdit::_push_current_op() {
}
}
-void TextEdit::set_indent_using_spaces(const bool p_use_spaces) {
- indent_using_spaces = p_use_spaces;
-}
-
-bool TextEdit::is_indent_using_spaces() const {
- return indent_using_spaces;
-}
-
-void TextEdit::set_indent_size(const int p_size) {
- ERR_FAIL_COND_MSG(p_size <= 0, "Indend size must be greater than 0.");
- if (indent_size != p_size) {
- indent_size = p_size;
- text.set_indent_size(p_size);
- text.invalidate_all_lines();
- }
-
- space_indent = "";
- for (int i = 0; i < p_size; i++) {
- space_indent += " ";
+void TextEdit::set_tab_size(const int p_size) {
+ ERR_FAIL_COND_MSG(p_size <= 0, "Tab size must be greater than 0.");
+ if (p_size == text.get_tab_size()) {
+ return;
}
-
+ text.set_tab_size(p_size);
+ text.invalidate_all_lines();
update();
}
-int TextEdit::get_indent_size() {
- return indent_size;
+int TextEdit::get_tab_size() const {
+ return text.get_tab_size();
}
void TextEdit::set_draw_tabs(bool p_draw) {
@@ -6093,313 +5253,6 @@ float TextEdit::get_v_scroll_speed() const {
return v_scroll_speed;
}
-void TextEdit::set_completion(bool p_enabled, const Vector<String> &p_prefixes) {
- completion_prefixes.clear();
- completion_enabled = p_enabled;
- for (int i = 0; i < p_prefixes.size(); i++) {
- completion_prefixes.insert(p_prefixes[i]);
- }
-}
-
-void TextEdit::_confirm_completion() {
- begin_complex_operation();
-
- _remove_text(cursor.line, cursor.column - completion_base.length(), cursor.line, cursor.column);
- cursor_set_column(cursor.column - completion_base.length(), false);
- insert_text_at_cursor(completion_current.insert_text);
-
- // When inserted into the middle of an existing string/method, don't add an unnecessary quote/bracket.
- String line = text[cursor.line];
- char32_t next_char = line[cursor.column];
- char32_t last_completion_char = completion_current.insert_text[completion_current.insert_text.length() - 1];
- char32_t last_completion_char_display = completion_current.display[completion_current.display.length() - 1];
-
- if ((last_completion_char == '"' || last_completion_char == '\'') && (last_completion_char == next_char || last_completion_char_display == next_char)) {
- _remove_text(cursor.line, cursor.column, cursor.line, cursor.column + 1);
- }
-
- if (last_completion_char == '(') {
- if (next_char == last_completion_char) {
- _base_remove_text(cursor.line, cursor.column - 1, cursor.line, cursor.column);
- } else if (auto_brace_completion_enabled) {
- insert_text_at_cursor(")");
- cursor.column--;
- }
- } else if (last_completion_char == ')' && next_char == '(') {
- _base_remove_text(cursor.line, cursor.column - 2, cursor.line, cursor.column);
- if (line[cursor.column + 1] != ')') {
- cursor.column--;
- }
- }
-
- end_complex_operation();
-
- _cancel_completion();
-
- if (last_completion_char == '(') {
- query_code_comple();
- }
-}
-
-void TextEdit::_cancel_code_hint() {
- completion_hint = "";
- update();
-}
-
-void TextEdit::_cancel_completion() {
- if (!completion_active) {
- return;
- }
-
- completion_active = false;
- completion_forced = false;
- update();
-}
-
-static bool _is_completable(char32_t c) {
- return !_is_symbol(c) || c == '"' || c == '\'';
-}
-
-void TextEdit::_update_completion_candidates() {
- String l = text[cursor.line];
- int cofs = CLAMP(cursor.column, 0, l.length());
-
- String s;
-
- // Look for keywords first.
-
- bool inquote = false;
- int first_quote = -1;
- int restore_quotes = -1;
-
- int c = cofs - 1;
- while (c >= 0) {
- if (l[c] == '"' || l[c] == '\'') {
- inquote = !inquote;
- if (first_quote == -1) {
- first_quote = c;
- }
- restore_quotes = 0;
- } else if (restore_quotes == 0 && l[c] == '$') {
- restore_quotes = 1;
- } else if (restore_quotes == 0 && !_is_whitespace(l[c])) {
- restore_quotes = -1;
- }
- c--;
- }
-
- bool pre_keyword = false;
- bool cancel = false;
-
- if (!inquote && first_quote == cofs - 1) {
- // No completion here.
- cancel = true;
- } else if (inquote && first_quote != -1) {
- s = l.substr(first_quote, cofs - first_quote);
- } else if (cofs > 0 && l[cofs - 1] == ' ') {
- int kofs = cofs - 1;
- String kw;
- while (kofs >= 0 && l[kofs] == ' ') {
- kofs--;
- }
-
- while (kofs >= 0 && l[kofs] > 32 && _is_completable(l[kofs])) {
- kw = String::chr(l[kofs]) + kw;
- kofs--;
- }
-
- pre_keyword = keywords.has(kw);
-
- } else {
- while (cofs > 0 && l[cofs - 1] > 32 && (l[cofs - 1] == '/' || _is_completable(l[cofs - 1]))) {
- s = String::chr(l[cofs - 1]) + s;
- if (l[cofs - 1] == '\'' || l[cofs - 1] == '"' || l[cofs - 1] == '$') {
- break;
- }
-
- cofs--;
- }
- }
-
- if (cursor.column > 0 && l[cursor.column - 1] == '(' && !pre_keyword && !completion_forced) {
- cancel = true;
- }
-
- update();
-
- bool prev_is_prefix = false;
- if (cofs > 0 && completion_prefixes.has(String::chr(l[cofs - 1]))) {
- prev_is_prefix = true;
- }
- // Check with one space before prefix, to allow indent.
- if (cofs > 1 && l[cofs - 1] == ' ' && completion_prefixes.has(String::chr(l[cofs - 2]))) {
- prev_is_prefix = true;
- }
-
- if (cancel || (!pre_keyword && s == "" && (cofs == 0 || !prev_is_prefix))) {
- // None to complete, cancel.
- _cancel_completion();
- return;
- }
-
- completion_options.clear();
- completion_index = 0;
- completion_base = s;
- Vector<float> sim_cache;
- bool single_quote = s.begins_with("'");
- Vector<ScriptCodeCompletionOption> completion_options_casei;
- Vector<ScriptCodeCompletionOption> completion_options_subseq;
- Vector<ScriptCodeCompletionOption> completion_options_subseq_casei;
-
- String s_lower = s.to_lower();
-
- for (List<ScriptCodeCompletionOption>::Element *E = completion_sources.front(); E; E = E->next()) {
- ScriptCodeCompletionOption &option = E->get();
-
- if (single_quote && option.display.is_quoted()) {
- option.display = option.display.unquote().quote("'");
- }
-
- if (inquote && restore_quotes == 1 && !option.display.is_quoted()) {
- String quote = single_quote ? "'" : "\"";
- option.display = option.display.quote(quote);
- option.insert_text = option.insert_text.quote(quote);
- }
-
- if (option.display.length() == 0) {
- continue;
- } else if (s.length() == 0) {
- completion_options.push_back(option);
- } else {
- // This code works the same as:
- /*
- if (option.display.begins_with(s)) {
- completion_options.push_back(option);
- } else if (option.display.to_lower().begins_with(s.to_lower())) {
- completion_options_casei.push_back(option);
- } else if (s.is_subsequence_of(option.display)) {
- completion_options_subseq.push_back(option);
- } else if (s.is_subsequence_ofi(option.display)) {
- completion_options_subseq_casei.push_back(option);
- }
- */
- // But is more performant due to being inlined and looping over the characters only once
-
- String display_lower = option.display.to_lower();
-
- const char32_t *ssq = &s[0];
- const char32_t *ssq_lower = &s_lower[0];
-
- const char32_t *tgt = &option.display[0];
- const char32_t *tgt_lower = &display_lower[0];
-
- const char32_t *ssq_last_tgt = nullptr;
- const char32_t *ssq_lower_last_tgt = nullptr;
-
- for (; *tgt; tgt++, tgt_lower++) {
- if (*ssq == *tgt) {
- ssq++;
- ssq_last_tgt = tgt;
- }
- if (*ssq_lower == *tgt_lower) {
- ssq_lower++;
- ssq_lower_last_tgt = tgt;
- }
- }
-
- if (!*ssq) { // Matched the whole subsequence in s
- if (ssq_last_tgt == &option.display[s.length() - 1]) { // Finished matching in the first s.length() characters
- completion_options.push_back(option);
- } else {
- completion_options_subseq.push_back(option);
- }
- } else if (!*ssq_lower) { // Matched the whole subsequence in s_lower
- if (ssq_lower_last_tgt == &option.display[s.length() - 1]) { // Finished matching in the first s.length() characters
- completion_options_casei.push_back(option);
- } else {
- completion_options_subseq_casei.push_back(option);
- }
- }
- }
- }
-
- completion_options.append_array(completion_options_casei);
- completion_options.append_array(completion_options_subseq);
- completion_options.append_array(completion_options_subseq_casei);
-
- if (completion_options.size() == 0) {
- // No options to complete, cancel.
- _cancel_completion();
- return;
- }
-
- if (completion_options.size() == 1 && s == completion_options[0].display) {
- // A perfect match, stop completion.
- _cancel_completion();
- return;
- }
-
- // The top of the list is the best match.
- completion_current = completion_options[0];
- completion_enabled = true;
-}
-
-void TextEdit::query_code_comple() {
- String l = text[cursor.line];
- int ofs = CLAMP(cursor.column, 0, l.length());
-
- bool inquote = false;
-
- int c = ofs - 1;
- while (c >= 0) {
- if (l[c] == '"' || l[c] == '\'') {
- inquote = !inquote;
- }
- c--;
- }
-
- bool ignored = completion_active && !completion_options.is_empty();
- if (ignored) {
- ScriptCodeCompletionOption::Kind kind = ScriptCodeCompletionOption::KIND_PLAIN_TEXT;
- const ScriptCodeCompletionOption *previous_option = nullptr;
- for (int i = 0; i < completion_options.size(); i++) {
- const ScriptCodeCompletionOption &current_option = completion_options[i];
- if (!previous_option) {
- previous_option = &current_option;
- kind = current_option.kind;
- }
- if (previous_option->kind != current_option.kind) {
- ignored = false;
- break;
- }
- }
- ignored = ignored && (kind == ScriptCodeCompletionOption::KIND_FILE_PATH || kind == ScriptCodeCompletionOption::KIND_NODE_PATH || kind == ScriptCodeCompletionOption::KIND_SIGNAL);
- }
-
- if (!ignored) {
- if (ofs > 0 && (inquote || _is_completable(l[ofs - 1]) || completion_prefixes.has(String::chr(l[ofs - 1])))) {
- emit_signal("request_completion");
- } else if (ofs > 1 && l[ofs - 1] == ' ' && completion_prefixes.has(String::chr(l[ofs - 2]))) { // Make it work with a space too, it's good enough.
- emit_signal("request_completion");
- }
- }
-}
-
-void TextEdit::set_code_hint(const String &p_hint) {
- completion_hint = p_hint;
- completion_hint_offset = -0xFFFF;
- update();
-}
-
-void TextEdit::code_complete(const List<ScriptCodeCompletionOption> &p_strings, bool p_forced) {
- completion_sources = p_strings;
- completion_active = true;
- completion_forced = p_forced;
- completion_current = ScriptCodeCompletionOption();
- completion_index = 0;
- _update_completion_candidates();
-}
-
String TextEdit::get_word_at_pos(const Vector2 &p_pos) const {
int row, col;
_get_mouse_pos(p_pos, row, col);
@@ -6829,12 +5682,18 @@ void TextEdit::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_language", "language"), &TextEdit::set_language);
ClassDB::bind_method(D_METHOD("get_language"), &TextEdit::get_language);
+ ClassDB::bind_method(D_METHOD("get_first_non_whitespace_column", "line"), &TextEdit::get_first_non_whitespace_column);
+ ClassDB::bind_method(D_METHOD("get_indent_level", "line"), &TextEdit::get_indent_level);
+ ClassDB::bind_method(D_METHOD("set_tab_size", "size"), &TextEdit::set_tab_size);
+ ClassDB::bind_method(D_METHOD("get_tab_size"), &TextEdit::get_tab_size);
+
ClassDB::bind_method(D_METHOD("set_text", "text"), &TextEdit::set_text);
ClassDB::bind_method(D_METHOD("insert_text_at_cursor", "text"), &TextEdit::insert_text_at_cursor);
ClassDB::bind_method(D_METHOD("get_line_count"), &TextEdit::get_line_count);
ClassDB::bind_method(D_METHOD("get_text"), &TextEdit::get_text);
ClassDB::bind_method(D_METHOD("get_line", "line"), &TextEdit::get_line);
+ ClassDB::bind_method(D_METHOD("get_visible_line_count"), &TextEdit::get_total_visible_rows);
ClassDB::bind_method(D_METHOD("set_line", "line", "new_text"), &TextEdit::set_line);
ClassDB::bind_method(D_METHOD("set_structured_text_bidi_override", "parser"), &TextEdit::set_structured_text_bidi_override);
@@ -6846,6 +5705,8 @@ void TextEdit::_bind_methods() {
ClassDB::bind_method(D_METHOD("cursor_set_column", "column", "adjust_viewport"), &TextEdit::cursor_set_column, DEFVAL(true));
ClassDB::bind_method(D_METHOD("cursor_set_line", "line", "adjust_viewport", "can_be_hidden", "wrap_index"), &TextEdit::cursor_set_line, DEFVAL(true), DEFVAL(true), DEFVAL(0));
+ ClassDB::bind_method(D_METHOD("get_caret_draw_pos"), &TextEdit::get_caret_draw_pos);
+ ClassDB::bind_method(D_METHOD("is_caret_visible"), &TextEdit::is_caret_visible);
ClassDB::bind_method(D_METHOD("cursor_get_column"), &TextEdit::cursor_get_column);
ClassDB::bind_method(D_METHOD("cursor_get_line"), &TextEdit::cursor_get_line);
ClassDB::bind_method(D_METHOD("cursor_set_blink_enabled", "enable"), &TextEdit::cursor_set_blink_enabled);
@@ -6880,6 +5741,10 @@ void TextEdit::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_selecting_enabled", "enable"), &TextEdit::set_selecting_enabled);
ClassDB::bind_method(D_METHOD("is_selecting_enabled"), &TextEdit::is_selecting_enabled);
+ ClassDB::bind_method(D_METHOD("delete_selection"), &TextEdit::delete_selection);
+ ClassDB::bind_method(D_METHOD("backspace"), &TextEdit::backspace);
+ BIND_VMETHOD(MethodInfo("_backspace"));
+
ClassDB::bind_method(D_METHOD("cut"), &TextEdit::cut);
ClassDB::bind_method(D_METHOD("copy"), &TextEdit::copy);
ClassDB::bind_method(D_METHOD("paste"), &TextEdit::paste);
@@ -6906,18 +5771,6 @@ void TextEdit::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_draw_spaces"), &TextEdit::set_draw_spaces);
ClassDB::bind_method(D_METHOD("is_drawing_spaces"), &TextEdit::is_drawing_spaces);
- ClassDB::bind_method(D_METHOD("set_hiding_enabled", "enable"), &TextEdit::set_hiding_enabled);
- ClassDB::bind_method(D_METHOD("is_hiding_enabled"), &TextEdit::is_hiding_enabled);
- ClassDB::bind_method(D_METHOD("set_line_as_hidden", "line", "enable"), &TextEdit::set_line_as_hidden);
- ClassDB::bind_method(D_METHOD("is_line_hidden", "line"), &TextEdit::is_line_hidden);
- ClassDB::bind_method(D_METHOD("fold_all_lines"), &TextEdit::fold_all_lines);
- ClassDB::bind_method(D_METHOD("unhide_all_lines"), &TextEdit::unhide_all_lines);
- ClassDB::bind_method(D_METHOD("fold_line", "line"), &TextEdit::fold_line);
- ClassDB::bind_method(D_METHOD("unfold_line", "line"), &TextEdit::unfold_line);
- ClassDB::bind_method(D_METHOD("toggle_fold_line", "line"), &TextEdit::toggle_fold_line);
- ClassDB::bind_method(D_METHOD("can_fold", "line"), &TextEdit::can_fold);
- ClassDB::bind_method(D_METHOD("is_folded", "line"), &TextEdit::is_folded);
-
ClassDB::bind_method(D_METHOD("set_highlight_all_occurrences", "enable"), &TextEdit::set_highlight_all_occurrences);
ClassDB::bind_method(D_METHOD("is_highlight_all_occurrences_enabled"), &TextEdit::is_highlight_all_occurrences_enabled);
@@ -6947,6 +5800,7 @@ void TextEdit::_bind_methods() {
ClassDB::bind_method(D_METHOD("is_gutter_clickable", "gutter"), &TextEdit::is_gutter_clickable);
ClassDB::bind_method(D_METHOD("set_gutter_overwritable", "gutter", "overwritable"), &TextEdit::set_gutter_overwritable);
ClassDB::bind_method(D_METHOD("is_gutter_overwritable", "gutter"), &TextEdit::is_gutter_overwritable);
+ ClassDB::bind_method(D_METHOD("merge_gutters", "from_line", "to_line"), &TextEdit::merge_gutters);
ClassDB::bind_method(D_METHOD("set_gutter_custom_draw", "column", "object", "callback"), &TextEdit::set_gutter_custom_draw);
// Line gutters.
@@ -6961,6 +5815,10 @@ void TextEdit::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_line_gutter_clickable", "line", "gutter", "clickable"), &TextEdit::set_line_gutter_clickable);
ClassDB::bind_method(D_METHOD("is_line_gutter_clickable", "line", "gutter"), &TextEdit::is_line_gutter_clickable);
+ // Line style
+ ClassDB::bind_method(D_METHOD("set_line_background_color", "line", "color"), &TextEdit::set_line_background_color);
+ ClassDB::bind_method(D_METHOD("get_line_background_color", "line"), &TextEdit::get_line_background_color);
+
ClassDB::bind_method(D_METHOD("set_highlight_current_line", "enabled"), &TextEdit::set_highlight_current_line);
ClassDB::bind_method(D_METHOD("is_highlight_current_line_enabled"), &TextEdit::is_highlight_current_line_enabled);
@@ -6982,7 +5840,7 @@ void TextEdit::_bind_methods() {
ClassDB::bind_method(D_METHOD("get_minimap_width"), &TextEdit::get_minimap_width);
ADD_PROPERTY(PropertyInfo(Variant::STRING, "text", PROPERTY_HINT_MULTILINE_TEXT), "set_text", "get_text");
- ADD_PROPERTY(PropertyInfo(Variant::INT, "text_direction", PROPERTY_HINT_ENUM, "Auto,LTR,RTL,Inherited"), "set_text_direction", "get_text_direction");
+ ADD_PROPERTY(PropertyInfo(Variant::INT, "text_direction", PROPERTY_HINT_ENUM, "Auto,Left-to-Right,Right-to-Left,Inherited"), "set_text_direction", "get_text_direction");
ADD_PROPERTY(PropertyInfo(Variant::STRING, "language"), "set_language", "get_language");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "draw_control_chars"), "set_draw_control_chars", "get_draw_control_chars");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "readonly"), "set_readonly", "is_readonly");
@@ -6997,7 +5855,6 @@ void TextEdit::_bind_methods() {
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "selecting_enabled"), "set_selecting_enabled", "is_selecting_enabled");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "smooth_scrolling"), "set_smooth_scroll_enable", "is_smooth_scroll_enabled");
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "v_scroll_speed"), "set_v_scroll_speed", "get_v_scroll_speed");
- ADD_PROPERTY(PropertyInfo(Variant::BOOL, "hiding_enabled"), "set_hiding_enabled", "is_hiding_enabled");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "wrap_enabled"), "set_wrap_enabled", "is_wrap_enabled");
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "scroll_vertical"), "set_v_scroll", "get_v_scroll");
ADD_PROPERTY(PropertyInfo(Variant::INT, "scroll_horizontal"), "set_h_scroll", "get_h_scroll");
@@ -7022,7 +5879,6 @@ void TextEdit::_bind_methods() {
ADD_SIGNAL(MethodInfo("cursor_changed"));
ADD_SIGNAL(MethodInfo("text_changed"));
ADD_SIGNAL(MethodInfo("lines_edited_from", PropertyInfo(Variant::INT, "from_line"), PropertyInfo(Variant::INT, "to_line")));
- ADD_SIGNAL(MethodInfo("request_completion"));
ADD_SIGNAL(MethodInfo("gutter_clicked", PropertyInfo(Variant::INT, "line"), PropertyInfo(Variant::INT, "gutter")));
ADD_SIGNAL(MethodInfo("gutter_added"));
ADD_SIGNAL(MethodInfo("gutter_removed"));
@@ -7071,7 +5927,7 @@ TextEdit::TextEdit() {
_update_caches();
set_default_cursor_shape(CURSOR_IBEAM);
- text.set_indent_size(indent_size);
+ text.set_tab_size(text.get_tab_size());
text.clear();
h_scroll = memnew(HScrollBar);
diff --git a/scene/gui/text_edit.h b/scene/gui/text_edit.h
index b0c7314c65..dcd5c6d0f8 100644
--- a/scene/gui/text_edit.h
+++ b/scene/gui/text_edit.h
@@ -92,11 +92,11 @@ private:
Vector<Vector2i> bidi_override;
Ref<TextParagraph> data_buf;
- bool marked = false;
+ Color background_color = Color(0, 0, 0, 0);
bool hidden = false;
Line() {
- data_buf.instance();
+ data_buf.instantiate();
}
};
@@ -112,11 +112,12 @@ private:
int width = -1;
- int indent_size = 4;
+ int tab_size = 4;
int gutter_count = 0;
public:
- void set_indent_size(int p_indent_size);
+ void set_tab_size(int p_tab_size);
+ int get_tab_size() const;
void set_font(const Ref<Font> &p_font);
void set_font_size(int p_font_size);
void set_font_features(const Dictionary &p_features);
@@ -124,17 +125,16 @@ private:
void set_draw_control_chars(bool p_draw_control_chars);
int get_line_height(int p_line, int p_wrap_index) const;
- int get_line_width(int p_line) const;
+ int get_line_width(int p_line, int p_wrap_index = -1) const;
int get_max_width(bool p_exclude_hidden = false) const;
void set_width(float p_width);
int get_line_wrap_amount(int p_line) const;
+
Vector<Vector2i> get_line_wrap_ranges(int p_line) const;
const Ref<TextParagraph> get_line_data(int p_line) const;
void set(int p_line, const String &p_text, const Vector<Vector2i> &p_bidi_override);
- void set_marked(int p_line, bool p_marked) { text.write[p_line].marked = p_marked; }
- bool is_marked(int p_line) const { return text[p_line].marked; }
void set_hidden(int p_line, bool p_hidden) { text.write[p_line].hidden = p_hidden; }
bool is_hidden(int p_line) const { return text[p_line].hidden; }
void insert(int p_at, const String &p_text, const Vector<Vector2i> &p_bidi_override);
@@ -167,9 +167,15 @@ private:
void set_line_gutter_clickable(int p_line, int p_gutter, bool p_clickable) { text.write[p_line].gutters.write[p_gutter].clickable = p_clickable; }
bool is_line_gutter_clickable(int p_line, int p_gutter) const { return text[p_line].gutters[p_gutter].clickable; }
+
+ /* Line style. */
+ void set_line_background_color(int p_line, const Color &p_color) { text.write[p_line].background_color = p_color; }
+ const Color get_line_background_color(int p_line) const { return text[p_line].background_color; }
};
struct Cursor {
+ Point2 draw_pos;
+ bool visible = false;
int last_fit_x = 0;
int line = 0;
int column = 0; ///< cursor
@@ -236,20 +242,6 @@ private:
Dictionary _get_line_syntax_highlighting(int p_line);
- Set<String> completion_prefixes;
- bool completion_enabled = false;
- List<ScriptCodeCompletionOption> completion_sources;
- Vector<ScriptCodeCompletionOption> completion_options;
- bool completion_active = false;
- bool completion_forced = false;
- ScriptCodeCompletionOption completion_current;
- String completion_base;
- int completion_index = 0;
- Rect2i completion_rect;
- int completion_line_ofs = 0;
- String completion_hint;
- int completion_hint_offset = 0;
-
bool setting_text = false;
// data
@@ -266,11 +258,7 @@ private:
uint32_t version = 0;
uint32_t saved_version = 0;
- int max_chars = 0;
bool readonly = true; // Initialise to opposite first, so we get past the early-out in set_readonly.
- bool indent_using_spaces = false;
- int indent_size = 4;
- String space_indent = " ";
Timer *caret_blink_timer;
bool caret_blink_enabled = false;
@@ -303,10 +291,9 @@ private:
bool highlight_all_occurrences = false;
bool scroll_past_end_of_file_enabled = false;
- bool auto_brace_completion_enabled = false;
bool brace_matching_enabled = false;
bool highlight_current_line = false;
- bool auto_indent = false;
+
String cut_copy_line;
bool insert_mode = false;
bool select_identifiers_enabled = false;
@@ -338,9 +325,6 @@ private:
bool next_operation_is_complex = false;
- bool callhint_below = false;
- Vector2 callhint_offset;
-
String search_text;
uint32_t search_flags = 0;
int search_result_line = 0;
@@ -361,11 +345,8 @@ private:
void update_cursor_wrap_offset();
void _update_wrap_at(bool p_force = false);
- bool line_wraps(int line) const;
- int times_line_wraps(int line) const;
Vector<String> get_wrap_rows_text(int p_line) const;
int get_cursor_wrap_index() const;
- int get_line_wrap_index_at_col(int p_line, int p_column) const;
int get_char_count();
double get_scroll_pos_for_line(int p_line, int p_wrap_index = 0) const;
@@ -434,19 +415,10 @@ private:
PopupMenu *menu_ctl;
void _clear();
- void _cancel_completion();
- void _cancel_code_hint();
- void _confirm_completion();
- void _update_completion_candidates();
-
- int _calculate_spaces_till_next_left_indent(int column);
- int _calculate_spaces_till_next_right_indent(int column);
// Methods used in shortcuts
void _swap_current_input_direction();
void _new_line(bool p_split_current = true, bool p_above = false);
- void _indent_right();
- void _indent_left();
void _move_cursor_left(bool p_select, bool p_move_by_word = false);
void _move_cursor_right(bool p_select, bool p_move_by_word = false);
void _move_cursor_up(bool p_select);
@@ -455,14 +427,15 @@ private:
void _move_cursor_to_line_end(bool p_select);
void _move_cursor_page_up(bool p_select);
void _move_cursor_page_down(bool p_select);
- void _backspace(bool p_word = false, bool p_all_to_left = false);
+ void _do_backspace(bool p_word = false, bool p_all_to_left = false);
void _delete(bool p_word = false, bool p_all_to_right = false);
- void _delete_selection();
void _move_cursor_document_start(bool p_select);
void _move_cursor_document_end(bool p_select);
- void _handle_unicode_character(uint32_t unicode, bool p_had_selection, bool p_update_auto_complete);
+ void _handle_unicode_character(uint32_t unicode, bool p_had_selection);
protected:
+ bool auto_brace_completion_enabled = false;
+
struct Cache {
Ref<Texture2D> tab_icon;
Ref<Texture2D> space_icon;
@@ -474,17 +447,12 @@ protected:
int font_size = 16;
int outline_size = 0;
Color outline_color;
- Color completion_background_color;
- Color completion_selected_color;
- Color completion_existing_color;
- Color completion_font_color;
Color caret_color;
Color caret_background_color;
Color font_color;
Color font_selected_color;
Color font_readonly_color;
Color selection_color;
- Color mark_color;
Color code_folding_color;
Color current_line_color;
Color line_length_guideline_color;
@@ -503,7 +471,7 @@ protected:
void _insert_text(int p_line, int p_char, const String &p_text, int *r_end_line = nullptr, int *r_end_char = nullptr);
void _remove_text(int p_from_line, int p_from_column, int p_to_line, int p_to_column);
void _insert_text_at_cursor(const String &p_text);
- void _gui_input(const Ref<InputEvent> &p_gui_input);
+ virtual void _gui_input(const Ref<InputEvent> &p_gui_input);
void _notification(int p_what);
void _consume_pair_symbol(char32_t ch);
@@ -533,6 +501,7 @@ public:
void set_gutter_width(int p_gutter, int p_width);
int get_gutter_width(int p_gutter) const;
+ int get_total_gutter_width() const;
void set_gutter_draw(int p_gutter, bool p_draw);
bool is_gutter_drawn(int p_gutter) const;
@@ -543,6 +512,8 @@ public:
void set_gutter_overwritable(int p_gutter, bool p_overwritable);
bool is_gutter_overwritable(int p_gutter) const;
+ void merge_gutters(int p_from_line, int p_to_line);
+
void set_gutter_custom_draw(int p_gutter, Object *p_object, const StringName &p_callback);
// Line gutters.
@@ -561,6 +532,10 @@ public:
void set_line_gutter_clickable(int p_line, int p_gutter, bool p_clickable);
bool is_line_gutter_clickable(int p_line, int p_gutter) const;
+ // Line style
+ void set_line_background_color(int p_line, const Color &p_color);
+ Color get_line_background_color(int p_line);
+
enum MenuItems {
MENU_CUT,
MENU_COPY,
@@ -637,33 +612,24 @@ public:
void insert_text_at_cursor(const String &p_text);
void insert_at(const String &p_text, int at);
int get_line_count() const;
- void set_line_as_marked(int p_line, bool p_marked);
+ int get_line_width(int p_line, int p_wrap_offset = -1) const;
+ int get_line_wrap_index_at_col(int p_line, int p_column) const;
void set_line_as_hidden(int p_line, bool p_hidden);
bool is_line_hidden(int p_line) const;
- void fold_all_lines();
void unhide_all_lines();
int num_lines_from(int p_line_from, int visible_amount) const;
int num_lines_from_rows(int p_line_from, int p_wrap_index_from, int visible_amount, int &wrap_index) const;
int get_last_unhidden_line() const;
- bool can_fold(int p_line) const;
- bool is_folded(int p_line) const;
- Vector<int> get_folded_lines() const;
- void fold_line(int p_line);
- void unfold_line(int p_line);
- void toggle_fold_line(int p_line);
-
String get_text();
String get_line(int line) const;
+ bool has_ime_text() const;
void set_line(int line, String new_text);
int get_row_height() const;
- void backspace_at_cursor();
- void indent_selected_lines_left();
- void indent_selected_lines_right();
int get_indent_level(int p_line) const;
- bool is_line_comment(int p_line) const;
+ int get_first_non_whitespace_column(int p_line) const;
inline void set_scroll_pass_end_of_file(bool p_enabled) {
scroll_past_end_of_file_enabled = p_enabled;
@@ -676,11 +642,6 @@ public:
brace_matching_enabled = p_enabled;
update();
}
- inline void set_callhint_settings(bool below, Vector2 offset) {
- callhint_below = below;
- callhint_offset = offset;
- }
- void set_auto_indent(bool p_auto_indent);
void center_viewport_to_cursor();
@@ -690,6 +651,8 @@ public:
void cursor_set_column(int p_col, bool p_adjust_viewport = true);
void cursor_set_line(int p_row, bool p_adjust_viewport = true, bool p_can_be_hidden = true, int p_wrap_index = 0);
+ Point2 get_caret_draw_pos() const;
+ bool is_caret_visible() const;
int cursor_get_column() const;
int cursor_get_line() const;
Vector2i _get_cursor_pixel_pos(bool p_adjust_viewport = true);
@@ -714,18 +677,21 @@ public:
void set_readonly(bool p_readonly);
bool is_readonly() const;
- void set_max_chars(int p_max_chars);
- int get_max_chars() const;
-
void set_wrap_enabled(bool p_wrap_enabled);
bool is_wrap_enabled() const;
+ bool line_wraps(int line) const;
+ int times_line_wraps(int line) const;
void clear();
+ void delete_selection();
+
+ virtual void backspace();
void cut();
void copy();
void paste();
void select_all();
+ void select_word_under_caret();
void select(int p_from_line, int p_from_column, int p_to_line, int p_to_column);
void deselect();
void swap_lines(int line1, int line2);
@@ -752,10 +718,8 @@ public:
void redo();
void clear_undo_history();
- void set_indent_using_spaces(const bool p_use_spaces);
- bool is_indent_using_spaces() const;
- void set_indent_size(const int p_size);
- int get_indent_size();
+ void set_tab_size(const int p_size);
+ int get_tab_size() const;
void set_draw_tabs(bool p_draw);
bool is_drawing_tabs() const;
void set_draw_spaces(bool p_draw);
@@ -766,9 +730,6 @@ public:
void set_insert_mode(bool p_enabled);
bool is_insert_mode() const;
- void add_keyword(const String &p_keyword);
- void clear_keywords();
-
double get_v_scroll() const;
void set_v_scroll(double p_scroll);
@@ -805,11 +766,6 @@ public:
void set_tooltip_request_func(Object *p_obj, const StringName &p_function, const Variant &p_udata);
- void set_completion(bool p_enabled, const Vector<String> &p_prefixes);
- void code_complete(const List<ScriptCodeCompletionOption> &p_strings, bool p_forced = false);
- void set_code_hint(const String &p_hint);
- void query_code_comple();
-
void set_select_identifiers_on_hover(bool p_enable);
bool is_selecting_identifiers_on_hover_enabled() const;
@@ -827,7 +783,6 @@ public:
PopupMenu *get_menu() const;
- String get_text_for_completion();
String get_text_for_lookup_completion();
virtual bool is_text_field() const override;
diff --git a/scene/gui/texture_button.cpp b/scene/gui/texture_button.cpp
index f43e3d1a9d..8659ea06a2 100644
--- a/scene/gui/texture_button.cpp
+++ b/scene/gui/texture_button.cpp
@@ -295,11 +295,13 @@ void TextureButton::set_normal_texture(const Ref<Texture2D> &p_normal) {
void TextureButton::set_pressed_texture(const Ref<Texture2D> &p_pressed) {
pressed = p_pressed;
update();
+ minimum_size_changed();
}
void TextureButton::set_hover_texture(const Ref<Texture2D> &p_hover) {
hover = p_hover;
update();
+ minimum_size_changed();
}
void TextureButton::set_disabled_texture(const Ref<Texture2D> &p_disabled) {
@@ -310,6 +312,7 @@ void TextureButton::set_disabled_texture(const Ref<Texture2D> &p_disabled) {
void TextureButton::set_click_mask(const Ref<BitMap> &p_click_mask) {
click_mask = p_click_mask;
update();
+ minimum_size_changed();
}
Ref<Texture2D> TextureButton::get_normal_texture() const {
diff --git a/scene/gui/texture_progress_bar.cpp b/scene/gui/texture_progress_bar.cpp
index 46ce9d5ca9..5e5dec3579 100644
--- a/scene/gui/texture_progress_bar.cpp
+++ b/scene/gui/texture_progress_bar.cpp
@@ -221,43 +221,87 @@ void TextureProgressBar::draw_nine_patch_stretched(const Ref<Texture2D> &p_textu
double width_texture = 0.0;
double first_section_size = 0.0;
double last_section_size = 0.0;
- switch (mode) {
- case FILL_LEFT_TO_RIGHT:
- case FILL_RIGHT_TO_LEFT: {
+ switch (p_mode) {
+ case FILL_LEFT_TO_RIGHT: {
width_total = dst_rect.size.x;
width_texture = texture_size.x;
first_section_size = topleft.x;
last_section_size = bottomright.x;
} break;
- case FILL_TOP_TO_BOTTOM:
- case FILL_BOTTOM_TO_TOP: {
+ case FILL_RIGHT_TO_LEFT: {
+ width_total = dst_rect.size.x;
+ width_texture = texture_size.x;
+ // In contrast to `FILL_LEFT_TO_RIGHT`, `first_section_size` and `last_section_size` should switch value.
+ first_section_size = bottomright.x;
+ last_section_size = topleft.x;
+ } break;
+ case FILL_TOP_TO_BOTTOM: {
width_total = dst_rect.size.y;
width_texture = texture_size.y;
first_section_size = topleft.y;
last_section_size = bottomright.y;
} break;
+ case FILL_BOTTOM_TO_TOP: {
+ width_total = dst_rect.size.y;
+ width_texture = texture_size.y;
+ // Similar to `FILL_RIGHT_TO_LEFT`.
+ first_section_size = bottomright.y;
+ last_section_size = topleft.y;
+ } break;
case FILL_BILINEAR_LEFT_AND_RIGHT: {
- // TODO: Implement
+ width_total = dst_rect.size.x;
+ width_texture = texture_size.x;
+ first_section_size = topleft.x;
+ last_section_size = bottomright.x;
} break;
case FILL_BILINEAR_TOP_AND_BOTTOM: {
- // TODO: Implement
+ width_total = dst_rect.size.y;
+ width_texture = texture_size.y;
+ first_section_size = topleft.y;
+ last_section_size = bottomright.y;
} break;
case FILL_CLOCKWISE:
case FILL_CLOCKWISE_AND_COUNTER_CLOCKWISE:
case FILL_COUNTER_CLOCKWISE: {
- // Those modes are circular, not relevant for nine patch
+ // Those modes are circular, not relevant for nine patch.
} break;
+ case FILL_MODE_MAX:
+ break;
}
double width_filled = width_total * p_ratio;
double middle_section_size = MAX(0.0, width_texture - first_section_size - last_section_size);
- middle_section_size *= MIN(1.0, (MAX(0.0, width_filled - first_section_size) / MAX(1.0, width_total - first_section_size - last_section_size)));
- last_section_size = MAX(0.0, last_section_size - (width_total - width_filled));
- first_section_size = MIN(first_section_size, width_filled);
- width_texture = MIN(width_texture, first_section_size + middle_section_size + last_section_size);
+ // Maximum middle texture size.
+ double max_middle_texture_size = middle_section_size;
+
+ // Maximum real middle texture size.
+ double max_middle_real_size = MAX(0.0, width_total - (first_section_size + last_section_size));
+
+ switch (p_mode) {
+ case FILL_BILINEAR_LEFT_AND_RIGHT:
+ case FILL_BILINEAR_TOP_AND_BOTTOM: {
+ last_section_size = MAX(0.0, last_section_size - (width_total - width_filled) * 0.5);
+ first_section_size = MAX(0.0, first_section_size - (width_total - width_filled) * 0.5);
+
+ // When `width_filled` increases, `middle_section_size` only increases when either of `first_section_size` and `last_section_size` is zero.
+ // Also, it should always be smaller than or equal to `(width_total - (first_section_size + last_section_size))`.
+ double real_middle_size = width_filled - first_section_size - last_section_size;
+ middle_section_size *= MIN(max_middle_real_size, real_middle_size) / max_middle_real_size;
+
+ width_texture = MIN(width_texture, first_section_size + middle_section_size + last_section_size);
+ } break;
+ case FILL_MODE_MAX:
+ break;
+ default: {
+ middle_section_size *= MIN(1.0, (MAX(0.0, width_filled - first_section_size) / MAX(1.0, width_total - first_section_size - last_section_size)));
+ last_section_size = MAX(0.0, last_section_size - (width_total - width_filled));
+ first_section_size = MIN(first_section_size, width_filled);
+ width_texture = MIN(width_texture, first_section_size + middle_section_size + last_section_size);
+ }
+ }
- switch (mode) {
+ switch (p_mode) {
case FILL_LEFT_TO_RIGHT: {
src_rect.size.x = width_texture;
dst_rect.size.x = width_filled;
@@ -287,16 +331,32 @@ void TextureProgressBar::draw_nine_patch_stretched(const Ref<Texture2D> &p_textu
bottomright.y = first_section_size;
} break;
case FILL_BILINEAR_LEFT_AND_RIGHT: {
- // TODO: Implement
+ double center_mapped_from_real_width = (width_total * 0.5 - topleft.x) / max_middle_real_size * max_middle_texture_size + topleft.x;
+ double drift_from_unscaled_center = (src_rect.size.x * 0.5 - center_mapped_from_real_width) * (last_section_size - first_section_size) / (bottomright.x - topleft.x);
+ src_rect.position.x += center_mapped_from_real_width + drift_from_unscaled_center - width_texture * 0.5;
+ src_rect.size.x = width_texture;
+ dst_rect.position.x += (width_total - width_filled) * 0.5;
+ dst_rect.size.x = width_filled;
+ topleft.x = first_section_size;
+ bottomright.x = last_section_size;
} break;
case FILL_BILINEAR_TOP_AND_BOTTOM: {
- // TODO: Implement
+ double center_mapped_from_real_width = (width_total * 0.5 - topleft.y) / max_middle_real_size * max_middle_texture_size + topleft.y;
+ double drift_from_unscaled_center = (src_rect.size.y * 0.5 - center_mapped_from_real_width) * (last_section_size - first_section_size) / (bottomright.y - topleft.y);
+ src_rect.position.y += center_mapped_from_real_width + drift_from_unscaled_center - width_texture * 0.5;
+ src_rect.size.y = width_texture;
+ dst_rect.position.y += (width_total - width_filled) * 0.5;
+ dst_rect.size.y = width_filled;
+ topleft.y = first_section_size;
+ bottomright.y = last_section_size;
} break;
case FILL_CLOCKWISE:
case FILL_CLOCKWISE_AND_COUNTER_CLOCKWISE:
case FILL_COUNTER_CLOCKWISE: {
- // Those modes are circular, not relevant for nine patch
+ // Those modes are circular, not relevant for nine patch.
} break;
+ case FILL_MODE_MAX:
+ break;
}
}
@@ -310,19 +370,34 @@ void TextureProgressBar::_notification(int p_what) {
const float corners[12] = { -0.125, -0.375, -0.625, -0.875, 0.125, 0.375, 0.625, 0.875, 1.125, 1.375, 1.625, 1.875 };
switch (p_what) {
case NOTIFICATION_DRAW: {
- if (nine_patch_stretch && (mode == FILL_LEFT_TO_RIGHT || mode == FILL_RIGHT_TO_LEFT || mode == FILL_TOP_TO_BOTTOM || mode == FILL_BOTTOM_TO_TOP)) {
+ if (nine_patch_stretch && (mode == FILL_LEFT_TO_RIGHT || mode == FILL_RIGHT_TO_LEFT || mode == FILL_TOP_TO_BOTTOM || mode == FILL_BOTTOM_TO_TOP || mode == FILL_BILINEAR_LEFT_AND_RIGHT || mode == FILL_BILINEAR_TOP_AND_BOTTOM)) {
if (under.is_valid()) {
- draw_nine_patch_stretched(under, FILL_LEFT_TO_RIGHT, 1.0, tint_under);
+ draw_nine_patch_stretched(under, mode, 1.0, tint_under);
}
if (progress.is_valid()) {
draw_nine_patch_stretched(progress, mode, get_as_ratio(), tint_progress);
}
if (over.is_valid()) {
- draw_nine_patch_stretched(over, FILL_LEFT_TO_RIGHT, 1.0, tint_over);
+ draw_nine_patch_stretched(over, mode, 1.0, tint_over);
}
} else {
if (under.is_valid()) {
- draw_texture(under, Point2(), tint_under);
+ switch (mode) {
+ case FILL_CLOCKWISE:
+ case FILL_COUNTER_CLOCKWISE:
+ case FILL_CLOCKWISE_AND_COUNTER_CLOCKWISE: {
+ if (nine_patch_stretch) {
+ Rect2 region = Rect2(Point2(), get_size());
+ draw_texture_rect(under, region, false, tint_under);
+ } else {
+ draw_texture(under, Point2(), tint_under);
+ }
+ } break;
+ case FILL_MODE_MAX:
+ break;
+ default:
+ draw_texture(under, Point2(), tint_under);
+ }
}
if (progress.is_valid()) {
Size2 s = progress->get_size();
@@ -353,7 +428,7 @@ void TextureProgressBar::_notification(int p_what) {
float val = get_as_ratio() * rad_max_degrees / 360;
if (val == 1) {
Rect2 region = Rect2(Point2(), s);
- draw_texture_rect_region(progress, region, region, tint_progress);
+ draw_texture_rect(progress, region, false, tint_progress);
} else if (val != 0) {
Array pts;
float direction = mode == FILL_COUNTER_CLOCKWISE ? -1 : 1;
@@ -416,12 +491,29 @@ void TextureProgressBar::_notification(int p_what) {
Rect2 region = Rect2(Point2(0, s.y / 2 - s.y * get_as_ratio() / 2), Size2(s.x, s.y * get_as_ratio()));
draw_texture_rect_region(progress, region, region, tint_progress);
} break;
+ case FILL_MODE_MAX:
+ break;
default:
draw_texture_rect_region(progress, Rect2(Point2(), Size2(s.x * get_as_ratio(), s.y)), Rect2(Point2(), Size2(s.x * get_as_ratio(), s.y)), tint_progress);
}
}
if (over.is_valid()) {
- draw_texture(over, Point2(), tint_over);
+ switch (mode) {
+ case FILL_CLOCKWISE:
+ case FILL_COUNTER_CLOCKWISE:
+ case FILL_CLOCKWISE_AND_COUNTER_CLOCKWISE: {
+ if (nine_patch_stretch) {
+ Rect2 region = Rect2(Point2(), get_size());
+ draw_texture_rect(over, region, false, tint_over);
+ } else {
+ draw_texture(over, Point2(), tint_over);
+ }
+ } break;
+ case FILL_MODE_MAX:
+ break;
+ default:
+ draw_texture(over, Point2(), tint_over);
+ }
}
}
} break;
@@ -429,7 +521,7 @@ void TextureProgressBar::_notification(int p_what) {
}
void TextureProgressBar::set_fill_mode(int p_fill) {
- ERR_FAIL_INDEX(p_fill, 9);
+ ERR_FAIL_INDEX(p_fill, FILL_MODE_MAX);
mode = (FillMode)p_fill;
update();
}
@@ -512,7 +604,7 @@ void TextureProgressBar::_bind_methods() {
ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "texture_under", PROPERTY_HINT_RESOURCE_TYPE, "Texture2D"), "set_under_texture", "get_under_texture");
ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "texture_over", PROPERTY_HINT_RESOURCE_TYPE, "Texture2D"), "set_over_texture", "get_over_texture");
ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "texture_progress", PROPERTY_HINT_RESOURCE_TYPE, "Texture2D"), "set_progress_texture", "get_progress_texture");
- ADD_PROPERTY(PropertyInfo(Variant::INT, "fill_mode", PROPERTY_HINT_ENUM, "Left to Right,Right to Left,Top to Bottom,Bottom to Top,Clockwise,Counter Clockwise,Bilinear (Left and Right),Bilinear (Top and Bottom), Clockwise and Counter Clockwise"), "set_fill_mode", "get_fill_mode");
+ ADD_PROPERTY(PropertyInfo(Variant::INT, "fill_mode", PROPERTY_HINT_ENUM, "Left to Right,Right to Left,Top to Bottom,Bottom to Top,Clockwise,Counter Clockwise,Bilinear (Left and Right),Bilinear (Top and Bottom),Clockwise and Counter Clockwise"), "set_fill_mode", "get_fill_mode");
ADD_GROUP("Tint", "tint_");
ADD_PROPERTY(PropertyInfo(Variant::COLOR, "tint_under"), "set_tint_under", "get_tint_under");
ADD_PROPERTY(PropertyInfo(Variant::COLOR, "tint_over"), "set_tint_over", "get_tint_over");
diff --git a/scene/gui/texture_progress_bar.h b/scene/gui/texture_progress_bar.h
index a3883a7017..d147c43a26 100644
--- a/scene/gui/texture_progress_bar.h
+++ b/scene/gui/texture_progress_bar.h
@@ -54,7 +54,8 @@ public:
FILL_COUNTER_CLOCKWISE,
FILL_BILINEAR_LEFT_AND_RIGHT,
FILL_BILINEAR_TOP_AND_BOTTOM,
- FILL_CLOCKWISE_AND_COUNTER_CLOCKWISE
+ FILL_CLOCKWISE_AND_COUNTER_CLOCKWISE,
+ FILL_MODE_MAX,
};
void set_fill_mode(int p_fill);
diff --git a/scene/gui/tree.cpp b/scene/gui/tree.cpp
index abfea241f3..4d2cb81f23 100644
--- a/scene/gui/tree.cpp
+++ b/scene/gui/tree.cpp
@@ -47,36 +47,6 @@
#include <limits.h>
-void TreeItem::move_to_top() {
- if (!parent || parent->children == this) {
- return; //already on top
- }
- TreeItem *prev = get_prev();
- prev->next = next;
- next = parent->children;
- parent->children = this;
-}
-
-void TreeItem::move_to_bottom() {
- if (!parent || !next) {
- return;
- }
-
- TreeItem *prev = get_prev();
- TreeItem *last = next;
- while (last->next) {
- last = last->next;
- }
-
- if (prev) {
- prev->next = next;
- } else {
- parent->children = next;
- }
- last->next = this;
- next = nullptr;
-}
-
Size2 TreeItem::Cell::get_icon_size() const {
if (icon.is_null()) {
return Size2();
@@ -118,6 +88,59 @@ void TreeItem::_cell_deselected(int p_cell) {
tree->item_deselected(p_cell, this);
}
+void TreeItem::_change_tree(Tree *p_tree) {
+ if (p_tree == tree) {
+ return;
+ }
+
+ TreeItem *c = first_child;
+ while (c) {
+ c->_change_tree(p_tree);
+ c = c->next;
+ }
+
+ if (tree) {
+ if (tree->root == this) {
+ tree->root = nullptr;
+ }
+
+ if (tree->popup_edited_item == this) {
+ tree->popup_edited_item = nullptr;
+ tree->pressing_for_editor = false;
+ }
+
+ if (tree->cache.hover_item == this) {
+ tree->cache.hover_item = nullptr;
+ }
+
+ if (tree->selected_item == this) {
+ tree->selected_item = nullptr;
+ }
+
+ if (tree->drop_mode_over == this) {
+ tree->drop_mode_over = nullptr;
+ }
+
+ if (tree->single_select_defer == this) {
+ tree->single_select_defer = nullptr;
+ }
+
+ if (tree->edited_item == this) {
+ tree->edited_item = nullptr;
+ tree->pressing_for_editor = false;
+ }
+
+ tree->update();
+ }
+
+ tree = p_tree;
+
+ if (tree) {
+ tree->update();
+ cells.resize(tree->columns.size());
+ }
+}
+
/* cell mode */
void TreeItem::set_cell_mode(int p_column, TreeCellMode p_mode) {
ERR_FAIL_INDEX(p_column, cells.size());
@@ -427,29 +450,84 @@ int TreeItem::get_custom_minimum_height() const {
return custom_min_height;
}
-TreeItem *TreeItem::get_next() {
+/* Item manipulation */
+
+TreeItem *TreeItem::create_child(int p_idx) {
+ TreeItem *ti = memnew(TreeItem(tree));
+ if (tree) {
+ ti->cells.resize(tree->columns.size());
+ tree->update();
+ }
+
+ TreeItem *l_prev = nullptr;
+ TreeItem *c = first_child;
+ int idx = 0;
+
+ while (c) {
+ if (idx++ == p_idx) {
+ c->prev = ti;
+ ti->next = c;
+ break;
+ }
+ l_prev = c;
+ c = c->next;
+ }
+
+ if (l_prev) {
+ l_prev->next = ti;
+ ti->prev = l_prev;
+ if (!children_cache.is_empty()) {
+ if (ti->next) {
+ children_cache.insert(p_idx, ti);
+ } else {
+ children_cache.append(ti);
+ }
+ }
+ } else {
+ first_child = ti;
+ if (!children_cache.is_empty()) {
+ children_cache.insert(0, ti);
+ }
+ }
+
+ ti->parent = this;
+
+ return ti;
+}
+
+Tree *TreeItem::get_tree() const {
+ return tree;
+}
+
+TreeItem *TreeItem::get_next() const {
return next;
}
TreeItem *TreeItem::get_prev() {
- if (!parent || parent->children == this) {
- return nullptr;
+ if (prev) {
+ return prev;
}
- TreeItem *prev = parent->children;
- while (prev && prev->next != this) {
- prev = prev->next;
+ if (!parent || parent->first_child == this) {
+ return nullptr;
+ }
+ // This is an edge case
+ TreeItem *l_prev = parent->first_child;
+ while (l_prev && l_prev->next != this) {
+ l_prev = l_prev->next;
}
+ prev = l_prev;
+
return prev;
}
-TreeItem *TreeItem::get_parent() {
+TreeItem *TreeItem::get_parent() const {
return parent;
}
-TreeItem *TreeItem::get_children() {
- return children;
+TreeItem *TreeItem::get_first_child() const {
+ return first_child;
}
TreeItem *TreeItem::get_prev_visible(bool p_wrap) {
@@ -475,10 +553,10 @@ TreeItem *TreeItem::get_prev_visible(bool p_wrap) {
}
} else {
current = prev;
- while (!current->collapsed && current->children) {
+ while (!current->collapsed && current->first_child) {
//go to the very end
- current = current->children;
+ current = current->first_child;
while (current->next) {
current = current->next;
}
@@ -491,8 +569,8 @@ TreeItem *TreeItem::get_prev_visible(bool p_wrap) {
TreeItem *TreeItem::get_next_visible(bool p_wrap) {
TreeItem *current = this;
- if (!current->collapsed && current->children) {
- current = current->children;
+ if (!current->collapsed && current->first_child) {
+ current = current->first_child;
} else if (current->next) {
current = current->next;
@@ -515,24 +593,128 @@ TreeItem *TreeItem::get_next_visible(bool p_wrap) {
return current;
}
-void TreeItem::remove_child(TreeItem *p_item) {
+TreeItem *TreeItem::get_child(int p_idx) {
+ _create_children_cache();
+ ERR_FAIL_INDEX_V(p_idx, children_cache.size(), nullptr);
+ return children_cache.get(p_idx);
+}
+
+int TreeItem::get_child_count() {
+ _create_children_cache();
+ return children_cache.size();
+}
+
+Array TreeItem::get_children() {
+ int size = get_child_count();
+ Array arr;
+ arr.resize(size);
+ for (int i = 0; i < size; i++) {
+ arr[i] = children_cache[i];
+ }
+
+ return arr;
+}
+
+int TreeItem::get_index() {
+ int idx = 0;
+ TreeItem *c = this;
+
+ while (c) {
+ c = c->get_prev();
+ idx++;
+ }
+ return idx - 1;
+}
+
+void TreeItem::move_before(TreeItem *p_item) {
ERR_FAIL_NULL(p_item);
- TreeItem **c = &children;
+ ERR_FAIL_COND(is_root);
+ ERR_FAIL_COND(!p_item->parent);
- while (*c) {
- if ((*c) == p_item) {
- TreeItem *aux = *c;
+ if (p_item == this) {
+ return;
+ }
+
+ TreeItem *p = p_item->parent;
+ while (p) {
+ ERR_FAIL_COND_MSG(p == this, "Can't move to a descendant");
+ p = p->parent;
+ }
- *c = (*c)->next;
+ Tree *old_tree = tree;
+ _unlink_from_tree();
+ _change_tree(p_item->tree);
- aux->parent = nullptr;
- return;
- }
+ parent = p_item->parent;
- c = &(*c)->next;
+ TreeItem *item_prev = p_item->get_prev();
+ if (item_prev) {
+ item_prev->next = this;
+ parent->children_cache.clear();
+ } else {
+ parent->first_child = this;
+ parent->children_cache.insert(0, this);
}
- ERR_FAIL();
+ prev = item_prev;
+ next = p_item;
+ p_item->prev = this;
+
+ if (tree && old_tree == tree) {
+ tree->update();
+ }
+}
+
+void TreeItem::move_after(TreeItem *p_item) {
+ ERR_FAIL_NULL(p_item);
+ ERR_FAIL_COND(is_root);
+ ERR_FAIL_COND(!p_item->parent);
+
+ if (p_item == this) {
+ return;
+ }
+
+ TreeItem *p = p_item->parent;
+ while (p) {
+ ERR_FAIL_COND_MSG(p == this, "Can't move to a descendant");
+ p = p->parent;
+ }
+
+ Tree *old_tree = tree;
+ _unlink_from_tree();
+ _change_tree(p_item->tree);
+
+ if (p_item->next) {
+ p_item->next->prev = this;
+ }
+ parent = p_item->parent;
+ prev = p_item;
+ next = p_item->next;
+ p_item->next = this;
+
+ if (next) {
+ parent->children_cache.clear();
+ } else {
+ parent->children_cache.append(this);
+ }
+
+ if (tree && old_tree == tree) {
+ tree->update();
+ }
+}
+
+void TreeItem::remove_child(TreeItem *p_item) {
+ ERR_FAIL_NULL(p_item);
+ ERR_FAIL_COND(p_item->parent != this);
+
+ p_item->_unlink_from_tree();
+ p_item->prev = nullptr;
+ p_item->next = nullptr;
+ p_item->parent = nullptr;
+
+ if (tree) {
+ tree->update();
+ }
}
void TreeItem::set_selectable(int p_column, bool p_selectable) {
@@ -686,6 +868,15 @@ void TreeItem::clear_custom_color(int p_column) {
_changed_notify(p_column);
}
+void TreeItem::set_custom_font(int p_column, const Ref<Font> &p_font) {
+ ERR_FAIL_INDEX(p_column, cells.size());
+ cells.write[p_column].custom_font = p_font;
+}
+Ref<Font> TreeItem::get_custom_font(int p_column) const {
+ ERR_FAIL_INDEX_V(p_column, cells.size(), Ref<Font>());
+ return cells[p_column].custom_font;
+}
+
void TreeItem::set_tooltip(int p_column, const String &p_tooltip) {
ERR_FAIL_INDEX(p_column, cells.size());
cells.write[p_column].tooltip = p_tooltip;
@@ -760,6 +951,56 @@ bool TreeItem::is_folding_disabled() const {
return disable_folding;
}
+Size2 TreeItem::get_minimum_size(int p_column) {
+ ERR_FAIL_INDEX_V(p_column, cells.size(), Size2());
+ Tree *tree = get_tree();
+ ERR_FAIL_COND_V(!tree, Size2());
+
+ Size2 size;
+
+ // Default offset?
+ //size.width += (disable_folding || tree->hide_folding) ? tree->cache.hseparation : tree->cache.item_margin;
+
+ // Text.
+ const TreeItem::Cell &cell = cells[p_column];
+ if (!cell.text.is_empty()) {
+ if (cell.dirty) {
+ tree->update_item_cell(this, p_column);
+ }
+ Size2 text_size = cell.text_buf->get_size();
+ size.width += text_size.width;
+ size.height = MAX(size.height, text_size.height);
+ }
+
+ // Icon.
+ if (cell.mode == CELL_MODE_CHECK) {
+ size.width += tree->cache.checked->get_width() + tree->cache.hseparation;
+ }
+ if (cell.icon.is_valid()) {
+ Size2i icon_size = cell.get_icon_size();
+ if (cell.icon_max_w > 0 && icon_size.width > cell.icon_max_w) {
+ icon_size.width = cell.icon_max_w;
+ }
+ size.width += icon_size.width + tree->cache.hseparation;
+ size.height = MAX(size.height, icon_size.height);
+ }
+
+ // Buttons.
+ for (int i = 0; i < cell.buttons.size(); i++) {
+ Ref<Texture2D> texture = cell.buttons[i].texture;
+ if (texture.is_valid()) {
+ Size2 button_size = texture->get_size() + tree->cache.button_pressed->get_minimum_size();
+ size.width += button_size.width;
+ size.height = MAX(size.height, button_size.height);
+ }
+ }
+ if (cell.buttons.size() >= 2) {
+ size.width += (cell.buttons.size() - 1) * tree->cache.button_margin;
+ }
+
+ return size;
+}
+
Variant TreeItem::_call_recursive_bind(const Variant **p_args, int p_argcount, Callable::CallError &r_error) {
if (p_argcount < 1) {
r_error.error = Callable::CallError::CALL_ERROR_TOO_FEW_ARGUMENTS;
@@ -785,7 +1026,7 @@ void recursive_call_aux(TreeItem *p_item, const StringName &p_method, const Vari
return;
}
p_item->call(p_method, p_args, p_argcount, r_error);
- TreeItem *c = p_item->get_children();
+ TreeItem *c = p_item->get_first_child();
while (c) {
recursive_call_aux(c, p_method, p_args, p_argcount, r_error);
c = c->get_next();
@@ -855,16 +1096,6 @@ void TreeItem::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_custom_minimum_height", "height"), &TreeItem::set_custom_minimum_height);
ClassDB::bind_method(D_METHOD("get_custom_minimum_height"), &TreeItem::get_custom_minimum_height);
- ClassDB::bind_method(D_METHOD("get_next"), &TreeItem::get_next);
- ClassDB::bind_method(D_METHOD("get_prev"), &TreeItem::get_prev);
- ClassDB::bind_method(D_METHOD("get_parent"), &TreeItem::get_parent);
- ClassDB::bind_method(D_METHOD("get_children"), &TreeItem::get_children);
-
- ClassDB::bind_method(D_METHOD("get_next_visible", "wrap"), &TreeItem::get_next_visible, DEFVAL(false));
- ClassDB::bind_method(D_METHOD("get_prev_visible", "wrap"), &TreeItem::get_prev_visible, DEFVAL(false));
-
- ClassDB::bind_method(D_METHOD("remove_child", "child"), &TreeItem::_remove_child);
-
ClassDB::bind_method(D_METHOD("set_selectable", "column", "selectable"), &TreeItem::set_selectable);
ClassDB::bind_method(D_METHOD("is_selectable", "column"), &TreeItem::is_selectable);
@@ -876,8 +1107,11 @@ void TreeItem::_bind_methods() {
ClassDB::bind_method(D_METHOD("is_editable", "column"), &TreeItem::is_editable);
ClassDB::bind_method(D_METHOD("set_custom_color", "column", "color"), &TreeItem::set_custom_color);
- ClassDB::bind_method(D_METHOD("clear_custom_color", "column"), &TreeItem::clear_custom_color);
ClassDB::bind_method(D_METHOD("get_custom_color", "column"), &TreeItem::get_custom_color);
+ ClassDB::bind_method(D_METHOD("clear_custom_color", "column"), &TreeItem::clear_custom_color);
+
+ ClassDB::bind_method(D_METHOD("set_custom_font", "column", "font"), &TreeItem::set_custom_font);
+ ClassDB::bind_method(D_METHOD("get_custom_font", "column"), &TreeItem::get_custom_font);
ClassDB::bind_method(D_METHOD("set_custom_bg_color", "column", "color", "just_outline"), &TreeItem::set_custom_bg_color, DEFVAL(false));
ClassDB::bind_method(D_METHOD("clear_custom_bg_color", "column"), &TreeItem::clear_custom_bg_color);
@@ -895,19 +1129,38 @@ void TreeItem::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_button_disabled", "column", "button_idx", "disabled"), &TreeItem::set_button_disabled);
ClassDB::bind_method(D_METHOD("is_button_disabled", "column", "button_idx"), &TreeItem::is_button_disabled);
- ClassDB::bind_method(D_METHOD("set_expand_right", "column", "enable"), &TreeItem::set_expand_right);
- ClassDB::bind_method(D_METHOD("get_expand_right", "column"), &TreeItem::get_expand_right);
-
ClassDB::bind_method(D_METHOD("set_tooltip", "column", "tooltip"), &TreeItem::set_tooltip);
ClassDB::bind_method(D_METHOD("get_tooltip", "column"), &TreeItem::get_tooltip);
ClassDB::bind_method(D_METHOD("set_text_align", "column", "text_align"), &TreeItem::set_text_align);
ClassDB::bind_method(D_METHOD("get_text_align", "column"), &TreeItem::get_text_align);
- ClassDB::bind_method(D_METHOD("move_to_top"), &TreeItem::move_to_top);
- ClassDB::bind_method(D_METHOD("move_to_bottom"), &TreeItem::move_to_bottom);
+
+ ClassDB::bind_method(D_METHOD("set_expand_right", "column", "enable"), &TreeItem::set_expand_right);
+ ClassDB::bind_method(D_METHOD("get_expand_right", "column"), &TreeItem::get_expand_right);
ClassDB::bind_method(D_METHOD("set_disable_folding", "disable"), &TreeItem::set_disable_folding);
ClassDB::bind_method(D_METHOD("is_folding_disabled"), &TreeItem::is_folding_disabled);
+ ClassDB::bind_method(D_METHOD("create_child", "idx"), &TreeItem::create_child, DEFVAL(-1));
+ ClassDB::bind_method(D_METHOD("get_tree"), &TreeItem::get_tree);
+
+ ClassDB::bind_method(D_METHOD("get_next"), &TreeItem::get_next);
+ ClassDB::bind_method(D_METHOD("get_prev"), &TreeItem::get_prev);
+ ClassDB::bind_method(D_METHOD("get_parent"), &TreeItem::get_parent);
+ ClassDB::bind_method(D_METHOD("get_first_child"), &TreeItem::get_first_child);
+
+ ClassDB::bind_method(D_METHOD("get_next_visible", "wrap"), &TreeItem::get_next_visible, DEFVAL(false));
+ ClassDB::bind_method(D_METHOD("get_prev_visible", "wrap"), &TreeItem::get_prev_visible, DEFVAL(false));
+
+ ClassDB::bind_method(D_METHOD("get_child", "idx"), &TreeItem::get_child);
+ ClassDB::bind_method(D_METHOD("get_child_count"), &TreeItem::get_child_count);
+ ClassDB::bind_method(D_METHOD("get_children"), &TreeItem::get_children);
+ ClassDB::bind_method(D_METHOD("get_index"), &TreeItem::get_index);
+
+ ClassDB::bind_method(D_METHOD("move_before", "item"), &TreeItem::_move_before);
+ ClassDB::bind_method(D_METHOD("move_after", "item"), &TreeItem::_move_after);
+
+ ClassDB::bind_method(D_METHOD("remove_child", "child"), &TreeItem::_remove_child);
+
{
MethodInfo mi;
mi.name = "call_recursive";
@@ -932,7 +1185,7 @@ void TreeItem::_bind_methods() {
}
void TreeItem::clear_children() {
- TreeItem *c = children;
+ TreeItem *c = first_child;
while (c) {
TreeItem *aux = c;
c = c->get_next();
@@ -940,56 +1193,18 @@ void TreeItem::clear_children() {
memdelete(aux);
}
- children = nullptr;
+ first_child = nullptr;
};
TreeItem::TreeItem(Tree *p_tree) {
tree = p_tree;
- collapsed = false;
- disable_folding = false;
- custom_min_height = 0;
-
- parent = nullptr; // parent item
- next = nullptr; // next in list
- children = nullptr; //child items
}
TreeItem::~TreeItem() {
+ _unlink_from_tree();
+ prev = nullptr;
clear_children();
-
- if (parent) {
- parent->remove_child(this);
- }
-
- if (tree && tree->root == this) {
- tree->root = nullptr;
- }
-
- if (tree && tree->popup_edited_item == this) {
- tree->popup_edited_item = nullptr;
- tree->pressing_for_editor = false;
- }
-
- if (tree && tree->cache.hover_item == this) {
- tree->cache.hover_item = nullptr;
- }
-
- if (tree && tree->selected_item == this) {
- tree->selected_item = nullptr;
- }
-
- if (tree && tree->drop_mode_over == this) {
- tree->drop_mode_over = nullptr;
- }
-
- if (tree && tree->single_select_defer == this) {
- tree->single_select_defer = nullptr;
- }
-
- if (tree && tree->edited_item == this) {
- tree->edited_item = nullptr;
- tree->pressing_for_editor = false;
- }
+ _change_tree(nullptr);
}
/**********************************************/
@@ -1029,15 +1244,26 @@ void Tree::update_cache() {
cache.font_color = get_theme_color("font_color");
cache.font_selected_color = get_theme_color("font_selected_color");
- cache.guide_color = get_theme_color("guide_color");
cache.drop_position_color = get_theme_color("drop_position_color");
cache.hseparation = get_theme_constant("hseparation");
cache.vseparation = get_theme_constant("vseparation");
cache.item_margin = get_theme_constant("item_margin");
cache.button_margin = get_theme_constant("button_margin");
+
+ cache.font_outline_color = get_theme_color("font_outline_color");
+ cache.font_outline_size = get_theme_constant("outline_size");
+
cache.draw_guides = get_theme_constant("draw_guides");
+ cache.guide_color = get_theme_color("guide_color");
cache.draw_relationship_lines = get_theme_constant("draw_relationship_lines");
+ cache.relationship_line_width = get_theme_constant("relationship_line_width");
+ cache.parent_hl_line_width = get_theme_constant("parent_hl_line_width");
+ cache.children_hl_line_width = get_theme_constant("children_hl_line_width");
+ cache.parent_hl_line_margin = get_theme_constant("parent_hl_line_margin");
cache.relationship_line_color = get_theme_color("relationship_line_color");
+ cache.parent_hl_line_color = get_theme_color("parent_hl_line_color");
+ cache.children_hl_line_color = get_theme_color("children_hl_line_color");
+
cache.scroll_border = get_theme_constant("scroll_border");
cache.scroll_speed = get_theme_constant("scroll_speed");
@@ -1116,7 +1342,7 @@ int Tree::get_item_height(TreeItem *p_item) const {
if (!p_item->collapsed) { /* if not collapsed, check the children */
- TreeItem *c = p_item->children;
+ TreeItem *c = p_item->first_child;
while (c) {
height += get_item_height(c);
@@ -1209,6 +1435,7 @@ void Tree::update_column(int p_col) {
} else {
columns.write[p_col].text_buf->set_direction((TextServer::Direction)columns[p_col].text_direction);
}
+
columns.write[p_col].text_buf->add_string(columns[p_col].title, cache.font, cache.font_size, columns[p_col].opentype_features, (columns[p_col].language != "") ? columns[p_col].language : TranslationServer::get_singleton()->get_tool_locale());
}
@@ -1253,7 +1480,14 @@ void Tree::update_item_cell(TreeItem *p_item, int p_col) {
} else {
p_item->cells.write[p_col].text_buf->set_direction((TextServer::Direction)p_item->cells[p_col].text_direction);
}
- p_item->cells.write[p_col].text_buf->add_string(valtext, cache.font, cache.font_size, p_item->cells[p_col].opentype_features, (p_item->cells[p_col].language != "") ? p_item->cells[p_col].language : TranslationServer::get_singleton()->get_tool_locale());
+
+ Ref<Font> font;
+ if (p_item->cells[p_col].custom_font.is_valid()) {
+ font = p_item->cells[p_col].custom_font;
+ } else {
+ font = cache.font;
+ }
+ p_item->cells.write[p_col].text_buf->add_string(valtext, font, cache.font_size, p_item->cells[p_col].opentype_features, (p_item->cells[p_col].language != "") ? p_item->cells[p_col].language : TranslationServer::get_singleton()->get_tool_locale());
TS->shaped_text_set_bidi_override(p_item->cells[p_col].text_buf->get_rid(), structured_text_parser(p_item->cells[p_col].st_parser, p_item->cells[p_col].st_args, valtext));
p_item->cells.write[p_col].dirty = false;
}
@@ -1263,7 +1497,7 @@ void Tree::update_item_cache(TreeItem *p_item) {
update_item_cell(p_item, i);
}
- TreeItem *c = p_item->children;
+ TreeItem *c = p_item->first_child;
while (c) {
update_item_cache(c);
c = c->next;
@@ -1280,7 +1514,7 @@ int Tree::draw_item(const Point2i &p_pos, const Point2 &p_draw_ofs, const Size2
int htotal = 0;
int label_h = compute_item_height(p_item);
- bool rtl = is_layout_rtl();
+ bool rtl = cache.rtl;
/* Calculate height of the label part */
label_h += cache.vseparation;
@@ -1326,6 +1560,20 @@ int Tree::draw_item(const Point2i &p_pos, const Point2 &p_draw_ofs, const Size2
}
}
+ if (!rtl && p_item->cells[i].buttons.size()) {
+ int button_w = 0;
+ for (int j = p_item->cells[i].buttons.size() - 1; j >= 0; j--) {
+ Ref<Texture2D> b = p_item->cells[i].buttons[j].texture;
+ button_w += b->get_size().width + cache.button_pressed->get_minimum_size().width + cache.button_margin;
+ }
+
+ int total_ofs = ofs - cache.offset.x;
+
+ if (total_ofs + w > p_draw_size.width) {
+ w = MAX(button_w, p_draw_size.width - total_ofs);
+ }
+ }
+
int bw = 0;
for (int j = p_item->cells[i].buttons.size() - 1; j >= 0; j--) {
Ref<Texture2D> b = p_item->cells[i].buttons[j].texture;
@@ -1388,7 +1636,7 @@ int Tree::draw_item(const Point2i &p_pos, const Point2 &p_draw_ofs, const Size2
}
}
- if ((select_mode == SELECT_ROW && selected_item == p_item) || p_item->cells[i].selected) {
+ if ((select_mode == SELECT_ROW && selected_item == p_item) || p_item->cells[i].selected || !p_item->has_meta("__focus_rect")) {
Rect2i r(cell_rect.position, cell_rect.size);
p_item->set_meta("__focus_rect", Rect2(r.position, r.size));
@@ -1430,7 +1678,7 @@ int Tree::draw_item(const Point2i &p_pos, const Point2 &p_draw_ofs, const Size2
if (drop_mode_flags && drop_mode_over == p_item) {
Rect2 r = cell_rect;
- bool has_parent = p_item->get_children() != nullptr;
+ bool has_parent = p_item->get_first_child() != nullptr;
if (rtl) {
r.position.x = get_size().width - r.position.x - r.size.x;
}
@@ -1450,8 +1698,8 @@ int Tree::draw_item(const Point2i &p_pos, const Point2 &p_draw_ofs, const Size2
}
Color col = p_item->cells[i].custom_color ? p_item->cells[i].color : get_theme_color(p_item->cells[i].selected ? "font_selected_color" : "font_color");
- Color font_outline_color = get_theme_color("font_outline_color");
- int outline_size = get_theme_constant("outline_size");
+ Color font_outline_color = cache.font_outline_color;
+ int outline_size = cache.font_outline_size;
Color icon_col = p_item->cells[i].icon_color;
if (p_item->cells[i].dirty) {
@@ -1626,7 +1874,7 @@ int Tree::draw_item(const Point2i &p_pos, const Point2 &p_draw_ofs, const Size2
}
}
- if (!p_item->disable_folding && !hide_folding && p_item->children) { //has children, draw the guide box
+ if (!p_item->disable_folding && !hide_folding && p_item->first_child) { //has children, draw the guide box
Ref<Texture2D> arrow;
@@ -1656,52 +1904,91 @@ int Tree::draw_item(const Point2i &p_pos, const Point2 &p_draw_ofs, const Size2
if (!p_item->collapsed) { /* if not collapsed, check the children */
- TreeItem *c = p_item->children;
+ TreeItem *c = p_item->first_child;
- int prev_ofs = children_pos.y - cache.offset.y + p_draw_ofs.y;
+ int base_ofs = children_pos.y - cache.offset.y + p_draw_ofs.y;
+ int prev_ofs = base_ofs;
+ int prev_hl_ofs = base_ofs;
while (c) {
- if (cache.draw_relationship_lines > 0 && (!hide_root || c->parent != root)) {
- int root_ofs = children_pos.x + ((p_item->disable_folding || hide_folding) ? cache.hseparation : cache.item_margin);
- int parent_ofs = p_pos.x + ((p_item->disable_folding || hide_folding) ? cache.hseparation : cache.item_margin);
- Point2i root_pos = Point2i(root_ofs, children_pos.y + label_h / 2) - cache.offset + p_draw_ofs;
+ if (htotal >= 0) {
+ int child_h = draw_item(children_pos, p_draw_ofs, p_draw_size, c);
- if (c->get_children() != nullptr) {
- root_pos -= Point2i(cache.arrow->get_width(), 0);
- }
+ // Draw relationship lines.
+ if (cache.draw_relationship_lines > 0 && (!hide_root || c->parent != root)) {
+ int root_ofs = children_pos.x + ((p_item->disable_folding || hide_folding) ? cache.hseparation : cache.item_margin);
+ int parent_ofs = p_pos.x + cache.item_margin;
+ Point2i root_pos = Point2i(root_ofs, children_pos.y + label_h / 2) - cache.offset + p_draw_ofs;
+
+ if (c->get_first_child() != nullptr) {
+ root_pos -= Point2i(cache.arrow->get_width(), 0);
+ }
+
+ float line_width = cache.relationship_line_width;
+ float parent_line_width = cache.parent_hl_line_width;
+ float children_line_width = cache.children_hl_line_width;
- float line_width = 1.0;
#ifdef TOOLS_ENABLED
- line_width *= EDSCALE;
+ line_width *= Math::round(EDSCALE);
+ parent_line_width *= Math::round(EDSCALE);
+ children_line_width *= Math::round(EDSCALE);
#endif
- Point2i parent_pos = Point2i(parent_ofs - cache.arrow->get_width() / 2, p_pos.y + label_h / 2 + cache.arrow->get_height() / 2) - cache.offset + p_draw_ofs;
+ Point2i parent_pos = Point2i(parent_ofs - cache.arrow->get_width() / 2, p_pos.y + label_h / 2 + cache.arrow->get_height() / 2) - cache.offset + p_draw_ofs;
- if (root_pos.y + line_width >= 0) {
- if (rtl) {
- root_pos.x = get_size().width - root_pos.x;
- parent_pos.x = get_size().width - parent_pos.x;
+ int more_prev_ofs = 0;
+
+ if (root_pos.y + line_width >= 0) {
+ if (rtl) {
+ root_pos.x = get_size().width - root_pos.x;
+ parent_pos.x = get_size().width - parent_pos.x;
+ }
+
+ // Order of parts on this bend: the horizontal line first, then the vertical line.
+ if (_is_branch_selected(c)) {
+ // If this item or one of its children is selected, we draw the line using parent highlight style.
+ RenderingServer::get_singleton()->canvas_item_add_line(ci, root_pos, Point2i(parent_pos.x + Math::floor(parent_line_width / 2), root_pos.y), cache.parent_hl_line_color, parent_line_width);
+ RenderingServer::get_singleton()->canvas_item_add_line(ci, Point2i(parent_pos.x, root_pos.y + Math::floor(parent_line_width / 2)), Point2i(parent_pos.x, prev_hl_ofs), cache.parent_hl_line_color, parent_line_width);
+
+ more_prev_ofs = cache.parent_hl_line_margin;
+ prev_hl_ofs = root_pos.y + Math::floor(parent_line_width / 2);
+ } else if (p_item->is_selected(0)) {
+ // If parent item is selected (but this item is not), we draw the line using children highlight style.
+ // Siblings of the selected branch can be drawn with a slight offset and their vertical line must appear as highlighted.
+ if (_is_sibling_branch_selected(c)) {
+ RenderingServer::get_singleton()->canvas_item_add_line(ci, root_pos, Point2i(parent_pos.x + Math::floor(parent_line_width / 2), root_pos.y), cache.children_hl_line_color, children_line_width);
+ RenderingServer::get_singleton()->canvas_item_add_line(ci, Point2i(parent_pos.x, root_pos.y + Math::floor(parent_line_width / 2)), Point2i(parent_pos.x, prev_hl_ofs), cache.parent_hl_line_color, parent_line_width);
+
+ prev_hl_ofs = root_pos.y + Math::floor(parent_line_width / 2);
+ } else {
+ RenderingServer::get_singleton()->canvas_item_add_line(ci, root_pos, Point2i(parent_pos.x + Math::floor(children_line_width / 2), root_pos.y), cache.children_hl_line_color, children_line_width);
+ RenderingServer::get_singleton()->canvas_item_add_line(ci, Point2i(parent_pos.x, root_pos.y + Math::floor(children_line_width / 2)), Point2i(parent_pos.x, prev_ofs + Math::floor(children_line_width / 2)), cache.children_hl_line_color, children_line_width);
+ }
+ } else {
+ // If nothing of the above is true, we draw the line using normal style.
+ // Siblings of the selected branch can be drawn with a slight offset and their vertical line must appear as highlighted.
+ if (_is_sibling_branch_selected(c)) {
+ RenderingServer::get_singleton()->canvas_item_add_line(ci, root_pos, Point2i(parent_pos.x + cache.parent_hl_line_margin, root_pos.y), cache.relationship_line_color, line_width);
+ RenderingServer::get_singleton()->canvas_item_add_line(ci, Point2i(parent_pos.x, root_pos.y + Math::floor(parent_line_width / 2)), Point2i(parent_pos.x, prev_hl_ofs), cache.parent_hl_line_color, parent_line_width);
+
+ prev_hl_ofs = root_pos.y + Math::floor(parent_line_width / 2);
+ } else {
+ RenderingServer::get_singleton()->canvas_item_add_line(ci, root_pos, Point2i(parent_pos.x + Math::floor(line_width / 2), root_pos.y), cache.relationship_line_color, line_width);
+ RenderingServer::get_singleton()->canvas_item_add_line(ci, Point2i(parent_pos.x, root_pos.y + Math::floor(line_width / 2)), Point2i(parent_pos.x, prev_ofs + Math::floor(line_width / 2)), cache.relationship_line_color, line_width);
+ }
+ }
}
- RenderingServer::get_singleton()->canvas_item_add_line(ci, root_pos, Point2i(parent_pos.x - Math::floor(line_width / 2), root_pos.y), cache.relationship_line_color, line_width);
- RenderingServer::get_singleton()->canvas_item_add_line(ci, Point2i(parent_pos.x, root_pos.y), Point2i(parent_pos.x, prev_ofs), cache.relationship_line_color, line_width);
- }
- if (htotal < 0) {
- return -1;
+ prev_ofs = root_pos.y + more_prev_ofs;
}
- prev_ofs = root_pos.y;
- }
-
- if (htotal >= 0) {
- int child_h = draw_item(children_pos, p_draw_ofs, p_draw_size, c);
if (child_h < 0) {
if (cache.draw_relationship_lines == 0) {
return -1; // break, stop drawing, no need to anymore
- } else {
- htotal = -1;
- children_pos.y = cache.offset.y + p_draw_size.height;
}
+
+ htotal = -1;
+ children_pos.y = cache.offset.y + p_draw_size.height;
} else {
htotal += child_h;
children_pos.y += child_h;
@@ -1723,8 +2010,8 @@ int Tree::_count_selected_items(TreeItem *p_from) const {
}
}
- if (p_from->get_children()) {
- count += _count_selected_items(p_from->get_children());
+ if (p_from->get_first_child()) {
+ count += _count_selected_items(p_from->get_first_child());
}
if (p_from->get_next()) {
@@ -1734,6 +2021,36 @@ int Tree::_count_selected_items(TreeItem *p_from) const {
return count;
}
+bool Tree::_is_branch_selected(TreeItem *p_from) const {
+ for (int i = 0; i < columns.size(); i++) {
+ if (p_from->is_selected(i)) {
+ return true;
+ }
+ }
+
+ TreeItem *child_item = p_from->get_first_child();
+ while (child_item) {
+ if (_is_branch_selected(child_item)) {
+ return true;
+ }
+ child_item = child_item->get_next();
+ }
+
+ return false;
+}
+
+bool Tree::_is_sibling_branch_selected(TreeItem *p_from) const {
+ TreeItem *sibling_item = p_from->get_next();
+ while (sibling_item) {
+ if (_is_branch_selected(sibling_item)) {
+ return true;
+ }
+ sibling_item = sibling_item->get_next();
+ }
+
+ return false;
+}
+
void Tree::select_single_item(TreeItem *p_selected, TreeItem *p_current, int p_col, TreeItem *p_prev, bool *r_in_range, bool p_force_deselect) {
TreeItem::Cell &selected_cell = p_selected->cells.write[p_col];
@@ -1812,7 +2129,7 @@ void Tree::select_single_item(TreeItem *p_selected, TreeItem *p_current, int p_c
*r_in_range = false;
}
- TreeItem *c = p_current->children;
+ TreeItem *c = p_current->first_child;
while (c) {
select_single_item(p_selected, c, p_col, p_prev, r_in_range, p_current->is_collapsed() || p_force_deselect);
@@ -1836,14 +2153,24 @@ void Tree::_range_click_timeout() {
}
}
+ if (!root) {
+ return;
+ }
+
click_handled = false;
Ref<InputEventMouseButton> mb;
- mb.instance();
- ;
+ mb.instantiate();
+
+ int x_limit = get_size().width - cache.bg->get_minimum_size().width;
+ if (h_scroll->is_visible()) {
+ x_limit -= h_scroll->get_minimum_size().width;
+ }
+
+ cache.rtl = is_layout_rtl();
propagate_mouse_activated = false; // done from outside, so signal handler can't clear the tree in the middle of emit (which is a common case)
blocked++;
- propagate_mouse_event(pos + cache.offset, 0, 0, false, root, MOUSE_BUTTON_LEFT, mb);
+ propagate_mouse_event(pos + cache.offset, 0, 0, x_limit + cache.offset.width, false, root, MOUSE_BUTTON_LEFT, mb);
blocked--;
if (range_click_timer->is_one_shot()) {
@@ -1866,7 +2193,7 @@ void Tree::_range_click_timeout() {
}
}
-int Tree::propagate_mouse_event(const Point2i &p_pos, int x_ofs, int y_ofs, bool p_doubleclick, TreeItem *p_item, int p_button, const Ref<InputEventWithModifiers> &p_mod) {
+int Tree::propagate_mouse_event(const Point2i &p_pos, int x_ofs, int y_ofs, int x_limit, bool p_double_click, TreeItem *p_item, int p_button, const Ref<InputEventWithModifiers> &p_mod) {
int item_h = compute_item_height(p_item) + cache.vseparation;
bool skip = (p_item == root && hide_root);
@@ -1879,7 +2206,7 @@ int Tree::propagate_mouse_event(const Point2i &p_pos, int x_ofs, int y_ofs, bool
}
if (!p_item->disable_folding && !hide_folding && (p_pos.x >= x_ofs && p_pos.x < (x_ofs + cache.item_margin))) {
- if (p_item->children) {
+ if (p_item->first_child) {
p_item->set_collapsed(!p_item->is_collapsed());
}
@@ -1891,6 +2218,9 @@ int Tree::propagate_mouse_event(const Point2i &p_pos, int x_ofs, int y_ofs, bool
int col = -1;
int col_ofs = 0;
int col_width = 0;
+
+ int limit_w = x_limit;
+
for (int i = 0; i < columns.size(); i++) {
col_width = get_column_width(i);
@@ -1906,6 +2236,7 @@ int Tree::propagate_mouse_event(const Point2i &p_pos, int x_ofs, int y_ofs, bool
if (x > col_width) {
col_ofs += col_width;
x -= col_width;
+ limit_w -= col_width;
continue;
}
@@ -1919,14 +2250,16 @@ int Tree::propagate_mouse_event(const Point2i &p_pos, int x_ofs, int y_ofs, bool
int margin = x_ofs + cache.item_margin; //-cache.hseparation;
//int lm = cache.bg->get_margin(SIDE_LEFT);
col_width -= margin;
+ limit_w -= margin;
col_ofs += margin;
x -= margin;
} else {
col_width -= cache.hseparation;
+ limit_w -= cache.hseparation;
x -= cache.hseparation;
}
- if (!p_item->disable_folding && !hide_folding && !p_item->cells[col].editable && !p_item->cells[col].selectable && p_item->get_children()) {
+ if (!p_item->disable_folding && !hide_folding && !p_item->cells[col].editable && !p_item->cells[col].selectable && p_item->get_first_child()) {
p_item->set_collapsed(!p_item->is_collapsed());
return -1; //collapse/uncollapse because nothing can be done with item
}
@@ -1936,6 +2269,16 @@ int Tree::propagate_mouse_event(const Point2i &p_pos, int x_ofs, int y_ofs, bool
bool already_selected = c.selected;
bool already_cursor = (p_item == selected_item) && col == selected_col;
+ if (!cache.rtl && p_item->cells[col].buttons.size()) {
+ int button_w = 0;
+ for (int j = p_item->cells[col].buttons.size() - 1; j >= 0; j--) {
+ Ref<Texture2D> b = p_item->cells[col].buttons[j].texture;
+ button_w += b->get_size().width + cache.button_pressed->get_minimum_size().width + cache.button_margin;
+ }
+
+ col_width = MAX(button_w, MIN(limit_w, col_width));
+ }
+
for (int j = c.buttons.size() - 1; j >= 0; j--) {
Ref<Texture2D> b = c.buttons[j].texture;
int w = b->get_size().width + cache.button_pressed->get_minimum_size().width;
@@ -1957,13 +2300,14 @@ int Tree::propagate_mouse_event(const Point2i &p_pos, int x_ofs, int y_ofs, bool
//emit_signal("button_pressed");
return -1;
}
+
col_width -= w + cache.button_margin;
}
if (p_button == MOUSE_BUTTON_LEFT || (p_button == MOUSE_BUTTON_RIGHT && allow_rmb_select)) {
/* process selection */
- if (p_doubleclick && (!c.editable || c.mode == TreeItem::CELL_MODE_CUSTOM || c.mode == TreeItem::CELL_MODE_ICON /*|| c.mode==TreeItem::CELL_MODE_CHECK*/)) { //it's confusing for check
+ if (p_double_click && (!c.editable || c.mode == TreeItem::CELL_MODE_CUSTOM || c.mode == TreeItem::CELL_MODE_ICON /*|| c.mode==TreeItem::CELL_MODE_CHECK*/)) { //it's confusing for check
propagate_mouse_activated = true;
@@ -1971,7 +2315,7 @@ int Tree::propagate_mouse_event(const Point2i &p_pos, int x_ofs, int y_ofs, bool
return -1;
}
- if (select_mode == SELECT_MULTI && p_mod->get_command() && c.selectable) {
+ if (select_mode == SELECT_MULTI && p_mod->is_command_pressed() && c.selectable) {
if (!c.selected || p_button == MOUSE_BUTTON_RIGHT) {
p_item->select(col);
emit_signal("multi_selected", p_item, col, true);
@@ -1988,7 +2332,7 @@ int Tree::propagate_mouse_event(const Point2i &p_pos, int x_ofs, int y_ofs, bool
} else {
if (c.selectable) {
- if (select_mode == SELECT_MULTI && p_mod->get_shift() && selected_item && selected_item != p_item) {
+ if (select_mode == SELECT_MULTI && p_mod->is_shift_pressed() && selected_item && selected_item != p_item) {
bool inrange = false;
select_single_item(p_item, root, col, selected_item, &inrange);
@@ -2164,10 +2508,10 @@ int Tree::propagate_mouse_event(const Point2i &p_pos, int x_ofs, int y_ofs, bool
if (!p_item->collapsed) { /* if not collapsed, check the children */
- TreeItem *c = p_item->children;
+ TreeItem *c = p_item->first_child;
while (c) {
- int child_h = propagate_mouse_event(new_pos, x_ofs, y_ofs, p_doubleclick, c, p_button, p_mod);
+ int child_h = propagate_mouse_event(new_pos, x_ofs, y_ofs, x_limit, p_double_click, c, p_button, p_mod);
if (child_h < 0) {
return -1; // break, stop propagating, no need to anymore
@@ -2198,10 +2542,10 @@ void Tree::_text_editor_modal_close() {
return;
}
- _text_editor_enter(text_editor->get_text());
+ _text_editor_submit(text_editor->get_text());
}
-void Tree::_text_editor_enter(String p_text) {
+void Tree::_text_editor_submit(String p_text) {
popup_editor->hide();
if (!popup_edited_item) {
@@ -2270,7 +2614,7 @@ void Tree::popup_select(int p_option) {
void Tree::_go_left() {
if (selected_col == 0) {
- if (selected_item->get_children() != nullptr && !selected_item->is_collapsed()) {
+ if (selected_item->get_first_child() != nullptr && !selected_item->is_collapsed()) {
selected_item->set_collapsed(true);
} else {
if (columns.size() == 1) { // goto parent with one column
@@ -2298,7 +2642,7 @@ void Tree::_go_left() {
void Tree::_go_right() {
if (selected_col == (columns.size() - 1)) {
- if (selected_item->get_children() != nullptr && selected_item->is_collapsed()) {
+ if (selected_item->get_first_child() != nullptr && selected_item->is_collapsed()) {
selected_item->set_collapsed(false);
} else if (selected_item->get_next_visible()) {
selected_col = 0;
@@ -2402,9 +2746,11 @@ void Tree::_go_down() {
}
void Tree::_gui_input(Ref<InputEvent> p_event) {
+ ERR_FAIL_COND(p_event.is_null());
+
Ref<InputEventKey> k = p_event;
- bool is_command = k.is_valid() && k->get_command();
+ bool is_command = k.is_valid() && k->is_command_pressed();
if (p_event->is_action("ui_right") && p_event->is_pressed()) {
if (!cursor_can_exit_tree) {
accept_event();
@@ -2413,9 +2759,9 @@ void Tree::_gui_input(Ref<InputEvent> p_event) {
if (!selected_item || select_mode == SELECT_ROW || selected_col > (columns.size() - 1)) {
return;
}
- if (k.is_valid() && k->get_alt()) {
+ if (k.is_valid() && k->is_alt_pressed()) {
selected_item->set_collapsed(false);
- TreeItem *next = selected_item->get_children();
+ TreeItem *next = selected_item->get_first_child();
while (next && next != selected_item->next) {
next->set_collapsed(false);
next = next->get_next_visible();
@@ -2432,9 +2778,9 @@ void Tree::_gui_input(Ref<InputEvent> p_event) {
return;
}
- if (k.is_valid() && k->get_alt()) {
+ if (k.is_valid() && k->is_alt_pressed()) {
selected_item->set_collapsed(true);
- TreeItem *next = selected_item->get_children();
+ TreeItem *next = selected_item->get_first_child();
while (next && next != selected_item->next) {
next->set_collapsed(true);
next = next->get_next_visible();
@@ -2562,7 +2908,7 @@ void Tree::_gui_input(Ref<InputEvent> p_event) {
if (!k->is_pressed()) {
return;
}
- if (k->get_command() || (k->get_shift() && k->get_unicode() == 0) || k->get_metakey()) {
+ if (k->is_command_pressed() || (k->is_shift_pressed() && k->get_unicode() == 0) || k->is_meta_pressed()) {
return;
}
if (!root) {
@@ -2832,7 +3178,7 @@ void Tree::_gui_input(Ref<InputEvent> p_event) {
break;
}
}
- if (!root || (!root->get_children() && hide_root)) {
+ if (!root || (!root->get_first_child() && hide_root)) {
if (b->get_button_index() == MOUSE_BUTTON_RIGHT && allow_rmb_select) {
emit_signal("empty_tree_rmb_selected", get_local_mouse_position());
}
@@ -2843,8 +3189,14 @@ void Tree::_gui_input(Ref<InputEvent> p_event) {
pressing_for_editor = false;
propagate_mouse_activated = false;
+ int x_limit = get_size().width - cache.bg->get_minimum_size().width;
+ if (h_scroll->is_visible()) {
+ x_limit -= h_scroll->get_minimum_size().width;
+ }
+
+ cache.rtl = is_layout_rtl();
blocked++;
- propagate_mouse_event(pos + cache.offset, 0, 0, b->is_doubleclick(), root, b->get_button_index(), b);
+ propagate_mouse_event(pos + cache.offset, 0, 0, x_limit + cache.offset.width, b->is_double_click(), root, b->get_button_index(), b);
blocked--;
if (pressing_for_editor) {
@@ -2871,14 +3223,14 @@ void Tree::_gui_input(Ref<InputEvent> p_event) {
drag_accum = 0;
//last_drag_accum=0;
drag_from = v_scroll->get_value();
- drag_touching = !DisplayServer::get_singleton()->screen_is_touchscreen(DisplayServer::get_singleton()->window_get_current_screen(get_viewport()->get_window_id()));
+ drag_touching = DisplayServer::get_singleton()->screen_is_touchscreen(DisplayServer::get_singleton()->window_get_current_screen(get_viewport()->get_window_id()));
drag_touching_deaccel = false;
if (drag_touching) {
set_physics_process_internal(true);
}
if (b->get_button_index() == MOUSE_BUTTON_LEFT) {
- if (get_item_at_position(b->get_position()) == nullptr && !b->get_shift() && !b->get_control() && !b->get_command()) {
+ if (get_item_at_position(b->get_position()) == nullptr && !b->is_shift_pressed() && !b->is_ctrl_pressed() && !b->is_command_pressed()) {
emit_signal("nothing_selected");
}
}
@@ -2906,6 +3258,8 @@ void Tree::_gui_input(Ref<InputEvent> p_event) {
}
} break;
+ default:
+ break;
}
}
@@ -3011,13 +3365,17 @@ bool Tree::edit_selected() {
return false;
}
+bool Tree::is_editing() {
+ return popup_editor->is_visible();
+}
+
Size2 Tree::get_internal_min_size() const {
Size2i size = cache.bg->get_offset();
if (root) {
size.height += get_item_height(root);
}
for (int i = 0; i < columns.size(); i++) {
- size.width += columns[i].min_width;
+ size.width += get_column_minimum_width(i);
}
return size;
@@ -3041,26 +3399,38 @@ void Tree::update_scrollbars() {
h_scroll->set_begin(Point2(0, size.height - hmin.height));
h_scroll->set_end(Point2(size.width - vmin.width, size.height));
- Size2 min = get_internal_min_size();
+ Size2 internal_min_size = get_internal_min_size();
- if (min.height < size.height - hmin.height) {
- v_scroll->hide();
- cache.offset.y = 0;
- } else {
+ bool display_vscroll = internal_min_size.height + cache.bg->get_margin(SIDE_TOP) > size.height;
+ bool display_hscroll = internal_min_size.width + cache.bg->get_margin(SIDE_LEFT) > size.width;
+ for (int i = 0; i < 2; i++) {
+ // Check twice, as both values are dependent on each other.
+ if (display_hscroll) {
+ display_vscroll = internal_min_size.height + cache.bg->get_margin(SIDE_TOP) + hmin.height > size.height;
+ }
+ if (display_vscroll) {
+ display_hscroll = internal_min_size.width + cache.bg->get_margin(SIDE_LEFT) + vmin.width > size.width;
+ }
+ }
+
+ if (display_vscroll) {
v_scroll->show();
- v_scroll->set_max(min.height);
+ v_scroll->set_max(internal_min_size.height);
v_scroll->set_page(size.height - hmin.height - tbh);
cache.offset.y = v_scroll->get_value();
+ } else {
+ v_scroll->hide();
+ cache.offset.y = 0;
}
- if (min.width < size.width - vmin.width) {
- h_scroll->hide();
- cache.offset.x = 0;
- } else {
+ if (display_hscroll) {
h_scroll->show();
- h_scroll->set_max(min.width);
+ h_scroll->set_max(internal_min_size.width);
h_scroll->set_page(size.width - vmin.width);
cache.offset.x = h_scroll->get_value();
+ } else {
+ h_scroll->hide();
+ cache.offset.x = 0;
}
}
@@ -3172,27 +3542,26 @@ void Tree::_notification(int p_what) {
RID ci = get_canvas_item();
Ref<StyleBox> bg = cache.bg;
- Ref<StyleBox> bg_focus = get_theme_stylebox("bg_focus");
Color font_outline_color = get_theme_color("font_outline_color");
int outline_size = get_theme_constant("outline_size");
Point2 draw_ofs;
draw_ofs += bg->get_offset();
Size2 draw_size = get_size() - bg->get_minimum_size();
+ if (h_scroll->is_visible()) {
+ draw_size.width -= h_scroll->get_minimum_size().width;
+ }
bg->draw(ci, Rect2(Point2(), get_size()));
- if (has_focus()) {
- RenderingServer::get_singleton()->canvas_item_add_clip_ignore(ci, true);
- bg_focus->draw(ci, Rect2(Point2(), get_size()));
- RenderingServer::get_singleton()->canvas_item_add_clip_ignore(ci, false);
- }
int tbh = _get_title_button_height();
draw_ofs.y += tbh;
draw_size.y -= tbh;
- if (root) {
+ cache.rtl = is_layout_rtl();
+
+ if (root && get_size().x > 0 && get_size().y > 0) {
draw_item(Point2(), draw_ofs, draw_size, root);
}
@@ -3203,7 +3572,7 @@ void Tree::_notification(int p_what) {
Ref<StyleBox> sb = (cache.click_type == Cache::CLICK_TITLE && cache.click_index == i) ? cache.title_button_pressed : ((cache.hover_type == Cache::CLICK_TITLE && cache.hover_index == i) ? cache.title_button_hover : cache.title_button);
Ref<Font> f = cache.tb_font;
Rect2 tbrect = Rect2(ofs2 - cache.offset.x, bg->get_margin(SIDE_TOP), get_column_width(i), tbh);
- if (is_layout_rtl()) {
+ if (cache.rtl) {
tbrect.position.x = get_size().width - tbrect.size.x - tbrect.position.x;
}
sb->draw(ci, tbrect);
@@ -3219,6 +3588,15 @@ void Tree::_notification(int p_what) {
columns[i].text_buf->draw(ci, text_pos, cache.title_button_color);
}
}
+
+ // Draw the background focus outline last, so that it is drawn in front of the section headings.
+ // Otherwise, section heading backgrounds can appear to be in front of the focus outline when scrolling.
+ if (has_focus()) {
+ RenderingServer::get_singleton()->canvas_item_add_clip_ignore(ci, true);
+ const Ref<StyleBox> bg_focus = get_theme_stylebox("bg_focus");
+ bg_focus->draw(ci, Rect2(Point2(), get_size()));
+ RenderingServer::get_singleton()->canvas_item_add_clip_ignore(ci, false);
+ }
}
if (p_what == NOTIFICATION_THEME_CHANGED || p_what == NOTIFICATION_LAYOUT_DIRECTION_CHANGED || p_what == NOTIFICATION_TRANSLATION_CHANGED) {
@@ -3251,7 +3629,17 @@ void Tree::_update_all() {
}
Size2 Tree::get_minimum_size() const {
- return Size2(1, 1);
+ if (h_scroll_enabled && v_scroll_enabled) {
+ return Size2();
+ } else {
+ Vector2 min_size = get_internal_min_size();
+ Ref<StyleBox> bg = cache.bg;
+ if (bg.is_valid()) {
+ min_size.x += bg->get_margin(SIDE_LEFT) + bg->get_margin(SIDE_RIGHT);
+ min_size.y += bg->get_margin(SIDE_TOP) + bg->get_margin(SIDE_BOTTOM);
+ }
+ return Vector2(h_scroll_enabled ? 0 : min_size.x, v_scroll_enabled ? 0 : min_size.y);
+ }
}
TreeItem *Tree::create_item(TreeItem *p_parent, int p_idx) {
@@ -3260,38 +3648,15 @@ TreeItem *Tree::create_item(TreeItem *p_parent, int p_idx) {
TreeItem *ti = nullptr;
if (p_parent) {
- // Append or insert a new item to the given parent.
- ti = memnew(TreeItem(this));
- ERR_FAIL_COND_V(!ti, nullptr);
- ti->cells.resize(columns.size());
-
- TreeItem *prev = nullptr;
- TreeItem *c = p_parent->children;
- int idx = 0;
-
- while (c) {
- if (idx++ == p_idx) {
- ti->next = c;
- break;
- }
- prev = c;
- c = c->next;
- }
-
- if (prev) {
- prev->next = ti;
- } else {
- p_parent->children = ti;
- }
- ti->parent = p_parent;
-
+ ERR_FAIL_COND_V_MSG(p_parent->tree != this, nullptr, "A different tree owns the given parent");
+ ti = p_parent->create_child(p_idx);
} else {
if (!root) {
// No root exists, make the given item the new root.
ti = memnew(TreeItem(this));
ERR_FAIL_COND_V(!ti, nullptr);
ti->cells.resize(columns.size());
-
+ ti->is_root = true;
root = ti;
} else {
// Root exists, append or insert to root.
@@ -3302,18 +3667,18 @@ TreeItem *Tree::create_item(TreeItem *p_parent, int p_idx) {
return ti;
}
-TreeItem *Tree::get_root() {
+TreeItem *Tree::get_root() const {
return root;
}
-TreeItem *Tree::get_last_item() {
+TreeItem *Tree::get_last_item() const {
TreeItem *last = root;
while (last) {
if (last->next) {
last = last->next;
- } else if (last->children) {
- last = last->children;
+ } else if (last->first_child) {
+ last = last->first_child;
} else {
break;
}
@@ -3436,13 +3801,13 @@ bool Tree::is_root_hidden() const {
return hide_root;
}
-void Tree::set_column_min_width(int p_column, int p_min_width) {
+void Tree::set_column_custom_minimum_width(int p_column, int p_min_width) {
ERR_FAIL_INDEX(p_column, columns.size());
- if (p_min_width < 1) {
+ if (p_min_width < 0) {
return;
}
- columns.write[p_column].min_width = p_min_width;
+ columns.write[p_column].custom_min_width = p_min_width;
update();
}
@@ -3453,6 +3818,36 @@ void Tree::set_column_expand(int p_column, bool p_expand) {
update();
}
+void Tree::set_column_expand_ratio(int p_column, int p_ratio) {
+ ERR_FAIL_INDEX(p_column, columns.size());
+ columns.write[p_column].expand_ratio = p_ratio;
+ update();
+}
+
+void Tree::set_column_clip_content(int p_column, bool p_fit) {
+ ERR_FAIL_INDEX(p_column, columns.size());
+
+ columns.write[p_column].clip_content = p_fit;
+ update();
+}
+
+bool Tree::is_column_expanding(int p_column) const {
+ ERR_FAIL_INDEX_V(p_column, columns.size(), false);
+
+ return columns[p_column].expand;
+}
+int Tree::get_column_expand_ratio(int p_column) const {
+ ERR_FAIL_INDEX_V(p_column, columns.size(), 1);
+
+ return columns[p_column].expand_ratio;
+}
+
+bool Tree::is_column_clipping_content(int p_column) const {
+ ERR_FAIL_INDEX_V(p_column, columns.size(), false);
+
+ return columns[p_column].clip_content;
+}
+
TreeItem *Tree::get_selected() const {
return selected_item;
}
@@ -3482,8 +3877,8 @@ TreeItem *Tree::get_next_selected(TreeItem *p_item) {
if (!p_item) {
p_item = root;
} else {
- if (p_item->children) {
- p_item = p_item->children;
+ if (p_item->first_child) {
+ p_item = p_item->first_child;
} else if (p_item->next) {
p_item = p_item->next;
@@ -3509,50 +3904,87 @@ TreeItem *Tree::get_next_selected(TreeItem *p_item) {
return nullptr;
}
-int Tree::get_column_width(int p_column) const {
+int Tree::get_column_minimum_width(int p_column) const {
ERR_FAIL_INDEX_V(p_column, columns.size(), -1);
- if (!columns[p_column].expand) {
- return columns[p_column].min_width;
+ int min_width = columns[p_column].custom_min_width;
+
+ if (show_column_titles) {
+ min_width = MAX(cache.font->get_string_size(columns[p_column].title).width, min_width);
+ }
+
+ if (!columns[p_column].clip_content) {
+ int depth = 0;
+ TreeItem *next;
+ for (TreeItem *item = get_root(); item; item = next) {
+ next = item->get_next_visible();
+ // Compute the depth in tree.
+ if (next && p_column == 0) {
+ if (next->get_parent() == item) {
+ depth += 1;
+ } else {
+ TreeItem *common_parent = item->get_parent();
+ while (common_parent != next->get_parent()) {
+ common_parent = common_parent->get_parent();
+ depth -= 1;
+ }
+ }
+ }
+
+ // Get the item minimum size.
+ Size2 item_size = item->get_minimum_size(p_column);
+ if (p_column == 0) {
+ item_size.width += cache.item_margin * depth;
+ }
+ min_width = MAX(min_width, item_size.width);
+ }
}
- int expand_area = get_size().width;
+ return min_width;
+}
- Ref<StyleBox> bg = cache.bg;
+int Tree::get_column_width(int p_column) const {
+ ERR_FAIL_INDEX_V(p_column, columns.size(), -1);
- if (bg.is_valid()) {
- expand_area -= bg->get_margin(SIDE_LEFT) + bg->get_margin(SIDE_RIGHT);
- }
+ int column_width = get_column_minimum_width(p_column);
- if (v_scroll->is_visible_in_tree()) {
- expand_area -= v_scroll->get_combined_minimum_size().width;
- }
+ if (columns[p_column].expand) {
+ int expand_area = get_size().width;
- int expanding_columns = 0;
- int expanding_total = 0;
+ Ref<StyleBox> bg = cache.bg;
- for (int i = 0; i < columns.size(); i++) {
- if (!columns[i].expand) {
- expand_area -= columns[i].min_width;
- } else {
- expanding_total += columns[i].min_width;
- expanding_columns++;
+ if (bg.is_valid()) {
+ expand_area -= bg->get_margin(SIDE_LEFT) + bg->get_margin(SIDE_RIGHT);
}
- }
- if (expand_area < expanding_total) {
- return columns[p_column].min_width;
- }
+ if (v_scroll->is_visible_in_tree()) {
+ expand_area -= v_scroll->get_combined_minimum_size().width;
+ }
+
+ int expanding_total = 0;
+
+ for (int i = 0; i < columns.size(); i++) {
+ expand_area -= get_column_minimum_width(i);
+ if (columns[i].expand) {
+ expanding_total += columns[i].expand_ratio;
+ }
+ }
- ERR_FAIL_COND_V(expanding_columns == 0, -1); // shouldn't happen
+ if (expand_area >= expanding_total && expanding_total > 0) {
+ column_width += expand_area * columns[p_column].expand_ratio / expanding_total;
+ }
+ }
- return expand_area * columns[p_column].min_width / expanding_total;
+ if (p_column < columns.size() - 1) {
+ column_width += cache.hseparation;
+ }
+ return column_width;
}
void Tree::propagate_set_columns(TreeItem *p_item) {
p_item->cells.resize(columns.size());
- TreeItem *c = p_item->get_children();
+ TreeItem *c = p_item->get_first_child();
while (c) {
propagate_set_columns(c);
c = c->next;
@@ -3602,8 +4034,8 @@ int Tree::get_item_offset(TreeItem *p_item) const {
ofs += cache.vseparation;
}
- if (it->children && !it->collapsed) {
- it = it->children;
+ if (it->first_child && !it->collapsed) {
+ it = it->first_child;
} else if (it->next) {
it = it->next;
@@ -3808,6 +4240,24 @@ void Tree::scroll_to_item(TreeItem *p_item) {
}
}
+void Tree::set_h_scroll_enabled(bool p_enable) {
+ h_scroll_enabled = p_enable;
+ minimum_size_changed();
+}
+
+bool Tree::is_h_scroll_enabled() const {
+ return h_scroll_enabled;
+}
+
+void Tree::set_v_scroll_enabled(bool p_enable) {
+ v_scroll_enabled = p_enable;
+ minimum_size_changed();
+}
+
+bool Tree::is_v_scroll_enabled() const {
+ return v_scroll_enabled;
+}
+
TreeItem *Tree::_search_item_text(TreeItem *p_at, const String &p_find, int *r_col, bool p_selectable, bool p_backwards) {
TreeItem *from = p_at;
TreeItem *loop = nullptr; // Safe-guard against infinite loop.
@@ -3926,7 +4376,7 @@ TreeItem *Tree::_find_item_at_pos(TreeItem *p_item, const Point2 &p_pos, int &r_
return nullptr; // do not try children, it's collapsed
}
- TreeItem *n = p_item->get_children();
+ TreeItem *n = p_item->get_first_child();
while (n) {
int ch;
TreeItem *r = _find_item_at_pos(n, pos, r_column, ch, section);
@@ -4183,8 +4633,14 @@ void Tree::_bind_methods() {
ClassDB::bind_method(D_METHOD("create_item", "parent", "idx"), &Tree::_create_item, DEFVAL(Variant()), DEFVAL(-1));
ClassDB::bind_method(D_METHOD("get_root"), &Tree::get_root);
- ClassDB::bind_method(D_METHOD("set_column_min_width", "column", "min_width"), &Tree::set_column_min_width);
+ ClassDB::bind_method(D_METHOD("set_column_custom_minimum_width", "column", "min_width"), &Tree::set_column_custom_minimum_width);
ClassDB::bind_method(D_METHOD("set_column_expand", "column", "expand"), &Tree::set_column_expand);
+ ClassDB::bind_method(D_METHOD("set_column_expand_ratio", "column", "ratio"), &Tree::set_column_expand_ratio);
+ ClassDB::bind_method(D_METHOD("set_column_clip_content", "column", "enable"), &Tree::set_column_clip_content);
+ ClassDB::bind_method(D_METHOD("is_column_expanding", "column"), &Tree::is_column_expanding);
+ ClassDB::bind_method(D_METHOD("is_column_clipping_content", "column"), &Tree::is_column_clipping_content);
+ ClassDB::bind_method(D_METHOD("get_column_expand_ratio", "column"), &Tree::get_column_expand_ratio);
+
ClassDB::bind_method(D_METHOD("get_column_width", "column"), &Tree::get_column_width);
ClassDB::bind_method(D_METHOD("set_hide_root", "enable"), &Tree::set_hide_root);
@@ -4229,6 +4685,12 @@ void Tree::_bind_methods() {
ClassDB::bind_method(D_METHOD("get_scroll"), &Tree::get_scroll);
ClassDB::bind_method(D_METHOD("scroll_to_item", "item"), &Tree::_scroll_to_item);
+ ClassDB::bind_method(D_METHOD("set_h_scroll_enabled", "h_scroll"), &Tree::set_h_scroll_enabled);
+ ClassDB::bind_method(D_METHOD("is_h_scroll_enabled"), &Tree::is_h_scroll_enabled);
+
+ ClassDB::bind_method(D_METHOD("set_v_scroll_enabled", "h_scroll"), &Tree::set_v_scroll_enabled);
+ ClassDB::bind_method(D_METHOD("is_v_scroll_enabled"), &Tree::is_v_scroll_enabled);
+
ClassDB::bind_method(D_METHOD("set_hide_folding", "hide"), &Tree::set_hide_folding);
ClassDB::bind_method(D_METHOD("is_folding_hidden"), &Tree::is_folding_hidden);
@@ -4246,8 +4708,10 @@ void Tree::_bind_methods() {
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "allow_rmb_select"), "set_allow_rmb_select", "get_allow_rmb_select");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "hide_folding"), "set_hide_folding", "is_folding_hidden");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "hide_root"), "set_hide_root", "is_root_hidden");
- ADD_PROPERTY(PropertyInfo(Variant::INT, "drop_mode_flags", PROPERTY_HINT_FLAGS, "On Item,In between"), "set_drop_mode_flags", "get_drop_mode_flags");
+ ADD_PROPERTY(PropertyInfo(Variant::INT, "drop_mode_flags", PROPERTY_HINT_FLAGS, "On Item,In Between"), "set_drop_mode_flags", "get_drop_mode_flags");
ADD_PROPERTY(PropertyInfo(Variant::INT, "select_mode", PROPERTY_HINT_ENUM, "Single,Row,Multi"), "set_select_mode", "get_select_mode");
+ ADD_PROPERTY(PropertyInfo(Variant::BOOL, "scroll_horizontal_enabled"), "set_h_scroll_enabled", "is_h_scroll_enabled");
+ ADD_PROPERTY(PropertyInfo(Variant::BOOL, "scroll_vertical_enabled"), "set_v_scroll_enabled", "is_v_scroll_enabled");
ADD_SIGNAL(MethodInfo("item_selected"));
ADD_SIGNAL(MethodInfo("cell_selected"));
@@ -4260,7 +4724,7 @@ void Tree::_bind_methods() {
ADD_SIGNAL(MethodInfo("item_custom_button_pressed"));
ADD_SIGNAL(MethodInfo("item_double_clicked"));
ADD_SIGNAL(MethodInfo("item_collapsed", PropertyInfo(Variant::OBJECT, "item", PROPERTY_HINT_RESOURCE_TYPE, "TreeItem")));
- //ADD_SIGNAL( MethodInfo("item_doubleclicked" ) );
+ //ADD_SIGNAL( MethodInfo("item_double_clicked" ) );
ADD_SIGNAL(MethodInfo("button_pressed", PropertyInfo(Variant::OBJECT, "item", PROPERTY_HINT_RESOURCE_TYPE, "TreeItem"), PropertyInfo(Variant::INT, "column"), PropertyInfo(Variant::INT, "id")));
ADD_SIGNAL(MethodInfo("custom_popup_edited", PropertyInfo(Variant::BOOL, "arrow_clicked")));
ADD_SIGNAL(MethodInfo("item_activated"));
@@ -4315,7 +4779,7 @@ Tree::Tree() {
h_scroll->connect("value_changed", callable_mp(this, &Tree::_scroll_moved));
v_scroll->connect("value_changed", callable_mp(this, &Tree::_scroll_moved));
- text_editor->connect("text_entered", callable_mp(this, &Tree::_text_editor_enter));
+ text_editor->connect("text_submitted", callable_mp(this, &Tree::_text_editor_submit));
popup_editor->connect("popup_hide", callable_mp(this, &Tree::_text_editor_modal_close));
popup_menu->connect("id_pressed", callable_mp(this, &Tree::popup_select));
value_editor->connect("value_changed", callable_mp(this, &Tree::value_editor_changed));
@@ -4325,6 +4789,8 @@ Tree::Tree() {
set_mouse_filter(MOUSE_FILTER_STOP);
set_clip_contents(true);
+
+ update_cache();
}
Tree::~Tree() {
diff --git a/scene/gui/tree.h b/scene/gui/tree.h
index d1407e24d4..10e6642303 100644
--- a/scene/gui/tree.h
+++ b/scene/gui/tree.h
@@ -112,8 +112,10 @@ private:
Vector<Button> buttons;
+ Ref<Font> custom_font;
+
Cell() {
- text_buf.instance();
+ text_buf.instantiate();
}
Size2 get_icon_size() const;
@@ -122,14 +124,18 @@ private:
Vector<Cell> cells;
- bool collapsed; // won't show children
- bool disable_folding;
- int custom_min_height;
+ bool collapsed = false; // won't show children
+ bool disable_folding = false;
+ int custom_min_height = 0;
+
+ TreeItem *parent = nullptr; // parent item
+ TreeItem *prev = nullptr; // previous in list
+ TreeItem *next = nullptr; // next in list
+ TreeItem *first_child = nullptr;
- TreeItem *parent; // parent item
- TreeItem *next; // next in list
- TreeItem *children; //child items
- Tree *tree; //tree (for reference)
+ Vector<TreeItem *> children_cache;
+ bool is_root = false; // for tree root
+ Tree *tree; // tree (for reference)
TreeItem(Tree *p_tree);
@@ -138,9 +144,40 @@ private:
void _cell_selected(int p_cell);
void _cell_deselected(int p_cell);
+ void _change_tree(Tree *p_tree);
+
+ _FORCE_INLINE_ void _create_children_cache() {
+ if (children_cache.is_empty()) {
+ TreeItem *c = first_child;
+ while (c) {
+ children_cache.append(c);
+ c = c->next;
+ }
+ }
+ }
+
+ _FORCE_INLINE_ void _unlink_from_tree() {
+ TreeItem *p = get_prev();
+ if (p) {
+ p->next = next;
+ }
+ if (next) {
+ next->prev = p;
+ }
+ if (parent) {
+ if (!parent->children_cache.is_empty()) {
+ parent->children_cache.remove(get_index());
+ }
+ if (parent->first_child == this) {
+ parent->first_child = next;
+ }
+ }
+ }
+
protected:
static void _bind_methods();
- //bind helpers
+
+ // Bind helpers
Dictionary _get_range_config(int p_column) {
Dictionary d;
double min = 0.0, max = 0.0, step = 0.0;
@@ -156,6 +193,13 @@ protected:
remove_child(Object::cast_to<TreeItem>(p_child));
}
+ void _move_before(Object *p_item) {
+ move_before(Object::cast_to<TreeItem>(p_item));
+ }
+ void _move_after(Object *p_item) {
+ move_after(Object::cast_to<TreeItem>(p_item));
+ }
+
Variant _call_recursive_bind(const Variant **p_args, int p_argcount, Callable::CallError &r_error);
public:
@@ -234,16 +278,6 @@ public:
void set_custom_minimum_height(int p_height);
int get_custom_minimum_height() const;
- TreeItem *get_prev();
- TreeItem *get_next();
- TreeItem *get_parent();
- TreeItem *get_children();
-
- TreeItem *get_prev_visible(bool p_wrap = false);
- TreeItem *get_next_visible(bool p_wrap = false);
-
- void remove_child(TreeItem *p_item);
-
void set_selectable(int p_column, bool p_selectable);
bool is_selectable(int p_column) const;
@@ -259,6 +293,9 @@ public:
Color get_custom_color(int p_column) const;
void clear_custom_color(int p_column);
+ void set_custom_font(int p_column, const Ref<Font> &p_font);
+ Ref<Font> get_custom_font(int p_column) const;
+
void set_custom_bg_color(int p_column, const Color &p_color, bool p_bg_outline = false);
void clear_custom_bg_color(int p_column);
Color get_custom_bg_color(int p_column) const;
@@ -269,22 +306,45 @@ public:
void set_tooltip(int p_column, const String &p_tooltip);
String get_tooltip(int p_column) const;
- void clear_children();
-
void set_text_align(int p_column, TextAlign p_align);
TextAlign get_text_align(int p_column) const;
void set_expand_right(int p_column, bool p_enable);
bool get_expand_right(int p_column) const;
- void move_to_top();
- void move_to_bottom();
-
void set_disable_folding(bool p_disable);
bool is_folding_disabled() const;
+ Size2 get_minimum_size(int p_column);
+
+ /* Item manipulation */
+
+ TreeItem *create_child(int p_idx = -1);
+
+ Tree *get_tree() const;
+
+ TreeItem *get_prev();
+ TreeItem *get_next() const;
+ TreeItem *get_parent() const;
+ TreeItem *get_first_child() const;
+
+ TreeItem *get_prev_visible(bool p_wrap = false);
+ TreeItem *get_next_visible(bool p_wrap = false);
+
+ TreeItem *get_child(int p_idx);
+ int get_child_count();
+ Array get_children();
+ int get_index();
+
+ void move_before(TreeItem *p_item);
+ void move_after(TreeItem *p_item);
+
+ void remove_child(TreeItem *p_item);
+
void call_recursive(const StringName &p_method, const Variant **p_args, int p_argcount, Callable::CallError &r_error);
+ void clear_children();
+
~TreeItem();
};
@@ -350,15 +410,17 @@ private:
int drop_mode_flags = 0;
struct ColumnInfo {
- int min_width = 1;
+ int custom_min_width = 0;
+ int expand_ratio = 1;
bool expand = true;
+ bool clip_content = false;
String title;
Ref<TextLine> text_buf;
Dictionary opentype_features;
String language;
Control::TextDirection text_direction = Control::TEXT_DIRECTION_INHERITED;
ColumnInfo() {
- text_buf.instance();
+ text_buf.instantiate();
}
};
@@ -390,8 +452,8 @@ private:
void draw_item_rect(TreeItem::Cell &p_cell, const Rect2i &p_rect, const Color &p_color, const Color &p_icon_color, int p_ol_size, const Color &p_ol_color);
int draw_item(const Point2i &p_pos, const Point2 &p_draw_ofs, const Size2 &p_draw_size, TreeItem *p_item);
void select_single_item(TreeItem *p_selected, TreeItem *p_current, int p_col, TreeItem *p_prev = nullptr, bool *r_in_range = nullptr, bool p_force_deselect = false);
- int propagate_mouse_event(const Point2i &p_pos, int x_ofs, int y_ofs, bool p_doubleclick, TreeItem *p_item, int p_button, const Ref<InputEventWithModifiers> &p_mod);
- void _text_editor_enter(String p_text);
+ int propagate_mouse_event(const Point2i &p_pos, int x_ofs, int y_ofs, int x_limit, bool p_double_click, TreeItem *p_item, int p_button, const Ref<InputEventWithModifiers> &p_mod);
+ void _text_editor_submit(String p_text);
void _text_editor_modal_close();
void value_editor_changed(double p_value);
@@ -441,7 +503,10 @@ private:
Color guide_color;
Color drop_position_color;
Color relationship_line_color;
+ Color parent_hl_line_color;
+ Color children_hl_line_color;
Color custom_button_font_highlight;
+ Color font_outline_color;
int hseparation = 0;
int vseparation = 0;
@@ -449,9 +514,14 @@ private:
int button_margin = 0;
Point2 offset;
int draw_relationship_lines = 0;
+ int relationship_line_width = 0;
+ int parent_hl_line_width = 0;
+ int children_hl_line_width = 0;
+ int parent_hl_line_margin = 0;
int draw_guides = 0;
int scroll_border = 0;
int scroll_speed = 0;
+ int font_outline_size = 0;
enum ClickType {
CLICK_NONE,
@@ -474,6 +544,8 @@ private:
Point2i text_editor_position;
+ bool rtl = false;
+
} cache;
int _get_title_button_height() const;
@@ -482,6 +554,9 @@ private:
HScrollBar *h_scroll;
VScrollBar *v_scroll;
+ bool h_scroll_enabled = true;
+ bool v_scroll_enabled = true;
+
Size2 get_internal_min_size() const;
void update_cache();
void update_scrollbars();
@@ -521,6 +596,8 @@ private:
bool hide_folding = false;
int _count_selected_items(TreeItem *p_from) const;
+ bool _is_branch_selected(TreeItem *p_from) const;
+ bool _is_sibling_branch_selected(TreeItem *p_from) const;
void _go_left();
void _go_right();
void _go_down();
@@ -557,12 +634,19 @@ public:
void clear();
TreeItem *create_item(TreeItem *p_parent = nullptr, int p_idx = -1);
- TreeItem *get_root();
- TreeItem *get_last_item();
+ TreeItem *get_root() const;
+ TreeItem *get_last_item() const;
- void set_column_min_width(int p_column, int p_min_width);
+ void set_column_custom_minimum_width(int p_column, int p_min_width);
void set_column_expand(int p_column, bool p_expand);
+ void set_column_expand_ratio(int p_column, int p_ratio);
+ void set_column_clip_content(int p_column, bool p_fit);
+ int get_column_minimum_width(int p_column) const;
int get_column_width(int p_column) const;
+ int get_column_expand_ratio(int p_column) const;
+
+ bool is_column_expanding(int p_column) const;
+ bool is_column_clipping_content(int p_column) const;
void set_hide_root(bool p_enabled);
bool is_root_hidden() const;
@@ -604,6 +688,7 @@ public:
int get_item_offset(TreeItem *p_item) const;
Rect2 get_item_rect(TreeItem *p_item, int p_column = -1) const;
bool edit_selected();
+ bool is_editing();
// First item that starts with the text, from the current focused item down and wraps around.
TreeItem *search_item_text(const String &p_find, int *r_col = nullptr, bool p_selectable = false);
@@ -612,6 +697,10 @@ public:
Point2 get_scroll() const;
void scroll_to_item(TreeItem *p_item);
+ void set_h_scroll_enabled(bool p_enable);
+ bool is_h_scroll_enabled() const;
+ void set_v_scroll_enabled(bool p_enable);
+ bool is_v_scroll_enabled() const;
void set_cursor_can_exit_tree(bool p_enable);
diff --git a/scene/gui/video_player.cpp b/scene/gui/video_player.cpp
index 0590ae2415..229b5384ea 100644
--- a/scene/gui/video_player.cpp
+++ b/scene/gui/video_player.cpp
@@ -452,12 +452,12 @@ void VideoPlayer::_bind_methods() {
ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "stream", PROPERTY_HINT_RESOURCE_TYPE, "VideoStream"), "set_stream", "get_stream");
//ADD_PROPERTY( PropertyInfo(Variant::BOOL, "stream/loop"), "set_loop", "has_loop") ;
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "volume_db", PROPERTY_HINT_RANGE, "-80,24,0.01"), "set_volume_db", "get_volume_db");
- ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "volume", PROPERTY_HINT_EXP_RANGE, "0,15,0.01", 0), "set_volume", "get_volume");
+ ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "volume", PROPERTY_HINT_RANGE, "0,15,0.01,exp", PROPERTY_USAGE_NONE), "set_volume", "get_volume");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "autoplay"), "set_autoplay", "has_autoplay");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "paused"), "set_paused", "is_paused");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "expand"), "set_expand", "has_expand");
ADD_PROPERTY(PropertyInfo(Variant::INT, "buffering_msec", PROPERTY_HINT_RANGE, "10,1000"), "set_buffering_msec", "get_buffering_msec");
- ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "stream_position", PROPERTY_HINT_RANGE, "0,1280000,0.1", 0), "set_stream_position", "get_stream_position");
+ ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "stream_position", PROPERTY_HINT_RANGE, "0,1280000,0.1", PROPERTY_USAGE_NONE), "set_stream_position", "get_stream_position");
ADD_PROPERTY(PropertyInfo(Variant::STRING_NAME, "bus", PROPERTY_HINT_ENUM, ""), "set_bus", "get_bus");
}