summaryrefslogtreecommitdiff
path: root/scene/gui
diff options
context:
space:
mode:
authorbruvzg <7645683+bruvzg@users.noreply.github.com>2020-09-03 14:22:16 +0300
committerbruvzg <7645683+bruvzg@users.noreply.github.com>2020-11-26 14:25:48 +0200
commit99666de00fb30cb86473257776504ca70b4469c3 (patch)
tree6ad5723c1a429e82b8b4b12cc10f2bec3102cac3 /scene/gui
parent07d14f5bb8e8a2cb3b2137d1ef4fb6c3b46c0873 (diff)
[Complex Text Layouts] Refactor Font class, default themes and controls to use Text Server interface.
Implement interface mirroring. Add TextLine and TextParagraph classes. Handle UTF-16 input on macOS and Windows.
Diffstat (limited to 'scene/gui')
-rw-r--r--scene/gui/aspect_ratio_container.cpp7
-rw-r--r--scene/gui/box_container.cpp56
-rw-r--r--scene/gui/button.cpp302
-rw-r--r--scene/gui/button.h23
-rw-r--r--scene/gui/check_box.cpp24
-rw-r--r--scene/gui/check_button.cpp38
-rw-r--r--scene/gui/code_edit.cpp2
-rw-r--r--scene/gui/container.cpp7
-rw-r--r--scene/gui/control.cpp327
-rw-r--r--scene/gui/control.h45
-rw-r--r--scene/gui/file_dialog.cpp3
-rw-r--r--scene/gui/graph_node.cpp142
-rw-r--r--scene/gui/graph_node.h18
-rw-r--r--scene/gui/grid_container.cpp28
-rw-r--r--scene/gui/item_list.cpp229
-rw-r--r--scene/gui/item_list.h17
-rw-r--r--scene/gui/label.cpp18
-rw-r--r--scene/gui/line_edit.cpp14
-rw-r--r--scene/gui/link_button.cpp186
-rw-r--r--scene/gui/link_button.h30
-rw-r--r--scene/gui/option_button.cpp29
-rw-r--r--scene/gui/popup_menu.cpp208
-rw-r--r--scene/gui/popup_menu.h24
-rw-r--r--scene/gui/progress_bar.cpp18
-rw-r--r--scene/gui/rich_text_label.cpp14
-rw-r--r--scene/gui/scroll_bar.h4
-rw-r--r--scene/gui/scroll_container.cpp19
-rw-r--r--scene/gui/scroll_container.h1
-rw-r--r--scene/gui/spin_box.cpp21
-rw-r--r--scene/gui/split_container.cpp23
-rw-r--r--scene/gui/tab_container.cpp334
-rw-r--r--scene/gui/tab_container.h4
-rw-r--r--scene/gui/tabs.cpp248
-rw-r--r--scene/gui/tabs.h19
-rw-r--r--scene/gui/text_edit.cpp39
-rw-r--r--scene/gui/text_edit.h1
-rw-r--r--scene/gui/tree.cpp499
-rw-r--r--scene/gui/tree.h49
38 files changed, 2603 insertions, 467 deletions
diff --git a/scene/gui/aspect_ratio_container.cpp b/scene/gui/aspect_ratio_container.cpp
index 9f60131186..672102bf7a 100644
--- a/scene/gui/aspect_ratio_container.cpp
+++ b/scene/gui/aspect_ratio_container.cpp
@@ -73,6 +73,7 @@ void AspectRatioContainer::set_alignment_vertical(AlignMode p_alignment_vertical
void AspectRatioContainer::_notification(int p_what) {
switch (p_what) {
case NOTIFICATION_SORT_CHILDREN: {
+ bool rtl = is_layout_rtl();
Size2 size = get_size();
for (int i = 0; i < get_child_count(); i++) {
Control *c = Object::cast_to<Control>(get_child(i));
@@ -130,7 +131,11 @@ void AspectRatioContainer::_notification(int p_what) {
}
Vector2 offset = (size - child_size) * Vector2(align_x, align_y);
- fit_child_in_rect(c, Rect2(offset, child_size));
+ if (rtl) {
+ fit_child_in_rect(c, Rect2(Vector2(size.x - offset.x - child_size.x, offset.y), child_size));
+ } else {
+ fit_child_in_rect(c, Rect2(offset, child_size));
+ }
}
} break;
}
diff --git a/scene/gui/box_container.cpp b/scene/gui/box_container.cpp
index 33e030a573..fdd88d155f 100644
--- a/scene/gui/box_container.cpp
+++ b/scene/gui/box_container.cpp
@@ -44,6 +44,7 @@ void BoxContainer::_resort() {
Size2i new_size = get_size();
int sep = get_theme_constant("separation"); //,vertical?"VBoxContainer":"HBoxContainer");
+ bool rtl = is_layout_rtl();
bool first = true;
int children_count = 0;
@@ -152,22 +153,53 @@ void BoxContainer::_resort() {
int ofs = 0;
if (!has_stretched) {
- switch (align) {
- case ALIGN_BEGIN:
- break;
- case ALIGN_CENTER:
- ofs = stretch_diff / 2;
- break;
- case ALIGN_END:
- ofs = stretch_diff;
- break;
+ if (!vertical) {
+ switch (align) {
+ case ALIGN_BEGIN:
+ if (rtl) {
+ ofs = stretch_diff;
+ }
+ break;
+ case ALIGN_CENTER:
+ ofs = stretch_diff / 2;
+ break;
+ case ALIGN_END:
+ if (!rtl) {
+ ofs = stretch_diff;
+ }
+ break;
+ }
+ } else {
+ switch (align) {
+ case ALIGN_BEGIN:
+ break;
+ case ALIGN_CENTER:
+ ofs = stretch_diff / 2;
+ break;
+ case ALIGN_END:
+ ofs = stretch_diff;
+ break;
+ }
}
}
first = true;
int idx = 0;
- for (int i = 0; i < get_child_count(); i++) {
+ int start;
+ int end;
+ int delta;
+ if (!rtl || vertical) {
+ start = 0;
+ end = get_child_count();
+ delta = +1;
+ } else {
+ start = get_child_count() - 1;
+ end = -1;
+ delta = -1;
+ }
+
+ for (int i = start; i != end; i += delta) {
Control *c = Object::cast_to<Control>(get_child(i));
if (!c || !c->is_visible_in_tree()) {
continue;
@@ -265,6 +297,10 @@ void BoxContainer::_notification(int p_what) {
case NOTIFICATION_THEME_CHANGED: {
minimum_size_changed();
} break;
+ case NOTIFICATION_TRANSLATION_CHANGED:
+ case NOTIFICATION_LAYOUT_DIRECTION_CHANGED: {
+ queue_sort();
+ } break;
}
}
diff --git a/scene/gui/button.cpp b/scene/gui/button.cpp
index e86ad09aa6..711e5f9262 100644
--- a/scene/gui/button.cpp
+++ b/scene/gui/button.cpp
@@ -34,7 +34,7 @@
#include "servers/rendering_server.h"
Size2 Button::get_minimum_size() const {
- Size2 minsize = get_theme_font("font")->get_string_size(xl_text);
+ Size2 minsize = text_buf->get_size();
if (clip_text) {
minsize.width = 0;
}
@@ -65,8 +65,19 @@ void Button::_set_internal_margin(Margin p_margin, float p_value) {
void Button::_notification(int p_what) {
switch (p_what) {
+ case NOTIFICATION_LAYOUT_DIRECTION_CHANGED: {
+ update();
+ } break;
case NOTIFICATION_TRANSLATION_CHANGED: {
xl_text = tr(text);
+ _shape();
+
+ minimum_size_changed();
+ update();
+ } break;
+ case NOTIFICATION_THEME_CHANGED: {
+ _shape();
+
minimum_size_changed();
update();
} break;
@@ -77,10 +88,16 @@ void Button::_notification(int p_what) {
Color color_icon(1, 1, 1, 1);
Ref<StyleBox> style = get_theme_stylebox("normal");
+ bool rtl = is_layout_rtl();
switch (get_draw_mode()) {
case DRAW_NORMAL: {
- style = get_theme_stylebox("normal");
+ if (rtl && has_theme_stylebox("normal_mirrored")) {
+ style = get_theme_stylebox("normal_mirrored");
+ } else {
+ style = get_theme_stylebox("normal");
+ }
+
if (!flat) {
style->draw(ci, Rect2(Point2(0, 0), size));
}
@@ -91,7 +108,12 @@ void Button::_notification(int p_what) {
} break;
case DRAW_HOVER_PRESSED: {
if (has_theme_stylebox("hover_pressed") && has_theme_stylebox_override("hover_pressed")) {
- style = get_theme_stylebox("hover_pressed");
+ if (rtl && has_theme_stylebox("hover_pressed_mirrored")) {
+ style = get_theme_stylebox("hover_pressed_mirrored");
+ } else {
+ style = get_theme_stylebox("hover_pressed");
+ }
+
if (!flat) {
style->draw(ci, Rect2(Point2(0, 0), size));
}
@@ -109,7 +131,12 @@ void Button::_notification(int p_what) {
[[fallthrough]];
}
case DRAW_PRESSED: {
- style = get_theme_stylebox("pressed");
+ if (rtl && has_theme_stylebox("pressed_mirrored")) {
+ style = get_theme_stylebox("pressed_mirrored");
+ } else {
+ style = get_theme_stylebox("pressed");
+ }
+
if (!flat) {
style->draw(ci, Rect2(Point2(0, 0), size));
}
@@ -124,7 +151,12 @@ void Button::_notification(int p_what) {
} break;
case DRAW_HOVER: {
- style = get_theme_stylebox("hover");
+ if (rtl && has_theme_stylebox("hover_mirrored")) {
+ style = get_theme_stylebox("hover_mirrored");
+ } else {
+ style = get_theme_stylebox("hover");
+ }
+
if (!flat) {
style->draw(ci, Rect2(Point2(0, 0), size));
}
@@ -135,7 +167,12 @@ void Button::_notification(int p_what) {
} break;
case DRAW_DISABLED: {
- style = get_theme_stylebox("disabled");
+ if (rtl && has_theme_stylebox("disabled_mirrored")) {
+ style = get_theme_stylebox("disabled_mirrored");
+ } else {
+ style = get_theme_stylebox("disabled");
+ }
+
if (!flat) {
style->draw(ci, Rect2(Point2(0, 0), size));
}
@@ -152,7 +189,6 @@ void Button::_notification(int p_what) {
style2->draw(ci, Rect2(Point2(), size));
}
- Ref<Font> font = get_theme_font("font");
Ref<Texture2D> _icon;
if (icon.is_null() && has_theme_icon("icon")) {
_icon = Control::get_theme_icon("icon");
@@ -168,15 +204,21 @@ void Button::_notification(int p_what) {
}
float icon_ofs_region = 0;
- if (_internal_margin[MARGIN_LEFT] > 0) {
- icon_ofs_region = _internal_margin[MARGIN_LEFT] + get_theme_constant("hseparation");
+ if (rtl) {
+ if (_internal_margin[MARGIN_RIGHT] > 0) {
+ icon_ofs_region = _internal_margin[MARGIN_RIGHT] + get_theme_constant("hseparation");
+ }
+ } else {
+ if (_internal_margin[MARGIN_LEFT] > 0) {
+ icon_ofs_region = _internal_margin[MARGIN_LEFT] + get_theme_constant("hseparation");
+ }
}
if (expand_icon) {
Size2 _size = get_size() - style->get_offset() * 2;
_size.width -= get_theme_constant("hseparation") + icon_ofs_region;
if (!clip_text) {
- _size.width -= get_theme_font("font")->get_string_size(xl_text).width;
+ _size.width -= text_buf->get_size().width;
}
float icon_width = _icon->get_width() * _size.height / _icon->get_height();
float icon_height = _size.height;
@@ -186,14 +228,26 @@ void Button::_notification(int p_what) {
icon_height = _icon->get_height() * icon_width / _icon->get_width();
}
- icon_region = Rect2(style->get_offset() + Point2(icon_ofs_region, (_size.height - icon_height) / 2), Size2(icon_width, icon_height));
+ if (rtl) {
+ icon_region = Rect2(Point2(size.width - (icon_ofs_region + icon_width + style->get_margin(MARGIN_RIGHT)), style->get_margin(MARGIN_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));
+ }
} else {
- icon_region = Rect2(style->get_offset() + Point2(icon_ofs_region, Math::floor((valign - _icon->get_height()) / 2.0)), _icon->get_size());
+ if (rtl) {
+ icon_region = Rect2(Point2(size.width - (icon_ofs_region + _icon->get_size().width + style->get_margin(MARGIN_RIGHT)), style->get_margin(MARGIN_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());
+ }
}
}
Point2 icon_ofs = !_icon.is_null() ? Point2(icon_region.size.width + get_theme_constant("hseparation"), 0) : Point2();
int text_clip = size.width - style->get_minimum_size().width - icon_ofs.width;
+ text_buf->set_width(clip_text ? text_clip : -1);
+
+ int text_width = clip_text ? MIN(text_clip, text_buf->get_size().x) : text_buf->get_size().x;
+
if (_internal_margin[MARGIN_LEFT] > 0) {
text_clip -= _internal_margin[MARGIN_LEFT] + get_theme_constant("hseparation");
}
@@ -201,14 +255,22 @@ void Button::_notification(int p_what) {
text_clip -= _internal_margin[MARGIN_RIGHT] + get_theme_constant("hseparation");
}
- Point2 text_ofs = (size - style->get_minimum_size() - icon_ofs - font->get_string_size(xl_text) - Point2(_internal_margin[MARGIN_RIGHT] - _internal_margin[MARGIN_LEFT], 0)) / 2.0;
+ Point2 text_ofs = (size - style->get_minimum_size() - icon_ofs - text_buf->get_size() - Point2(_internal_margin[MARGIN_RIGHT] - _internal_margin[MARGIN_LEFT], 0)) / 2.0;
switch (align) {
case ALIGN_LEFT: {
- if (_internal_margin[MARGIN_LEFT] > 0) {
- text_ofs.x = style->get_margin(MARGIN_LEFT) + icon_ofs.x + _internal_margin[MARGIN_LEFT] + get_theme_constant("hseparation");
+ if (rtl) {
+ if (_internal_margin[MARGIN_RIGHT] > 0) {
+ text_ofs.x = size.x - style->get_margin(MARGIN_RIGHT) - text_width - _internal_margin[MARGIN_RIGHT] - get_theme_constant("hseparation");
+ } else {
+ text_ofs.x = size.x - style->get_margin(MARGIN_RIGHT) - text_width;
+ }
} else {
- text_ofs.x = style->get_margin(MARGIN_LEFT) + icon_ofs.x;
+ if (_internal_margin[MARGIN_LEFT] > 0) {
+ text_ofs.x = style->get_margin(MARGIN_LEFT) + icon_ofs.x + _internal_margin[MARGIN_LEFT] + get_theme_constant("hseparation");
+ } else {
+ text_ofs.x = style->get_margin(MARGIN_LEFT) + icon_ofs.x;
+ }
}
text_ofs.y += style->get_offset().y;
} break;
@@ -220,17 +282,34 @@ void Button::_notification(int p_what) {
text_ofs += style->get_offset();
} break;
case ALIGN_RIGHT: {
- if (_internal_margin[MARGIN_RIGHT] > 0) {
- text_ofs.x = size.x - style->get_margin(MARGIN_RIGHT) - font->get_string_size(xl_text).x - _internal_margin[MARGIN_RIGHT] - get_theme_constant("hseparation");
+ if (rtl) {
+ if (_internal_margin[MARGIN_LEFT] > 0) {
+ text_ofs.x = style->get_margin(MARGIN_LEFT) + icon_ofs.x + _internal_margin[MARGIN_LEFT] + get_theme_constant("hseparation");
+ } else {
+ text_ofs.x = style->get_margin(MARGIN_LEFT) + icon_ofs.x;
+ }
} else {
- text_ofs.x = size.x - style->get_margin(MARGIN_RIGHT) - font->get_string_size(xl_text).x;
+ if (_internal_margin[MARGIN_RIGHT] > 0) {
+ text_ofs.x = size.x - style->get_margin(MARGIN_RIGHT) - text_width - _internal_margin[MARGIN_RIGHT] - get_theme_constant("hseparation");
+ } else {
+ text_ofs.x = size.x - style->get_margin(MARGIN_RIGHT) - text_width;
+ }
}
text_ofs.y += style->get_offset().y;
} break;
}
- text_ofs.y += font->get_ascent();
- font->draw(ci, text_ofs.floor(), xl_text, color, clip_text ? text_clip : -1);
+ if (rtl) {
+ text_ofs.x -= icon_ofs.x;
+ }
+
+ Color font_outline_modulate = get_theme_color("font_outline_modulate");
+ int outline_size = get_theme_constant("outline_size");
+ if (outline_size > 0 && font_outline_modulate.a > 0) {
+ text_buf->draw_outline(ci, text_ofs.floor(), outline_size, font_outline_modulate);
+ }
+
+ text_buf->draw(ci, text_ofs.floor(), color);
if (!_icon.is_null() && icon_region.size.width > 0) {
draw_texture_rect_region(_icon, icon_region, Rect2(Point2(), _icon->get_size()), color_icon);
@@ -239,29 +318,90 @@ void Button::_notification(int p_what) {
}
}
+void Button::_shape() {
+ Ref<Font> font = get_theme_font("font");
+ int font_size = get_theme_font_size("font_size");
+
+ text_buf->clear();
+ if (text_direction == Control::TEXT_DIRECTION_INHERITED) {
+ text_buf->set_direction(is_layout_rtl() ? TextServer::DIRECTION_RTL : TextServer::DIRECTION_LTR);
+ } else {
+ text_buf->set_direction((TextServer::Direction)text_direction);
+ }
+ text_buf->add_string(xl_text, font, font_size, opentype_features, (language != "") ? language : TranslationServer::get_singleton()->get_tool_locale());
+}
+
void Button::set_text(const String &p_text) {
- if (text == p_text) {
- return;
+ if (text != p_text) {
+ text = p_text;
+ xl_text = tr(text);
+ _shape();
+
+ update();
+ _change_notify("text");
+ minimum_size_changed();
}
- text = p_text;
- xl_text = tr(p_text);
- update();
- _change_notify("text");
- minimum_size_changed();
}
String Button::get_text() const {
return text;
}
-void Button::set_icon(const Ref<Texture2D> &p_icon) {
- if (icon == p_icon) {
- return;
+void Button::set_text_direction(Control::TextDirection p_text_direction) {
+ ERR_FAIL_COND((int)p_text_direction < -1 || (int)p_text_direction > 3);
+ if (text_direction != p_text_direction) {
+ text_direction = p_text_direction;
+ _shape();
+ update();
}
- icon = p_icon;
+}
+
+Control::TextDirection Button::get_text_direction() const {
+ return text_direction;
+}
+
+void Button::clear_opentype_features() {
+ opentype_features.clear();
+ _shape();
update();
- _change_notify("icon");
- minimum_size_changed();
+}
+
+void Button::set_opentype_feature(const String &p_name, int p_value) {
+ int32_t tag = TS->name_to_tag(p_name);
+ if (!opentype_features.has(tag) || (int)opentype_features[tag] != p_value) {
+ opentype_features[tag] = p_value;
+ _shape();
+ update();
+ }
+}
+
+int Button::get_opentype_feature(const String &p_name) const {
+ int32_t tag = TS->name_to_tag(p_name);
+ if (!opentype_features.has(tag)) {
+ return -1;
+ }
+ return opentype_features[tag];
+}
+
+void Button::set_language(const String &p_language) {
+ if (language != p_language) {
+ language = p_language;
+ _shape();
+ update();
+ }
+}
+
+String Button::get_language() const {
+ return language;
+}
+
+void Button::set_icon(const Ref<Texture2D> &p_icon) {
+ if (icon != p_icon) {
+ icon = p_icon;
+ update();
+ _change_notify("icon");
+ minimum_size_changed();
+ }
}
Ref<Texture2D> Button::get_icon() const {
@@ -269,9 +409,11 @@ Ref<Texture2D> Button::get_icon() const {
}
void Button::set_expand_icon(bool p_expand_icon) {
- expand_icon = p_expand_icon;
- update();
- minimum_size_changed();
+ if (expand_icon != p_expand_icon) {
+ expand_icon = p_expand_icon;
+ update();
+ minimum_size_changed();
+ }
}
bool Button::is_expand_icon() const {
@@ -279,9 +421,11 @@ bool Button::is_expand_icon() const {
}
void Button::set_flat(bool p_flat) {
- flat = p_flat;
- update();
- _change_notify("flat");
+ if (flat != p_flat) {
+ flat = p_flat;
+ update();
+ _change_notify("flat");
+ }
}
bool Button::is_flat() const {
@@ -289,9 +433,11 @@ bool Button::is_flat() const {
}
void Button::set_clip_text(bool p_clip_text) {
- clip_text = p_clip_text;
- update();
- minimum_size_changed();
+ if (clip_text != p_clip_text) {
+ clip_text = p_clip_text;
+ update();
+ minimum_size_changed();
+ }
}
bool Button::get_clip_text() const {
@@ -299,17 +445,76 @@ bool Button::get_clip_text() const {
}
void Button::set_text_align(TextAlign p_align) {
- align = p_align;
- update();
+ if (align != p_align) {
+ align = p_align;
+ update();
+ }
}
Button::TextAlign Button::get_text_align() const {
return align;
}
+bool Button::_set(const StringName &p_name, const Variant &p_value) {
+ String str = p_name;
+ if (str.begins_with("opentype_features/")) {
+ String name = str.get_slicec('/', 1);
+ int32_t tag = TS->name_to_tag(name);
+ double value = p_value;
+ if (value == -1) {
+ if (opentype_features.has(tag)) {
+ opentype_features.erase(tag);
+ _shape();
+ update();
+ }
+ } else {
+ if ((double)opentype_features[tag] != value) {
+ opentype_features[tag] = value;
+ _shape();
+ update();
+ }
+ }
+ _change_notify();
+ return true;
+ }
+
+ return false;
+}
+
+bool Button::_get(const StringName &p_name, Variant &r_ret) const {
+ String str = p_name;
+ if (str.begins_with("opentype_features/")) {
+ String name = str.get_slicec('/', 1);
+ int32_t tag = TS->name_to_tag(name);
+ if (opentype_features.has(tag)) {
+ r_ret = opentype_features[tag];
+ return true;
+ } else {
+ r_ret = -1;
+ return true;
+ }
+ }
+ return false;
+}
+
+void Button::_get_property_list(List<PropertyInfo> *p_list) const {
+ for (const Variant *ftr = opentype_features.next(nullptr); ftr != nullptr; ftr = opentype_features.next(ftr)) {
+ String name = TS->tag_to_name(*ftr);
+ p_list->push_back(PropertyInfo(Variant::FLOAT, "opentype_features/" + name));
+ }
+ p_list->push_back(PropertyInfo(Variant::NIL, "opentype_features/_new", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_EDITOR));
+}
+
void Button::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_text", "text"), &Button::set_text);
ClassDB::bind_method(D_METHOD("get_text"), &Button::get_text);
+ ClassDB::bind_method(D_METHOD("set_text_direction", "direction"), &Button::set_text_direction);
+ ClassDB::bind_method(D_METHOD("get_text_direction"), &Button::get_text_direction);
+ ClassDB::bind_method(D_METHOD("set_opentype_feature", "tag", "value"), &Button::set_opentype_feature);
+ ClassDB::bind_method(D_METHOD("get_opentype_feature", "tag"), &Button::get_opentype_feature);
+ ClassDB::bind_method(D_METHOD("clear_opentype_features"), &Button::clear_opentype_features);
+ ClassDB::bind_method(D_METHOD("set_language", "language"), &Button::set_language);
+ 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);
@@ -325,7 +530,9 @@ void Button::_bind_methods() {
BIND_ENUM_CONSTANT(ALIGN_CENTER);
BIND_ENUM_CONSTANT(ALIGN_RIGHT);
- ADD_PROPERTY(PropertyInfo(Variant::STRING, "text", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT_INTL), "set_text", "get_text");
+ 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::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");
@@ -334,6 +541,9 @@ void Button::_bind_methods() {
}
Button::Button(const String &p_text) {
+ text_buf.instance();
+ text_buf->set_flags(TextServer::BREAK_MANDATORY);
+
flat = false;
clip_text = false;
expand_icon = false;
diff --git a/scene/gui/button.h b/scene/gui/button.h
index 5b44b1322e..da89e5e6c4 100644
--- a/scene/gui/button.h
+++ b/scene/gui/button.h
@@ -32,6 +32,7 @@
#define BUTTON_H
#include "scene/gui/base_button.h"
+#include "scene/resources/text_paragraph.h"
class Button : public BaseButton {
GDCLASS(Button, BaseButton);
@@ -47,23 +48,45 @@ private:
bool flat;
String text;
String xl_text;
+ Ref<TextParagraph> text_buf;
+
+ Dictionary opentype_features;
+ String language;
+ TextDirection text_direction = TEXT_DIRECTION_AUTO;
+
Ref<Texture2D> icon;
bool expand_icon;
bool clip_text;
TextAlign align;
float _internal_margin[4];
+ void _shape();
+
protected:
void _set_internal_margin(Margin p_margin, float p_value);
void _notification(int p_what);
static void _bind_methods();
+ 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;
+
public:
virtual Size2 get_minimum_size() const override;
void set_text(const String &p_text);
String get_text() const;
+ void set_text_direction(TextDirection p_text_direction);
+ TextDirection get_text_direction() const;
+
+ void set_opentype_feature(const String &p_name, int p_value);
+ int get_opentype_feature(const String &p_name) const;
+ void clear_opentype_features();
+
+ void set_language(const String &p_language);
+ String get_language() const;
+
void set_icon(const Ref<Texture2D> &p_icon);
Ref<Texture2D> get_icon() const;
diff --git a/scene/gui/check_box.cpp b/scene/gui/check_box.cpp
index df6f38f65d..0c78369688 100644
--- a/scene/gui/check_box.cpp
+++ b/scene/gui/check_box.cpp
@@ -68,8 +68,14 @@ Size2 CheckBox::get_minimum_size() const {
}
void CheckBox::_notification(int p_what) {
- if (p_what == NOTIFICATION_THEME_CHANGED) {
- _set_internal_margin(MARGIN_LEFT, get_icon_size().width);
+ if ((p_what == NOTIFICATION_THEME_CHANGED) || (p_what == NOTIFICATION_LAYOUT_DIRECTION_CHANGED || (p_what == NOTIFICATION_TRANSLATION_CHANGED))) {
+ if (is_layout_rtl()) {
+ _set_internal_margin(MARGIN_LEFT, 0.f);
+ _set_internal_margin(MARGIN_RIGHT, get_icon_size().width);
+ } else {
+ _set_internal_margin(MARGIN_LEFT, get_icon_size().width);
+ _set_internal_margin(MARGIN_RIGHT, 0.f);
+ }
} else if (p_what == NOTIFICATION_DRAW) {
RID ci = get_canvas_item();
@@ -78,7 +84,11 @@ void CheckBox::_notification(int p_what) {
Ref<StyleBox> sb = get_theme_stylebox("normal");
Vector2 ofs;
- ofs.x = sb->get_margin(MARGIN_LEFT);
+ if (is_layout_rtl()) {
+ ofs.x = get_size().x - sb->get_margin(MARGIN_RIGHT) - get_icon_size().width;
+ } else {
+ ofs.x = sb->get_margin(MARGIN_LEFT);
+ }
ofs.y = int((get_size().height - get_icon_size().height) / 2) + get_theme_constant("check_vadjust");
if (is_pressed()) {
@@ -96,8 +106,14 @@ bool CheckBox::is_radio() {
CheckBox::CheckBox(const String &p_text) :
Button(p_text) {
set_toggle_mode(true);
+
set_text_align(ALIGN_LEFT);
- _set_internal_margin(MARGIN_LEFT, get_icon_size().width);
+
+ if (is_layout_rtl()) {
+ _set_internal_margin(MARGIN_RIGHT, get_icon_size().width);
+ } else {
+ _set_internal_margin(MARGIN_LEFT, get_icon_size().width);
+ }
}
CheckBox::~CheckBox() {
diff --git a/scene/gui/check_button.cpp b/scene/gui/check_button.cpp
index 790faeb4fd..e58f56a99b 100644
--- a/scene/gui/check_button.cpp
+++ b/scene/gui/check_button.cpp
@@ -61,19 +61,40 @@ Size2 CheckButton::get_minimum_size() const {
}
void CheckButton::_notification(int p_what) {
- if (p_what == NOTIFICATION_THEME_CHANGED) {
- _set_internal_margin(MARGIN_RIGHT, get_icon_size().width);
+ if ((p_what == NOTIFICATION_THEME_CHANGED) || (p_what == NOTIFICATION_LAYOUT_DIRECTION_CHANGED) || (p_what == NOTIFICATION_TRANSLATION_CHANGED)) {
+ if (is_layout_rtl()) {
+ _set_internal_margin(MARGIN_LEFT, get_icon_size().width);
+ _set_internal_margin(MARGIN_RIGHT, 0.f);
+ } else {
+ _set_internal_margin(MARGIN_LEFT, 0.f);
+ _set_internal_margin(MARGIN_RIGHT, get_icon_size().width);
+ }
} else if (p_what == NOTIFICATION_DRAW) {
RID ci = get_canvas_item();
+ bool rtl = is_layout_rtl();
- Ref<Texture2D> on = Control::get_theme_icon(is_disabled() ? "on_disabled" : "on");
- Ref<Texture2D> off = Control::get_theme_icon(is_disabled() ? "off_disabled" : "off");
+ Ref<Texture2D> on;
+ if (rtl) {
+ on = Control::get_theme_icon(is_disabled() ? "on_disabled_mirrored" : "on_mirrored");
+ } else {
+ on = Control::get_theme_icon(is_disabled() ? "on_disabled" : "on");
+ }
+ Ref<Texture2D> off;
+ if (rtl) {
+ off = Control::get_theme_icon(is_disabled() ? "off_disabled_mirrored" : "off_mirrored");
+ } else {
+ off = Control::get_theme_icon(is_disabled() ? "off_disabled" : "off");
+ }
Ref<StyleBox> sb = get_theme_stylebox("normal");
Vector2 ofs;
Size2 tex_size = get_icon_size();
- ofs.x = get_size().width - (tex_size.width + sb->get_margin(MARGIN_RIGHT));
+ if (rtl) {
+ ofs.x = sb->get_margin(MARGIN_LEFT);
+ } else {
+ ofs.x = get_size().width - (tex_size.width + sb->get_margin(MARGIN_RIGHT));
+ }
ofs.y = (get_size().height - tex_size.height) / 2 + get_theme_constant("check_vadjust");
if (is_pressed()) {
@@ -87,8 +108,11 @@ void CheckButton::_notification(int p_what) {
CheckButton::CheckButton() {
set_toggle_mode(true);
set_text_align(ALIGN_LEFT);
-
- _set_internal_margin(MARGIN_RIGHT, get_icon_size().width);
+ if (is_layout_rtl()) {
+ _set_internal_margin(MARGIN_LEFT, get_icon_size().width);
+ } else {
+ _set_internal_margin(MARGIN_RIGHT, get_icon_size().width);
+ }
}
CheckButton::~CheckButton() {
diff --git a/scene/gui/code_edit.cpp b/scene/gui/code_edit.cpp
index f6f52fbf55..d088a8cc3b 100644
--- a/scene/gui/code_edit.cpp
+++ b/scene/gui/code_edit.cpp
@@ -241,7 +241,7 @@ void CodeEdit::_line_number_draw_callback(int p_line, int p_gutter, const Rect2
if (number_color == Color(1, 1, 1)) {
number_color = line_number_color;
}
- cache.font->draw(get_canvas_item(), Point2(p_region.position.x, yofs + cache.font->get_ascent()), fc, number_color);
+ cache.font->draw_string(get_canvas_item(), Point2(p_region.position.x, yofs + cache.font->get_ascent()), fc, HALIGN_LEFT, -1, cache.font_size, number_color);
}
/* Fold Gutter */
diff --git a/scene/gui/container.cpp b/scene/gui/container.cpp
index 5643110b89..f01da703c1 100644
--- a/scene/gui/container.cpp
+++ b/scene/gui/container.cpp
@@ -121,12 +121,7 @@ void Container::fit_child_in_rect(Control *p_child, const Rect2 &p_rect) {
}
}
- for (int i = 0; i < 4; i++) {
- p_child->set_anchor(Margin(i), ANCHOR_BEGIN);
- }
-
- p_child->set_position(r.position);
- p_child->set_size(r.size);
+ p_child->set_rect(r);
p_child->set_rotation(0);
p_child->set_scale(Vector2(1, 1));
}
diff --git a/scene/gui/control.cpp b/scene/gui/control.cpp
index 0381f69bcb..f9b7d828f4 100644
--- a/scene/gui/control.cpp
+++ b/scene/gui/control.cpp
@@ -36,12 +36,15 @@
#include "core/os/keyboard.h"
#include "core/os/os.h"
#include "core/string/print_string.h"
+#include "core/string/translation.h"
+
#include "scene/gui/label.h"
#include "scene/gui/panel.h"
#include "scene/main/canvas_layer.h"
#include "scene/main/window.h"
#include "scene/scene_string_names.h"
#include "servers/rendering_server.h"
+#include "servers/text_server.h"
#ifdef TOOLS_ENABLED
#include "editor/editor_settings.h"
@@ -241,6 +244,10 @@ bool Control::_set(const StringName &p_name, const Variant &p_value) {
}
data.font_override.erase(dname);
notification(NOTIFICATION_THEME_CHANGED);
+ } else if (name.begins_with("custom_font_sizes/")) {
+ String dname = name.get_slicec('/', 1);
+ data.font_size_override.erase(dname);
+ notification(NOTIFICATION_THEME_CHANGED);
} else if (name.begins_with("custom_colors/")) {
String dname = name.get_slicec('/', 1);
data.color_override.erase(dname);
@@ -266,6 +273,9 @@ bool Control::_set(const StringName &p_name, const Variant &p_value) {
} else if (name.begins_with("custom_fonts/")) {
String dname = name.get_slicec('/', 1);
add_theme_font_override(dname, p_value);
+ } else if (name.begins_with("custom_font_sizes/")) {
+ String dname = name.get_slicec('/', 1);
+ add_theme_font_size_override(dname, p_value);
} else if (name.begins_with("custom_colors/")) {
String dname = name.get_slicec('/', 1);
add_theme_color_override(dname, p_value);
@@ -307,20 +317,19 @@ bool Control::_get(const StringName &p_name, Variant &r_ret) const {
if (sname.begins_with("custom_icons/")) {
String name = sname.get_slicec('/', 1);
-
r_ret = data.icon_override.has(name) ? Variant(data.icon_override[name]) : Variant();
} else if (sname.begins_with("custom_shaders/")) {
String name = sname.get_slicec('/', 1);
-
r_ret = data.shader_override.has(name) ? Variant(data.shader_override[name]) : Variant();
} else if (sname.begins_with("custom_styles/")) {
String name = sname.get_slicec('/', 1);
-
r_ret = data.style_override.has(name) ? Variant(data.style_override[name]) : Variant();
} else if (sname.begins_with("custom_fonts/")) {
String name = sname.get_slicec('/', 1);
-
r_ret = data.font_override.has(name) ? Variant(data.font_override[name]) : Variant();
+ } else if (sname.begins_with("custom_font_sizes/")) {
+ String name = sname.get_slicec('/', 1);
+ r_ret = data.font_size_override.has(name) ? Variant(data.font_size_override[name]) : Variant();
} else if (sname.begins_with("custom_colors/")) {
String name = sname.get_slicec('/', 1);
r_ret = data.color_override.has(name) ? Variant(data.color_override[name]) : Variant();
@@ -395,6 +404,18 @@ void Control::_get_property_list(List<PropertyInfo> *p_list) const {
}
{
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;
+ if (data.font_size_override.has(E->get())) {
+ hint |= PROPERTY_USAGE_STORAGE | PROPERTY_USAGE_CHECKED;
+ }
+
+ p_list->push_back(PropertyInfo(Variant::INT, "custom_font_sizes/" + E->get(), PROPERTY_HINT_NONE, "", hint));
+ }
+ }
+ {
+ 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;
@@ -423,6 +444,43 @@ Control *Control::get_parent_control() const {
return data.parent;
}
+void Control::set_layout_direction(Control::LayoutDirection p_direction) {
+ ERR_FAIL_INDEX((int)p_direction, 4);
+
+ data.layout_dir = p_direction;
+ propagate_notification(NOTIFICATION_LAYOUT_DIRECTION_CHANGED);
+}
+
+Control::LayoutDirection Control::get_layout_direction() const {
+ return data.layout_dir;
+}
+
+bool Control::is_layout_rtl() const {
+ if (data.layout_dir == LAYOUT_DIRECTION_INHERITED) {
+ Window *parent_window = Object::cast_to<Window>(get_parent());
+ Control *parent_control = get_parent_control();
+ if (parent_control) {
+ return parent_control->is_layout_rtl();
+ } else if (parent_window) {
+ return parent_window->is_layout_rtl();
+ } else {
+ if (GLOBAL_GET("display/window/force_right_to_left_layout_direction")) {
+ return true;
+ }
+ String locale = TranslationServer::get_singleton()->get_tool_locale();
+ return TS->is_locale_right_to_left(locale);
+ }
+ } else if (data.layout_dir == LAYOUT_DIRECTION_LOCALE) {
+ if (GLOBAL_GET("display/window/force_right_to_left_layout_direction")) {
+ return true;
+ }
+ String locale = TranslationServer::get_singleton()->get_tool_locale();
+ return TS->is_locale_right_to_left(locale);
+ } else {
+ return (data.layout_dir == LAYOUT_DIRECTION_RTL);
+ }
+}
+
void Control::_resize(const Size2 &p_size) {
_size_changed();
}
@@ -581,7 +639,6 @@ void Control::_notification(int p_notification) {
case NOTIFICATION_FOCUS_EXIT: {
emit_signal(SceneStringNames::get_singleton()->focus_exited);
update();
-
} break;
case NOTIFICATION_THEME_CHANGED: {
minimum_size_changed();
@@ -601,6 +658,10 @@ void Control::_notification(int p_notification) {
}
} break;
+ case NOTIFICATION_TRANSLATION_CHANGED:
+ case NOTIFICATION_LAYOUT_DIRECTION_CHANGED: {
+ _size_changed();
+ } break;
}
}
@@ -911,6 +972,19 @@ Ref<Font> Control::get_theme_font(const StringName &p_name, const StringName &p_
return get_fonts(data.theme_owner, data.theme_owner_window, p_name, type);
}
+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()) {
+ 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;
@@ -927,6 +1001,22 @@ Ref<Font> Control::get_fonts(Control *p_theme_owner, Window *p_theme_owner_windo
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);
+}
+
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()) {
const Color *color = data.color_override.getptr(p_name);
@@ -1003,6 +1093,11 @@ bool Control::has_theme_font_override(const StringName &p_name) const {
return font != nullptr;
}
+bool Control::has_theme_font_size_override(const StringName &p_name) const {
+ const int *font_size = data.font_size_override.getptr(p_name);
+ return font_size != nullptr;
+}
+
bool Control::has_theme_color_override(const StringName &p_name) const {
const Color *color = data.color_override.getptr(p_name);
return color != nullptr;
@@ -1113,6 +1208,31 @@ bool Control::has_fonts(Control *p_theme_owner, Window *p_theme_owner_window, co
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()) {
+ 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);
+}
+
+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()) {
if (has_theme_color_override(p_name)) {
@@ -1217,6 +1337,10 @@ void Control::_size_changed() {
new_size_cache.width = minimum_size.width;
}
+ if (is_layout_rtl()) {
+ new_pos_cache.x = parent_rect.size.x - new_pos_cache.x - new_size_cache.x;
+ }
+
if (minimum_size.height > new_size_cache.height) {
if (data.v_grow == GROW_DIRECTION_BEGIN) {
new_pos_cache.y += new_size_cache.height - minimum_size.height;
@@ -1426,6 +1550,10 @@ void Control::set_margins_preset(LayoutPreset p_preset, LayoutPresetMode p_resiz
Rect2 parent_rect = get_parent_anchorable_rect();
+ float x = parent_rect.size.x;
+ if (is_layout_rtl()) {
+ x = parent_rect.size.x - x - new_size.x;
+ }
//Left
switch (p_preset) {
case PRESET_TOP_LEFT:
@@ -1436,21 +1564,21 @@ void Control::set_margins_preset(LayoutPreset p_preset, LayoutPresetMode p_resiz
case PRESET_LEFT_WIDE:
case PRESET_HCENTER_WIDE:
case PRESET_WIDE:
- data.margin[0] = parent_rect.size.x * (0.0 - data.anchor[0]) + p_margin + parent_rect.position.x;
+ data.margin[0] = x * (0.0 - data.anchor[0]) + p_margin + parent_rect.position.x;
break;
case PRESET_CENTER_TOP:
case PRESET_CENTER_BOTTOM:
case PRESET_CENTER:
case PRESET_VCENTER_WIDE:
- data.margin[0] = parent_rect.size.x * (0.5 - data.anchor[0]) - new_size.x / 2 + parent_rect.position.x;
+ data.margin[0] = x * (0.5 - data.anchor[0]) - new_size.x / 2 + parent_rect.position.x;
break;
case PRESET_TOP_RIGHT:
case PRESET_BOTTOM_RIGHT:
case PRESET_CENTER_RIGHT:
case PRESET_RIGHT_WIDE:
- data.margin[0] = parent_rect.size.x * (1.0 - data.anchor[0]) - new_size.x - p_margin + parent_rect.position.x;
+ data.margin[0] = x * (1.0 - data.anchor[0]) - new_size.x - p_margin + parent_rect.position.x;
break;
}
@@ -1488,14 +1616,14 @@ void Control::set_margins_preset(LayoutPreset p_preset, LayoutPresetMode p_resiz
case PRESET_BOTTOM_LEFT:
case PRESET_CENTER_LEFT:
case PRESET_LEFT_WIDE:
- data.margin[2] = parent_rect.size.x * (0.0 - data.anchor[2]) + new_size.x + p_margin + parent_rect.position.x;
+ data.margin[2] = x * (0.0 - data.anchor[2]) + new_size.x + p_margin + parent_rect.position.x;
break;
case PRESET_CENTER_TOP:
case PRESET_CENTER_BOTTOM:
case PRESET_CENTER:
case PRESET_VCENTER_WIDE:
- data.margin[2] = parent_rect.size.x * (0.5 - data.anchor[2]) + new_size.x / 2 + parent_rect.position.x;
+ data.margin[2] = x * (0.5 - data.anchor[2]) + new_size.x / 2 + parent_rect.position.x;
break;
case PRESET_TOP_RIGHT:
@@ -1506,7 +1634,7 @@ void Control::set_margins_preset(LayoutPreset p_preset, LayoutPresetMode p_resiz
case PRESET_BOTTOM_WIDE:
case PRESET_HCENTER_WIDE:
case PRESET_WIDE:
- data.margin[2] = parent_rect.size.x * (1.0 - data.anchor[2]) - p_margin + parent_rect.position.x;
+ data.margin[2] = x * (1.0 - data.anchor[2]) - p_margin + parent_rect.position.x;
break;
}
@@ -1629,17 +1757,26 @@ void Control::_compute_anchors(Rect2 p_rect, const float p_margins[4], float (&r
ERR_FAIL_COND(parent_rect_size.x == 0.0);
ERR_FAIL_COND(parent_rect_size.y == 0.0);
- r_anchors[0] = (p_rect.position.x - p_margins[0]) / parent_rect_size.x;
+ float x = p_rect.position.x;
+ if (is_layout_rtl()) {
+ x = parent_rect_size.x - x - p_rect.size.x;
+ }
+ r_anchors[0] = (x - p_margins[0]) / parent_rect_size.x;
r_anchors[1] = (p_rect.position.y - p_margins[1]) / parent_rect_size.y;
- r_anchors[2] = (p_rect.position.x + p_rect.size.x - p_margins[2]) / parent_rect_size.x;
+ r_anchors[2] = (x + p_rect.size.x - p_margins[2]) / parent_rect_size.x;
r_anchors[3] = (p_rect.position.y + p_rect.size.y - p_margins[3]) / parent_rect_size.y;
}
void Control::_compute_margins(Rect2 p_rect, const float p_anchors[4], float (&r_margins)[4]) {
Size2 parent_rect_size = get_parent_anchorable_rect().size;
- r_margins[0] = p_rect.position.x - (p_anchors[0] * parent_rect_size.x);
+
+ float x = p_rect.position.x;
+ if (is_layout_rtl()) {
+ x = parent_rect_size.x - x - p_rect.size.x;
+ }
+ r_margins[0] = x - (p_anchors[0] * parent_rect_size.x);
r_margins[1] = p_rect.position.y - (p_anchors[1] * parent_rect_size.y);
- r_margins[2] = p_rect.position.x + p_rect.size.x - (p_anchors[2] * parent_rect_size.x);
+ r_margins[2] = x + p_rect.size.x - (p_anchors[2] * parent_rect_size.x);
r_margins[3] = p_rect.position.y + p_rect.size.y - (p_anchors[3] * parent_rect_size.y);
}
@@ -1660,6 +1797,17 @@ void Control::set_position(const Size2 &p_point, bool p_keep_margins) {
_size_changed();
}
+void Control::set_rect(const Rect2 &p_rect) {
+ for (int i = 0; i < 4; i++) {
+ data.anchor[i] = ANCHOR_BEGIN;
+ }
+
+ _compute_margins(p_rect, data.anchor, data.margin);
+ if (is_inside_tree()) {
+ _size_changed();
+ }
+}
+
void Control::_set_size(const Size2 &p_size) {
set_size(p_size);
}
@@ -1794,6 +1942,11 @@ void Control::add_theme_font_override(const StringName &p_name, const Ref<Font>
notification(NOTIFICATION_THEME_CHANGED);
}
+void Control::add_theme_font_size_override(const StringName &p_name, int p_font_size) {
+ data.font_size_override[p_name] = p_font_size;
+ notification(NOTIFICATION_THEME_CHANGED);
+}
+
void Control::add_theme_color_override(const StringName &p_name, const Color &p_color) {
data.color_override[p_name] = p_color;
notification(NOTIFICATION_THEME_CHANGED);
@@ -2436,6 +2589,95 @@ 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> ret;
+ switch (p_node_type) {
+ case STRUCTURED_TEXT_URI: {
+ int prev = 0;
+ for (int i = 0; i < p_text.length(); i++) {
+ if ((p_text[i] == '\\') || (p_text[i] == '/') || (p_text[i] == '.') || (p_text[i] == ':') || (p_text[i] == '&') || (p_text[i] == '=') || (p_text[i] == '@') || (p_text[i] == '?') || (p_text[i] == '#')) {
+ if (prev != i) {
+ ret.push_back(Vector2i(prev, i));
+ }
+ ret.push_back(Vector2i(i, i + 1));
+ prev = i + 1;
+ }
+ }
+ if (prev != p_text.length()) {
+ ret.push_back(Vector2i(prev, p_text.length()));
+ }
+ } break;
+ case STRUCTURED_TEXT_FILE: {
+ int prev = 0;
+ for (int i = 0; i < p_text.length(); i++) {
+ if ((p_text[i] == '\\') || (p_text[i] == '/') || (p_text[i] == ':')) {
+ if (prev != i) {
+ ret.push_back(Vector2i(prev, i));
+ }
+ ret.push_back(Vector2i(i, i + 1));
+ prev = i + 1;
+ }
+ }
+ if (prev != p_text.length()) {
+ ret.push_back(Vector2i(prev, p_text.length()));
+ }
+ } break;
+ case STRUCTURED_TEXT_EMAIL: {
+ bool local = true;
+ int prev = 0;
+ for (int i = 0; i < p_text.length(); i++) {
+ if ((p_text[i] == '@') && local) { // Add full "local" as single context.
+ local = false;
+ ret.push_back(Vector2i(prev, i));
+ ret.push_back(Vector2i(i, i + 1));
+ prev = i + 1;
+ } else if (!local & (p_text[i] == '.')) { // Add each dot separated "domain" part as context.
+ if (prev != i) {
+ ret.push_back(Vector2i(prev, i));
+ }
+ ret.push_back(Vector2i(i, i + 1));
+ prev = i + 1;
+ }
+ }
+ if (prev != p_text.length()) {
+ ret.push_back(Vector2i(prev, p_text.length()));
+ }
+ } break;
+ case STRUCTURED_TEXT_LIST: {
+ if (p_args.size() == 1 && p_args[0].get_type() == Variant::STRING) {
+ Vector<String> tags = p_text.split(String(p_args[0]));
+ int prev = 0;
+ for (int i = 0; i < tags.size(); i++) {
+ if (prev != i) {
+ ret.push_back(Vector2i(prev, prev + tags[i].length()));
+ }
+ ret.push_back(Vector2i(prev + tags[i].length(), prev + tags[i].length() + 1));
+ prev = prev + tags[i].length() + 1;
+ }
+ }
+ } break;
+ case STRUCTURED_TEXT_CUSTOM: {
+ if (get_script_instance()) {
+ Variant data = get_script_instance()->call(SceneStringNames::get_singleton()->_structured_text_parser, p_args, p_text);
+ if (data.get_type() == Variant::ARRAY) {
+ Array _data = data;
+ for (int i = 0; i < _data.size(); i++) {
+ if (_data[i].get_type() == Variant::VECTOR2I) {
+ ret.push_back(Vector2i(_data[i]));
+ }
+ }
+ }
+ }
+ } break;
+ case STRUCTURED_TEXT_NONE:
+ case STRUCTURED_TEXT_DEFAULT:
+ default: {
+ ret.push_back(Vector2i(0, p_text.length()));
+ }
+ }
+ return ret;
+}
+
void Control::set_rotation(float p_radians) {
data.rotation = p_radians;
update();
@@ -2544,6 +2786,8 @@ void Control::get_argument_options(const StringName &p_function, int p_idx, List
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") {
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") {
+ 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") {
Theme::get_default()->get_constant_list(get_class(), &sn);
}
@@ -2664,27 +2908,31 @@ void Control::_bind_methods() {
ClassDB::bind_method(D_METHOD("add_theme_shader_override", "name", "shader"), &Control::add_theme_shader_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);
+ ClassDB::bind_method(D_METHOD("add_theme_font_size_override", "name", "font_size"), &Control::add_theme_font_size_override);
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", "type"), &Control::get_theme_icon, DEFVAL(""));
- ClassDB::bind_method(D_METHOD("get_theme_stylebox", "name", "type"), &Control::get_theme_stylebox, DEFVAL(""));
- ClassDB::bind_method(D_METHOD("get_theme_font", "name", "type"), &Control::get_theme_font, DEFVAL(""));
- ClassDB::bind_method(D_METHOD("get_theme_color", "name", "type"), &Control::get_theme_color, DEFVAL(""));
- ClassDB::bind_method(D_METHOD("get_theme_constant", "name", "type"), &Control::get_theme_constant, DEFVAL(""));
+ 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("has_theme_icon_override", "name"), &Control::has_theme_icon_override);
ClassDB::bind_method(D_METHOD("has_theme_shader_override", "name"), &Control::has_theme_shader_override);
ClassDB::bind_method(D_METHOD("has_theme_stylebox_override", "name"), &Control::has_theme_stylebox_override);
ClassDB::bind_method(D_METHOD("has_theme_font_override", "name"), &Control::has_theme_font_override);
+ ClassDB::bind_method(D_METHOD("has_theme_font_size_override", "name"), &Control::has_theme_font_size_override);
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", "type"), &Control::has_theme_icon, DEFVAL(""));
- ClassDB::bind_method(D_METHOD("has_theme_stylebox", "name", "type"), &Control::has_theme_stylebox, DEFVAL(""));
- ClassDB::bind_method(D_METHOD("has_theme_font", "name", "type"), &Control::has_theme_font, DEFVAL(""));
- ClassDB::bind_method(D_METHOD("has_theme_color", "name", "type"), &Control::has_theme_color, DEFVAL(""));
- ClassDB::bind_method(D_METHOD("has_theme_constant", "name", "type"), &Control::has_theme_constant, DEFVAL(""));
+ 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("get_parent_control"), &Control::get_parent_control);
@@ -2728,6 +2976,12 @@ void Control::_bind_methods() {
ClassDB::bind_method(D_METHOD("minimum_size_changed"), &Control::minimum_size_changed);
+ ClassDB::bind_method(D_METHOD("set_layout_direction", "direction"), &Control::set_layout_direction);
+ ClassDB::bind_method(D_METHOD("get_layout_direction"), &Control::get_layout_direction);
+ ClassDB::bind_method(D_METHOD("is_layout_rtl"), &Control::is_layout_rtl);
+
+ BIND_VMETHOD(MethodInfo("_structured_text_parser", PropertyInfo(Variant::ARRAY, "args"), PropertyInfo(Variant::STRING, "text")));
+
BIND_VMETHOD(MethodInfo("_gui_input", PropertyInfo(Variant::OBJECT, "event", PROPERTY_HINT_RESOURCE_TYPE, "InputEvent")));
BIND_VMETHOD(MethodInfo(Variant::VECTOR2, "_get_minimum_size"));
@@ -2758,6 +3012,9 @@ void Control::_bind_methods() {
ADD_PROPERTY(PropertyInfo(Variant::INT, "grow_horizontal", PROPERTY_HINT_ENUM, "Begin,End,Both"), "set_h_grow_direction", "get_h_grow_direction");
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_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");
@@ -2804,6 +3061,7 @@ void Control::_bind_methods() {
BIND_CONSTANT(NOTIFICATION_THEME_CHANGED);
BIND_CONSTANT(NOTIFICATION_SCROLL_BEGIN);
BIND_CONSTANT(NOTIFICATION_SCROLL_END);
+ BIND_CONSTANT(NOTIFICATION_LAYOUT_DIRECTION_CHANGED);
BIND_ENUM_CONSTANT(CURSOR_ARROW);
BIND_ENUM_CONSTANT(CURSOR_IBEAM);
@@ -2862,6 +3120,24 @@ void Control::_bind_methods() {
BIND_ENUM_CONSTANT(ANCHOR_BEGIN);
BIND_ENUM_CONSTANT(ANCHOR_END);
+ BIND_ENUM_CONSTANT(LAYOUT_DIRECTION_INHERITED);
+ BIND_ENUM_CONSTANT(LAYOUT_DIRECTION_LOCALE);
+ BIND_ENUM_CONSTANT(LAYOUT_DIRECTION_LTR);
+ BIND_ENUM_CONSTANT(LAYOUT_DIRECTION_RTL);
+
+ BIND_ENUM_CONSTANT(TEXT_DIRECTION_INHERITED);
+ BIND_ENUM_CONSTANT(TEXT_DIRECTION_AUTO);
+ BIND_ENUM_CONSTANT(TEXT_DIRECTION_LTR);
+ BIND_ENUM_CONSTANT(TEXT_DIRECTION_RTL);
+
+ BIND_ENUM_CONSTANT(STRUCTURED_TEXT_DEFAULT);
+ BIND_ENUM_CONSTANT(STRUCTURED_TEXT_URI);
+ BIND_ENUM_CONSTANT(STRUCTURED_TEXT_FILE);
+ BIND_ENUM_CONSTANT(STRUCTURED_TEXT_EMAIL);
+ BIND_ENUM_CONSTANT(STRUCTURED_TEXT_LIST);
+ BIND_ENUM_CONSTANT(STRUCTURED_TEXT_NONE);
+ BIND_ENUM_CONSTANT(STRUCTURED_TEXT_CUSTOM);
+
ADD_SIGNAL(MethodInfo("resized"));
ADD_SIGNAL(MethodInfo("gui_input", PropertyInfo(Variant::OBJECT, "event", PROPERTY_HINT_RESOURCE_TYPE, "InputEvent")));
ADD_SIGNAL(MethodInfo("mouse_entered"));
@@ -2884,6 +3160,7 @@ Control::Control() {
data.theme_owner = nullptr;
data.theme_owner_window = nullptr;
data.default_cursor = CURSOR_ARROW;
+ data.layout_dir = LAYOUT_DIRECTION_INHERITED;
data.h_size_flags = SIZE_FILL;
data.v_size_flags = SIZE_FILL;
data.expand = 1;
diff --git a/scene/gui/control.h b/scene/gui/control.h
index e4fe0bb25d..e1f05dfe64 100644
--- a/scene/gui/control.h
+++ b/scene/gui/control.h
@@ -127,6 +127,30 @@ public:
PRESET_MODE_KEEP_SIZE
};
+ enum LayoutDirection {
+ LAYOUT_DIRECTION_INHERITED,
+ LAYOUT_DIRECTION_LOCALE,
+ LAYOUT_DIRECTION_LTR,
+ LAYOUT_DIRECTION_RTL
+ };
+
+ enum TextDirection {
+ TEXT_DIRECTION_AUTO = TextServer::DIRECTION_AUTO,
+ TEXT_DIRECTION_LTR = TextServer::DIRECTION_LTR,
+ TEXT_DIRECTION_RTL = TextServer::DIRECTION_RTL,
+ TEXT_DIRECTION_INHERITED,
+ };
+
+ enum StructuredTextParser {
+ STRUCTURED_TEXT_DEFAULT,
+ STRUCTURED_TEXT_URI,
+ STRUCTURED_TEXT_FILE,
+ STRUCTURED_TEXT_EMAIL,
+ STRUCTURED_TEXT_LIST,
+ STRUCTURED_TEXT_NONE,
+ STRUCTURED_TEXT_CUSTOM
+ };
+
private:
struct CComparator {
bool operator()(const Control *p_a, const Control *p_b) const {
@@ -153,6 +177,8 @@ private:
GrowDirection h_grow;
GrowDirection v_grow;
+ LayoutDirection layout_dir;
+
float rotation;
Vector2 scale;
Vector2 pivot_offset;
@@ -189,6 +215,7 @@ private:
HashMap<StringName, Ref<Shader>> shader_override;
HashMap<StringName, Ref<StyleBox>> style_override;
HashMap<StringName, Ref<Font>> font_override;
+ HashMap<StringName, int> font_size_override;
HashMap<StringName, Color> color_override;
HashMap<StringName, int> constant_override;
@@ -240,6 +267,7 @@ private:
static Ref<Shader> get_shaders(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());
@@ -247,6 +275,7 @@ private:
static bool has_shaders(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());
@@ -256,6 +285,8 @@ protected:
//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;
+
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;
@@ -278,6 +309,7 @@ public:
NOTIFICATION_THEME_CHANGED = 45,
NOTIFICATION_SCROLL_BEGIN = 47,
NOTIFICATION_SCROLL_END = 48,
+ NOTIFICATION_LAYOUT_DIRECTION_CHANGED = 49,
};
@@ -325,6 +357,10 @@ public:
Control *get_parent_control() const;
+ void set_layout_direction(LayoutDirection p_direction);
+ LayoutDirection get_layout_direction() const;
+ virtual bool is_layout_rtl() const;
+
/* POSITIONING */
void set_anchors_preset(LayoutPreset p_preset, bool p_keep_margins = true);
@@ -360,6 +396,8 @@ public:
Rect2 get_window_rect() const; ///< use with care, as it blocks waiting for the visual server
Rect2 get_anchorable_rect() const override;
+ void set_rect(const Rect2 &p_rect); // Reset anchors to begin and set rect, for faster container children sorting.
+
void set_rotation(float p_radians);
void set_rotation_degrees(float p_degrees);
float get_rotation() const;
@@ -421,6 +459,7 @@ public:
void add_theme_shader_override(const StringName &p_name, const Ref<Shader> &p_shader);
void add_theme_style_override(const StringName &p_name, const Ref<StyleBox> &p_style);
void add_theme_font_override(const StringName &p_name, const Ref<Font> &p_font);
+ void add_theme_font_size_override(const StringName &p_name, int p_font_size);
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);
@@ -428,6 +467,7 @@ public:
Ref<Shader> get_theme_shader(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;
@@ -435,6 +475,7 @@ public:
bool has_theme_shader_override(const StringName &p_name) const;
bool has_theme_stylebox_override(const StringName &p_name) const;
bool has_theme_font_override(const StringName &p_name) const;
+ bool has_theme_font_size_override(const StringName &p_name) const;
bool has_theme_color_override(const StringName &p_name) const;
bool has_theme_constant_override(const StringName &p_name) const;
@@ -442,6 +483,7 @@ public:
bool has_theme_shader(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;
@@ -496,5 +538,8 @@ VARIANT_ENUM_CAST(Control::LayoutPresetMode);
VARIANT_ENUM_CAST(Control::MouseFilter);
VARIANT_ENUM_CAST(Control::GrowDirection);
VARIANT_ENUM_CAST(Control::Anchor);
+VARIANT_ENUM_CAST(Control::LayoutDirection);
+VARIANT_ENUM_CAST(Control::TextDirection);
+VARIANT_ENUM_CAST(Control::StructuredTextParser);
#endif
diff --git a/scene/gui/file_dialog.cpp b/scene/gui/file_dialog.cpp
index 7ce4e90f28..eb3d5d5c6d 100644
--- a/scene/gui/file_dialog.cpp
+++ b/scene/gui/file_dialog.cpp
@@ -879,6 +879,7 @@ FileDialog::FileDialog() {
hbc->add_child(drives);
dir = memnew(LineEdit);
+ dir->set_structured_text_bidi_override(Control::STRUCTURED_TEXT_FILE);
hbc->add_child(dir);
dir->set_h_size_flags(Control::SIZE_EXPAND_FILL);
@@ -912,6 +913,7 @@ FileDialog::FileDialog() {
file_box = memnew(HBoxContainer);
file_box->add_child(memnew(Label(RTR("File:"))));
file = memnew(LineEdit);
+ file->set_structured_text_bidi_override(Control::STRUCTURED_TEXT_FILE);
file->set_stretch_ratio(4);
file->set_h_size_flags(Control::SIZE_EXPAND_FILL);
file_box->add_child(file);
@@ -947,6 +949,7 @@ FileDialog::FileDialog() {
makedialog->add_child(makevb);
makedirname = memnew(LineEdit);
+ makedirname->set_structured_text_bidi_override(Control::STRUCTURED_TEXT_FILE);
makevb->add_margin_child(RTR("Name:"), makedirname);
add_child(makedialog);
makedialog->register_text_enter(makedirname);
diff --git a/scene/gui/graph_node.cpp b/scene/gui/graph_node.cpp
index 4454e87017..4ce33ec8f2 100644
--- a/scene/gui/graph_node.cpp
+++ b/scene/gui/graph_node.cpp
@@ -30,13 +30,37 @@
#include "graph_node.h"
+#include "core/string/translation.h"
+
bool GraphNode::_set(const StringName &p_name, const Variant &p_value) {
- if (!p_name.operator String().begins_with("slot/")) {
+ String str = p_name;
+ if (str.begins_with("opentype_features/")) {
+ String name = str.get_slicec('/', 1);
+ int32_t tag = TS->name_to_tag(name);
+ double value = p_value;
+ if (value == -1) {
+ if (opentype_features.has(tag)) {
+ opentype_features.erase(tag);
+ _shape();
+ update();
+ }
+ } else {
+ if ((double)opentype_features[tag] != value) {
+ opentype_features[tag] = value;
+ _shape();
+ update();
+ }
+ }
+ _change_notify();
+ return true;
+ }
+
+ if (!str.begins_with("slot/")) {
return false;
}
- int idx = p_name.operator String().get_slice("/", 1).to_int();
- String what = p_name.operator String().get_slice("/", 2);
+ int idx = str.get_slice("/", 1).to_int();
+ String what = str.get_slice("/", 2);
Slot si;
if (slot_info.has(idx)) {
@@ -65,12 +89,25 @@ bool GraphNode::_set(const StringName &p_name, const Variant &p_value) {
}
bool GraphNode::_get(const StringName &p_name, Variant &r_ret) const {
- if (!p_name.operator String().begins_with("slot/")) {
+ String str = p_name;
+ if (str.begins_with("opentype_features/")) {
+ String name = str.get_slicec('/', 1);
+ int32_t tag = TS->name_to_tag(name);
+ if (opentype_features.has(tag)) {
+ r_ret = opentype_features[tag];
+ return true;
+ } else {
+ r_ret = -1;
+ return true;
+ }
+ }
+
+ if (!str.begins_with("slot/")) {
return false;
}
- int idx = p_name.operator String().get_slice("/", 1).to_int();
- String what = p_name.operator String().get_slice("/", 2);
+ int idx = str.get_slice("/", 1).to_int();
+ String what = str.get_slice("/", 2);
Slot si;
if (slot_info.has(idx)) {
@@ -97,6 +134,12 @@ bool GraphNode::_get(const StringName &p_name, Variant &r_ret) const {
}
void GraphNode::_get_property_list(List<PropertyInfo> *p_list) const {
+ for (const Variant *ftr = opentype_features.next(nullptr); ftr != nullptr; ftr = opentype_features.next(ftr)) {
+ String name = TS->tag_to_name(*ftr);
+ p_list->push_back(PropertyInfo(Variant::FLOAT, "opentype_features/" + name));
+ }
+ p_list->push_back(PropertyInfo(Variant::NIL, "opentype_features/_new", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_EDITOR));
+
int idx = 0;
for (int i = 0; i < get_child_count(); i++) {
Control *c = Object::cast_to<Control>(get_child(i));
@@ -213,7 +256,6 @@ void GraphNode::_notification(int p_what) {
int close_h_offset = get_theme_constant("close_h_offset");
Color close_color = get_theme_color("close_color");
Color resizer_color = get_theme_color("resizer_color");
- Ref<Font> title_font = get_theme_font("title_font");
int title_offset = get_theme_constant("title_offset");
int title_h_offset = get_theme_constant("title_h_offset");
Color title_color = get_theme_color("title_color");
@@ -241,7 +283,8 @@ void GraphNode::_notification(int p_what) {
w -= close->get_width();
}
- draw_string(title_font, Point2(sb->get_margin(MARGIN_LEFT) + title_h_offset, -title_font->get_height() + title_font->get_ascent() + title_offset), title, title_color, w);
+ title_buf->set_width(w);
+ title_buf->draw(get_canvas_item(), Point2(sb->get_margin(MARGIN_LEFT) + title_h_offset, -title_buf->get_size().y + title_offset), title_color);
if (show_close) {
Vector2 cpos = Point2(w + sb->get_margin(MARGIN_LEFT) + close_h_offset, -close->get_height() + close_offset);
draw_texture(close, cpos, close_color);
@@ -285,12 +328,30 @@ void GraphNode::_notification(int p_what) {
_resort();
} break;
+ case NOTIFICATION_LAYOUT_DIRECTION_CHANGED:
+ case NOTIFICATION_TRANSLATION_CHANGED:
case NOTIFICATION_THEME_CHANGED: {
+ _shape();
+
minimum_size_changed();
+ update();
} break;
}
}
+void GraphNode::_shape() {
+ Ref<Font> font = get_theme_font("title_font");
+ int font_size = get_theme_font_size("title_font_size");
+
+ title_buf->clear();
+ if (text_direction == Control::TEXT_DIRECTION_INHERITED) {
+ title_buf->set_direction(is_layout_rtl() ? TextServer::DIRECTION_RTL : TextServer::DIRECTION_LTR);
+ } else {
+ title_buf->set_direction((TextServer::Direction)text_direction);
+ }
+ title_buf->add_string(title, font, font_size, opentype_features, (language != "") ? language : TranslationServer::get_singleton()->get_tool_locale());
+}
+
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);
@@ -368,14 +429,12 @@ Color GraphNode::get_slot_color_right(int p_idx) const {
}
Size2 GraphNode::get_minimum_size() const {
- Ref<Font> title_font = get_theme_font("title_font");
-
int sep = get_theme_constant("separation");
Ref<StyleBox> sb = get_theme_stylebox("frame");
bool first = true;
Size2 minsize;
- minsize.x = title_font->get_string_size(title).x;
+ minsize.x = title_buf->get_size().x;
if (show_close) {
Ref<Texture2D> close = get_theme_icon("close");
minsize.x += sep + close->get_width();
@@ -410,6 +469,8 @@ void GraphNode::set_title(const String &p_title) {
return;
}
title = p_title;
+ _shape();
+
update();
_change_notify("title");
minimum_size_changed();
@@ -419,6 +480,54 @@ String GraphNode::get_title() const {
return title;
}
+void GraphNode::set_text_direction(Control::TextDirection p_text_direction) {
+ ERR_FAIL_COND((int)p_text_direction < -1 || (int)p_text_direction > 3);
+ if (text_direction != p_text_direction) {
+ text_direction = p_text_direction;
+ _shape();
+ update();
+ }
+}
+
+Control::TextDirection GraphNode::get_text_direction() const {
+ return text_direction;
+}
+
+void GraphNode::clear_opentype_features() {
+ opentype_features.clear();
+ _shape();
+ update();
+}
+
+void GraphNode::set_opentype_feature(const String &p_name, int p_value) {
+ int32_t tag = TS->name_to_tag(p_name);
+ if (!opentype_features.has(tag) || (int)opentype_features[tag] != p_value) {
+ opentype_features[tag] = p_value;
+ _shape();
+ update();
+ }
+}
+
+int GraphNode::get_opentype_feature(const String &p_name) const {
+ int32_t tag = TS->name_to_tag(p_name);
+ if (!opentype_features.has(tag)) {
+ return -1;
+ }
+ return opentype_features[tag];
+}
+
+void GraphNode::set_language(const String &p_language) {
+ if (language != p_language) {
+ language = p_language;
+ _shape();
+ update();
+ }
+}
+
+String GraphNode::get_language() const {
+ return language;
+}
+
void GraphNode::set_offset(const Vector2 &p_offset) {
offset = p_offset;
emit_signal("offset_changed");
@@ -658,6 +767,14 @@ bool GraphNode::is_resizable() const {
void GraphNode::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_title", "title"), &GraphNode::set_title);
ClassDB::bind_method(D_METHOD("get_title"), &GraphNode::get_title);
+ ClassDB::bind_method(D_METHOD("set_text_direction", "direction"), &GraphNode::set_text_direction);
+ ClassDB::bind_method(D_METHOD("get_text_direction"), &GraphNode::get_text_direction);
+ ClassDB::bind_method(D_METHOD("set_opentype_feature", "tag", "value"), &GraphNode::set_opentype_feature);
+ ClassDB::bind_method(D_METHOD("get_opentype_feature", "tag"), &GraphNode::get_opentype_feature);
+ ClassDB::bind_method(D_METHOD("clear_opentype_features"), &GraphNode::clear_opentype_features);
+ ClassDB::bind_method(D_METHOD("set_language", "language"), &GraphNode::set_language);
+ ClassDB::bind_method(D_METHOD("get_language"), &GraphNode::get_language);
+
ClassDB::bind_method(D_METHOD("_gui_input"), &GraphNode::_gui_input);
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>()));
@@ -699,6 +816,8 @@ 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::STRING, "language"), "set_language", "get_language");
ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "offset"), "set_offset", "get_offset");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "show_close"), "set_show_close_button", "is_close_button_visible");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "resizable"), "set_resizable", "is_resizable");
@@ -718,6 +837,7 @@ void GraphNode::_bind_methods() {
}
GraphNode::GraphNode() {
+ title_buf.instance();
overlay = OVERLAY_DISABLED;
show_close = false;
connpos_dirty = true;
diff --git a/scene/gui/graph_node.h b/scene/gui/graph_node.h
index 0cf6d9b09a..3cd7ae6e24 100644
--- a/scene/gui/graph_node.h
+++ b/scene/gui/graph_node.h
@@ -32,6 +32,7 @@
#define GRAPH_NODE_H
#include "scene/gui/container.h"
+#include "scene/resources/text_line.h"
class GraphNode : public Container {
GDCLASS(GraphNode, Container);
@@ -65,6 +66,12 @@ private:
};
String title;
+ Ref<TextLine> title_buf;
+
+ Dictionary opentype_features;
+ String language;
+ TextDirection text_direction = TEXT_DIRECTION_AUTO;
+
bool show_close;
Vector2 offset;
bool comment;
@@ -93,6 +100,7 @@ private:
void _connpos_update();
void _resort();
+ void _shape();
Vector2 drag_from;
bool selected;
@@ -124,6 +132,16 @@ public:
void set_title(const String &p_title);
String get_title() const;
+ void set_text_direction(TextDirection p_text_direction);
+ TextDirection get_text_direction() const;
+
+ void set_opentype_feature(const String &p_name, int p_value);
+ int get_opentype_feature(const String &p_name) const;
+ void clear_opentype_features();
+
+ void set_language(const String &p_language);
+ String get_language() const;
+
void set_offset(const Vector2 &p_offset);
Vector2 get_offset() const;
diff --git a/scene/gui/grid_container.cpp b/scene/gui/grid_container.cpp
index 2f37461c4d..a08a348a18 100644
--- a/scene/gui/grid_container.cpp
+++ b/scene/gui/grid_container.cpp
@@ -141,6 +141,7 @@ void GridContainer::_notification(int p_what) {
// Finally, fit the nodes.
int col_expand = col_expanded.size() > 0 ? remaining_space.width / col_expanded.size() : 0;
int row_expand = row_expanded.size() > 0 ? remaining_space.height / row_expanded.size() : 0;
+ bool rtl = is_layout_rtl();
int col_ofs = 0;
int row_ofs = 0;
@@ -156,24 +157,37 @@ void GridContainer::_notification(int p_what) {
valid_controls_index++;
if (col == 0) {
- col_ofs = 0;
+ if (rtl) {
+ col_ofs = get_size().width;
+ } else {
+ col_ofs = 0;
+ }
if (row > 0) {
row_ofs += (row_expanded.has(row - 1) ? row_expand : row_minh[row - 1]) + vsep;
}
}
- Point2 p(col_ofs, row_ofs);
- Size2 s(col_expanded.has(col) ? col_expand : col_minw[col], row_expanded.has(row) ? row_expand : row_minh[row]);
-
- fit_child_in_rect(c, Rect2(p, s));
-
- col_ofs += s.width + hsep;
+ if (rtl) {
+ Size2 s(col_expanded.has(col) ? col_expand : col_minw[col], row_expanded.has(row) ? row_expand : row_minh[row]);
+ Point2 p(col_ofs - s.width, row_ofs);
+ fit_child_in_rect(c, Rect2(p, s));
+ col_ofs -= s.width + hsep;
+ } else {
+ Point2 p(col_ofs, row_ofs);
+ Size2 s(col_expanded.has(col) ? col_expand : col_minw[col], row_expanded.has(row) ? row_expand : row_minh[row]);
+ fit_child_in_rect(c, Rect2(p, s));
+ col_ofs += s.width + hsep;
+ }
}
} break;
case NOTIFICATION_THEME_CHANGED: {
minimum_size_changed();
} break;
+ case NOTIFICATION_TRANSLATION_CHANGED:
+ case NOTIFICATION_LAYOUT_DIRECTION_CHANGED: {
+ queue_sort();
+ } break;
}
}
diff --git a/scene/gui/item_list.cpp b/scene/gui/item_list.cpp
index 6708b18e0a..53fe5712c7 100644
--- a/scene/gui/item_list.cpp
+++ b/scene/gui/item_list.cpp
@@ -31,6 +31,24 @@
#include "item_list.h"
#include "core/config/project_settings.h"
#include "core/os/os.h"
+#include "core/string/translation.h"
+
+void ItemList::_shape(int p_idx) {
+ Item &item = items.write[p_idx];
+
+ item.text_buf->clear();
+ if (item.text_direction == Control::TEXT_DIRECTION_INHERITED) {
+ item.text_buf->set_direction(is_layout_rtl() ? TextServer::DIRECTION_RTL : TextServer::DIRECTION_LTR);
+ } else {
+ item.text_buf->set_direction((TextServer::Direction)item.text_direction);
+ }
+ item.text_buf->add_string(item.text, get_theme_font("font"), get_theme_font_size("font_size"), item.opentype_features, (item.language != "") ? item.language : TranslationServer::get_singleton()->get_tool_locale());
+ if (icon_mode == ICON_MODE_TOP && max_text_lines > 0) {
+ item.text_buf->set_flags(TextServer::BREAK_MANDATORY | TextServer::BREAK_WORD_BOUND | TextServer::BREAK_GRAPHEME_BOUND);
+ } else {
+ item.text_buf->set_flags(TextServer::BREAK_NONE);
+ }
+}
void ItemList::add_item(const String &p_item, const Ref<Texture2D> &p_texture, bool p_selectable) {
Item item;
@@ -39,6 +57,7 @@ void ItemList::add_item(const String &p_item, const Ref<Texture2D> &p_texture, b
item.icon_region = Rect2i();
item.icon_modulate = Color(1, 1, 1, 1);
item.text = p_item;
+ item.text_buf.instance();
item.selectable = p_selectable;
item.selected = false;
item.disabled = false;
@@ -46,6 +65,8 @@ void ItemList::add_item(const String &p_item, const Ref<Texture2D> &p_texture, b
item.custom_bg = Color(0, 0, 0, 0);
items.push_back(item);
+ _shape(items.size() - 1);
+
update();
shape_changed = true;
}
@@ -72,6 +93,7 @@ void ItemList::set_item_text(int p_idx, const String &p_text) {
ERR_FAIL_INDEX(p_idx, items.size());
items.write[p_idx].text = p_text;
+ _shape(p_idx);
update();
shape_changed = true;
}
@@ -81,6 +103,61 @@ String ItemList::get_item_text(int p_idx) const {
return items[p_idx].text;
}
+void ItemList::set_item_text_direction(int p_idx, Control::TextDirection p_text_direction) {
+ ERR_FAIL_INDEX(p_idx, items.size());
+ ERR_FAIL_COND((int)p_text_direction < -1 || (int)p_text_direction > 3);
+ if (items[p_idx].text_direction != p_text_direction) {
+ items.write[p_idx].text_direction = p_text_direction;
+ _shape(p_idx);
+ update();
+ }
+}
+
+Control::TextDirection ItemList::get_item_text_direction(int p_idx) const {
+ ERR_FAIL_INDEX_V(p_idx, items.size(), TEXT_DIRECTION_INHERITED);
+ return items[p_idx].text_direction;
+}
+
+void ItemList::clear_item_opentype_features(int p_idx) {
+ ERR_FAIL_INDEX(p_idx, items.size());
+ items.write[p_idx].opentype_features.clear();
+ _shape(p_idx);
+ update();
+}
+
+void ItemList::set_item_opentype_feature(int p_idx, const String &p_name, int p_value) {
+ ERR_FAIL_INDEX(p_idx, items.size());
+ int32_t tag = TS->name_to_tag(p_name);
+ if (!items[p_idx].opentype_features.has(tag) || (int)items[p_idx].opentype_features[tag] != p_value) {
+ items.write[p_idx].opentype_features[tag] = p_value;
+ _shape(p_idx);
+ update();
+ }
+}
+
+int ItemList::get_item_opentype_feature(int p_idx, const String &p_name) const {
+ ERR_FAIL_INDEX_V(p_idx, items.size(), -1);
+ int32_t tag = TS->name_to_tag(p_name);
+ if (!items[p_idx].opentype_features.has(tag)) {
+ return -1;
+ }
+ return items[p_idx].opentype_features[tag];
+}
+
+void ItemList::set_item_language(int p_idx, const String &p_language) {
+ ERR_FAIL_INDEX(p_idx, items.size());
+ if (items[p_idx].language != p_language) {
+ items.write[p_idx].language = p_language;
+ _shape(p_idx);
+ update();
+ }
+}
+
+String ItemList::get_item_language(int p_idx) const {
+ ERR_FAIL_INDEX_V(p_idx, items.size(), "");
+ return items[p_idx].language;
+}
+
void ItemList::set_item_tooltip_enabled(int p_idx, const bool p_enabled) {
ERR_FAIL_INDEX(p_idx, items.size());
items.write[p_idx].tooltip_enabled = p_enabled;
@@ -361,9 +438,18 @@ bool ItemList::is_same_column_width() const {
void ItemList::set_max_text_lines(int p_lines) {
ERR_FAIL_COND(p_lines < 1);
- max_text_lines = p_lines;
- update();
- shape_changed = true;
+ if (max_text_lines != p_lines) {
+ max_text_lines = p_lines;
+ for (int i = 0; i < items.size(); i++) {
+ if (icon_mode == ICON_MODE_TOP && max_text_lines > 0) {
+ items.write[i].text_buf->set_flags(TextServer::BREAK_MANDATORY | TextServer::BREAK_WORD_BOUND | TextServer::BREAK_GRAPHEME_BOUND);
+ } else {
+ items.write[i].text_buf->set_flags(TextServer::BREAK_NONE);
+ }
+ }
+ shape_changed = true;
+ update();
+ }
}
int ItemList::get_max_text_lines() const {
@@ -392,9 +478,18 @@ ItemList::SelectMode ItemList::get_select_mode() const {
void ItemList::set_icon_mode(IconMode p_mode) {
ERR_FAIL_INDEX((int)p_mode, 2);
- icon_mode = p_mode;
- update();
- shape_changed = true;
+ if (icon_mode != p_mode) {
+ icon_mode = p_mode;
+ for (int i = 0; i < items.size(); i++) {
+ if (icon_mode == ICON_MODE_TOP && max_text_lines > 0) {
+ items.write[i].text_buf->set_flags(TextServer::BREAK_MANDATORY | TextServer::BREAK_WORD_BOUND | TextServer::BREAK_GRAPHEME_BOUND);
+ } else {
+ items.write[i].text_buf->set_flags(TextServer::BREAK_NONE);
+ }
+ }
+ shape_changed = true;
+ update();
+ }
}
ItemList::IconMode ItemList::get_icon_mode() const {
@@ -455,6 +550,10 @@ void ItemList::_gui_input(const Ref<InputEvent> &p_event) {
pos -= bg->get_offset();
pos.y += scroll_bar->get_value();
+ if (is_layout_rtl()) {
+ pos.x = get_size().width - pos.x;
+ }
+
int closest = -1;
for (int i = 0; i < items.size(); i++) {
@@ -749,6 +848,14 @@ void ItemList::_notification(int p_what) {
update();
}
+ if ((p_what == NOTIFICATION_LAYOUT_DIRECTION_CHANGED) || (p_what == NOTIFICATION_TRANSLATION_CHANGED) || (p_what == NOTIFICATION_THEME_CHANGED)) {
+ for (int i = 0; i < items.size(); i++) {
+ _shape(i);
+ }
+ shape_changed = true;
+ update();
+ }
+
if (p_what == NOTIFICATION_DRAW) {
Ref<StyleBox> bg = get_theme_stylebox("bg");
@@ -774,19 +881,11 @@ void ItemList::_notification(int p_what) {
Ref<StyleBox> sbsel = has_focus() ? get_theme_stylebox("selected_focus") : get_theme_stylebox("selected");
Ref<StyleBox> cursor = has_focus() ? get_theme_stylebox("cursor") : get_theme_stylebox("cursor_unfocused");
+ bool rtl = is_layout_rtl();
- Ref<Font> font = get_theme_font("font");
Color guide_color = get_theme_color("guide_color");
Color font_color = get_theme_color("font_color");
Color font_color_selected = get_theme_color("font_color_selected");
- int font_height = font->get_height();
- Vector<int> line_size_cache;
- Vector<int> line_limit_cache;
-
- if (max_text_lines) {
- line_size_cache.resize(max_text_lines);
- line_limit_cache.resize(max_text_lines);
- }
if (has_focus()) {
RenderingServer::get_singleton()->canvas_item_add_clip_ignore(get_canvas_item(), true);
@@ -817,13 +916,13 @@ void ItemList::_notification(int p_what) {
}
if (items[i].text != "") {
- Size2 s = font->get_string_size(items[i].text);
+ Size2 s = items[i].text_buf->get_size();
//s.width=MIN(s.width,fixed_column_width);
if (icon_mode == ICON_MODE_TOP) {
minsize.x = MAX(minsize.x, s.width);
if (max_text_lines > 0) {
- minsize.y += (font_height + line_separation) * max_text_lines;
+ minsize.y += s.height + line_separation * max_text_lines;
} else {
minsize.y += s.height;
}
@@ -986,6 +1085,10 @@ void ItemList::_notification(int p_what) {
r.position.x -= hseparation / 2;
r.size.x += hseparation;
+ if (rtl) {
+ r.position.x = size.width - r.position.x - r.size.x;
+ }
+
draw_style_box(sbsel, r);
}
if (items[i].custom_bg.a > 0.001) {
@@ -998,6 +1101,10 @@ void ItemList::_notification(int p_what) {
r.position.x -= hseparation / 2;
r.size.x += hseparation;
+ if (rtl) {
+ r.position.x = size.width - r.position.x - r.size.x;
+ }
+
draw_rect(r, items[i].custom_bg);
}
@@ -1049,17 +1156,25 @@ void ItemList::_notification(int p_what) {
}
Rect2 region = (items[i].icon_region.size.x == 0 || items[i].icon_region.size.y == 0) ? Rect2(Vector2(), items[i].icon->get_size()) : Rect2(items[i].icon_region);
+
+ if (rtl) {
+ draw_rect.position.x = size.width - draw_rect.position.x - draw_rect.size.x;
+ }
draw_texture_rect_region(items[i].icon, draw_rect, region, modulate, items[i].icon_transposed);
}
if (items[i].tag_icon.is_valid()) {
- draw_texture(items[i].tag_icon, items[i].rect_cache.position + base_ofs);
+ Point2 draw_pos = items[i].rect_cache.position;
+ if (rtl) {
+ draw_pos.x = size.width - draw_pos.x - items[i].tag_icon->get_width();
+ }
+ draw_texture(items[i].tag_icon, draw_pos + base_ofs);
}
if (items[i].text != "") {
int max_len = -1;
- Vector2 size2 = font->get_string_size(items[i].text);
+ Vector2 size2 = items[i].text_buf->get_size();
if (fixed_column_width) {
max_len = fixed_column_width;
} else if (same_column_width) {
@@ -1074,45 +1189,18 @@ void ItemList::_notification(int p_what) {
}
if (icon_mode == ICON_MODE_TOP && max_text_lines > 0) {
- int ss = items[i].text.length();
- float ofs = 0;
- int line = 0;
- for (int j = 0; j <= ss; j++) {
- int cs = j < ss ? font->get_char_size(items[i].text[j], items[i].text[j + 1]).x : 0;
- if (ofs + cs > max_len || j == ss) {
- line_limit_cache.write[line] = j;
- line_size_cache.write[line] = ofs;
- line++;
- ofs = 0;
- if (line >= max_text_lines) {
- break;
- }
- } else {
- ofs += cs;
- }
- }
-
- line = 0;
- ofs = 0;
-
- text_ofs.y += font->get_ascent();
text_ofs = text_ofs.floor();
text_ofs += base_ofs;
text_ofs += items[i].rect_cache.position;
- FontDrawer drawer(font, Color(1, 1, 1));
- for (int j = 0; j < ss; j++) {
- if (j == line_limit_cache[line]) {
- line++;
- ofs = 0;
- if (line >= max_text_lines) {
- break;
- }
- }
- ofs += drawer.draw_char(get_canvas_item(), text_ofs + Vector2(ofs + (max_len - line_size_cache[line]) / 2, line * (font_height + line_separation)).floor(), items[i].text[j], items[i].text[j + 1], modulate);
+ if (rtl) {
+ text_ofs.x = size.width - text_ofs.x - max_len;
}
- //special multiline mode
+ items.write[i].text_buf->set_width(max_len);
+ items.write[i].text_buf->set_align(HALIGN_CENTER);
+
+ items[i].text_buf->draw(get_canvas_item(), text_ofs, modulate);
} else {
if (fixed_column_width > 0) {
size2.x = MIN(size2.x, fixed_column_width);
@@ -1124,12 +1212,22 @@ void ItemList::_notification(int p_what) {
text_ofs.y += (items[i].rect_cache.size.height - size2.y) / 2;
}
- text_ofs.y += font->get_ascent();
text_ofs = text_ofs.floor();
text_ofs += base_ofs;
text_ofs += items[i].rect_cache.position;
- draw_string(font, text_ofs, items[i].text, modulate, max_len + 1);
+ if (rtl) {
+ text_ofs.x = size.width - text_ofs.x - max_len;
+ }
+
+ items.write[i].text_buf->set_width(max_len);
+
+ if (rtl) {
+ items.write[i].text_buf->set_align(HALIGN_RIGHT);
+ } else {
+ items.write[i].text_buf->set_align(HALIGN_LEFT);
+ }
+ items[i].text_buf->draw(get_canvas_item(), text_ofs, modulate);
}
}
@@ -1140,6 +1238,11 @@ void ItemList::_notification(int p_what) {
r.size.y += vseparation;
r.position.x -= hseparation / 2;
r.size.x += hseparation;
+
+ if (rtl) {
+ r.position.x = size.width - r.position.x - r.size.x;
+ }
+
draw_style_box(cursor, r);
}
}
@@ -1181,6 +1284,10 @@ int ItemList::get_item_at_position(const Point2 &p_pos, bool p_exact) const {
pos -= bg->get_offset();
pos.y += scroll_bar->get_value();
+ if (is_layout_rtl()) {
+ pos.x = get_size().width - pos.x;
+ }
+
int closest = -1;
int closest_dist = 0x7FFFFFFF;
@@ -1215,6 +1322,10 @@ bool ItemList::is_pos_at_end_of_items(const Point2 &p_pos) const {
pos -= bg->get_offset();
pos.y += scroll_bar->get_value();
+ if (is_layout_rtl()) {
+ pos.x = get_size().width - pos.x;
+ }
+
Rect2 endrect = items[items.size() - 1].rect_cache;
return (pos.y > endrect.position.y + endrect.size.y);
}
@@ -1366,6 +1477,16 @@ void ItemList::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_item_icon", "idx", "icon"), &ItemList::set_item_icon);
ClassDB::bind_method(D_METHOD("get_item_icon", "idx"), &ItemList::get_item_icon);
+ ClassDB::bind_method(D_METHOD("set_item_text_direction", "idx", "direction"), &ItemList::set_item_text_direction);
+ ClassDB::bind_method(D_METHOD("get_item_text_direction", "idx"), &ItemList::get_item_text_direction);
+
+ ClassDB::bind_method(D_METHOD("set_item_opentype_feature", "idx", "tag", "value"), &ItemList::set_item_opentype_feature);
+ ClassDB::bind_method(D_METHOD("get_item_opentype_feature", "idx", "tag"), &ItemList::get_item_opentype_feature);
+ ClassDB::bind_method(D_METHOD("clear_item_opentype_features", "idx"), &ItemList::clear_item_opentype_features);
+
+ ClassDB::bind_method(D_METHOD("set_item_language", "idx", "language"), &ItemList::set_item_language);
+ ClassDB::bind_method(D_METHOD("get_item_language", "idx"), &ItemList::get_item_language);
+
ClassDB::bind_method(D_METHOD("set_item_icon_transposed", "idx", "transposed"), &ItemList::set_item_icon_transposed);
ClassDB::bind_method(D_METHOD("is_item_icon_transposed", "idx"), &ItemList::is_item_icon_transposed);
diff --git a/scene/gui/item_list.h b/scene/gui/item_list.h
index 03f477940c..9684ce0a32 100644
--- a/scene/gui/item_list.h
+++ b/scene/gui/item_list.h
@@ -33,6 +33,7 @@
#include "scene/gui/control.h"
#include "scene/gui/scroll_bar.h"
+#include "scene/resources/text_paragraph.h"
class ItemList : public Control {
GDCLASS(ItemList, Control);
@@ -56,6 +57,11 @@ private:
Color icon_modulate;
Ref<Texture2D> tag_icon;
String text;
+ Ref<TextParagraph> text_buf;
+ Dictionary opentype_features;
+ String language;
+ TextDirection text_direction = TEXT_DIRECTION_AUTO;
+
bool selectable;
bool selected;
bool disabled;
@@ -117,6 +123,7 @@ private:
void _scroll_changed(double);
void _gui_input(const Ref<InputEvent> &p_event);
+ void _shape(int p_idx);
protected:
void _notification(int p_what);
@@ -129,6 +136,16 @@ public:
void set_item_text(int p_idx, const String &p_text);
String get_item_text(int p_idx) const;
+ void set_item_text_direction(int p_idx, TextDirection p_text_direction);
+ TextDirection get_item_text_direction(int p_idx) const;
+
+ void set_item_opentype_feature(int p_idx, const String &p_name, int p_value);
+ int get_item_opentype_feature(int p_idx, const String &p_name) const;
+ void clear_item_opentype_features(int p_idx);
+
+ void set_item_language(int p_idx, const String &p_language);
+ String get_item_language(int p_idx) const;
+
void set_item_icon(int p_idx, const Ref<Texture2D> &p_icon);
Ref<Texture2D> get_item_icon(int p_idx) const;
diff --git a/scene/gui/label.cpp b/scene/gui/label.cpp
index 9df63a3c71..a15db9528f 100644
--- a/scene/gui/label.cpp
+++ b/scene/gui/label.cpp
@@ -93,16 +93,17 @@ void Label::_notification(int p_what) {
Size2 size = get_size();
Ref<StyleBox> style = get_theme_stylebox("normal");
Ref<Font> font = get_theme_font("font");
+ int font_size = get_theme_font_size("font_size");
Color font_color = get_theme_color("font_color");
Color font_color_shadow = get_theme_color("font_color_shadow");
bool use_outline = get_theme_constant("shadow_as_outline");
Point2 shadow_ofs(get_theme_constant("shadow_offset_x"), get_theme_constant("shadow_offset_y"));
int line_spacing = get_theme_constant("line_spacing");
- Color font_outline_modulate = get_theme_color("font_outline_modulate");
+ //Color font_outline_modulate = get_theme_color("font_outline_modulate");
style->draw(ci, Rect2(Point2(0, 0), get_size()));
- RenderingServer::get_singleton()->canvas_item_set_distance_field_mode(get_canvas_item(), font.is_valid() && font->is_distance_field_hint());
+ //RenderingServer::get_singleton()->canvas_item_set_distance_field_mode(get_canvas_item(), font.is_valid() && font->is_distance_field_hint());
int font_h = font->get_height() + line_spacing;
@@ -155,7 +156,7 @@ void Label::_notification(int p_what) {
int line = 0;
int line_to = lines_skipped + (lines_visible > 0 ? lines_visible : 1);
- FontDrawer drawer(font, font_outline_modulate);
+ //FontDrawer drawer(font, font_outline_modulate);
while (wc) {
/* handle lines not meant to be drawn quickly */
if (line >= line_to) {
@@ -242,11 +243,12 @@ void Label::_notification(int p_what) {
n = String::char_uppercase(n);
}
- float move = drawer.draw_char(ci, Point2(x_ofs_shadow, y_ofs) + shadow_ofs, c, n, font_color_shadow);
+ //TODO replace with TS
+ float move = font->draw_char(ci, Point2(x_ofs_shadow, y_ofs) + shadow_ofs, c, n, font_size, font_color_shadow);
if (use_outline) {
- drawer.draw_char(ci, Point2(x_ofs_shadow, y_ofs) + Vector2(-shadow_ofs.x, shadow_ofs.y), c, n, font_color_shadow);
- drawer.draw_char(ci, Point2(x_ofs_shadow, y_ofs) + Vector2(shadow_ofs.x, -shadow_ofs.y), c, n, font_color_shadow);
- drawer.draw_char(ci, Point2(x_ofs_shadow, y_ofs) + Vector2(-shadow_ofs.x, -shadow_ofs.y), c, n, font_color_shadow);
+ font->draw_char(ci, Point2(x_ofs_shadow, y_ofs) + Vector2(-shadow_ofs.x, shadow_ofs.y), c, n, font_size, font_color_shadow);
+ font->draw_char(ci, Point2(x_ofs_shadow, y_ofs) + Vector2(shadow_ofs.x, -shadow_ofs.y), c, n, font_size, font_color_shadow);
+ font->draw_char(ci, Point2(x_ofs_shadow, y_ofs) + Vector2(-shadow_ofs.x, -shadow_ofs.y), c, n, font_size, font_color_shadow);
}
x_ofs_shadow += move;
chars_total_shadow++;
@@ -262,7 +264,7 @@ void Label::_notification(int p_what) {
n = String::char_uppercase(n);
}
- x_ofs += drawer.draw_char(ci, Point2(x_ofs, y_ofs), c, n, font_color);
+ x_ofs += font->draw_char(ci, Point2(x_ofs, y_ofs), c, n, font_size, font_color);
chars_total++;
}
}
diff --git a/scene/gui/line_edit.cpp b/scene/gui/line_edit.cpp
index 857c96bea3..1502f1cbfa 100644
--- a/scene/gui/line_edit.cpp
+++ b/scene/gui/line_edit.cpp
@@ -719,6 +719,7 @@ void LineEdit::_notification(int p_what) {
}
Ref<Font> font = get_theme_font("font");
+ int font_size = get_theme_font_size("font_size");
style->draw(ci, Rect2(Point2(), size));
@@ -792,8 +793,10 @@ void LineEdit::_notification(int p_what) {
}
int caret_height = font->get_height() > y_area ? y_area : font->get_height();
- FontDrawer drawer(font, Color(1, 1, 1));
+ //FontDrawer drawer(font, Color(1, 1, 1));
while (true) {
+ //TODO replace with TS
+
// End of string, break.
if (char_ofs >= t.length()) {
break;
@@ -822,7 +825,7 @@ void LineEdit::_notification(int p_what) {
RenderingServer::get_singleton()->canvas_item_add_rect(ci, Rect2(Point2(x_ofs, y_ofs + caret_height), Size2(im_char_width, 1)), font_color);
}
- drawer.draw_char(ci, Point2(x_ofs, y_ofs + font_ascent), cchar, next, font_color);
+ font->draw_char(ci, Point2(x_ofs, y_ofs + font_ascent), cchar, next, font_size, font_color);
x_ofs += im_char_width;
ofs++;
@@ -846,7 +849,7 @@ void LineEdit::_notification(int p_what) {
}
int yofs = y_ofs + (caret_height - font->get_height()) / 2;
- drawer.draw_char(ci, Point2(x_ofs, yofs + font_ascent), cchar, next, selected ? font_color_selected : font_color);
+ font->draw_char(ci, Point2(x_ofs, yofs + font_ascent), cchar, next, font_size, selected ? font_color_selected : font_color);
if (char_ofs == cursor_pos && draw_caret && !using_placeholder) {
if (ime_text.length() == 0) {
@@ -885,7 +888,7 @@ void LineEdit::_notification(int p_what) {
RenderingServer::get_singleton()->canvas_item_add_rect(ci, Rect2(Point2(x_ofs, y_ofs + caret_height), Size2(im_char_width, 1)), font_color);
}
- drawer.draw_char(ci, Point2(x_ofs, y_ofs + font_ascent), cchar, next, font_color);
+ font->draw_char(ci, Point2(x_ofs, y_ofs + font_ascent), cchar, next, font_size, font_color);
x_ofs += im_char_width;
ofs++;
@@ -1427,6 +1430,7 @@ void LineEdit::clear_internal() {
Size2 LineEdit::get_minimum_size() const {
Ref<StyleBox> style = get_theme_stylebox("normal");
Ref<Font> font = get_theme_font("font");
+ int font_size = get_theme_font_size("font_size");
Size2 min_size;
@@ -1436,7 +1440,7 @@ Size2 LineEdit::get_minimum_size() const {
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.
- min_size.width = MAX(min_size.width, font->get_string_size(text).x + space_size);
+ min_size.width = MAX(min_size.width, font->get_string_size(text, font_size).x + space_size);
}
min_size.height = font->get_height();
diff --git a/scene/gui/link_button.cpp b/scene/gui/link_button.cpp
index 27a60945c8..d85c8d2112 100644
--- a/scene/gui/link_button.cpp
+++ b/scene/gui/link_button.cpp
@@ -29,17 +29,103 @@
/*************************************************************************/
#include "link_button.h"
+#include "core/string/translation.h"
+
+void LinkButton::_shape() {
+ Ref<Font> font = get_theme_font("font");
+ int font_size = get_theme_font_size("font_size");
+
+ text_buf->clear();
+ if (text_direction == Control::TEXT_DIRECTION_INHERITED) {
+ text_buf->set_direction(is_layout_rtl() ? TextServer::DIRECTION_RTL : TextServer::DIRECTION_LTR);
+ } else {
+ text_buf->set_direction((TextServer::Direction)text_direction);
+ }
+ TS->shaped_text_set_bidi_override(text_buf->get_rid(), structured_text_parser(st_parser, st_args, text));
+ text_buf->add_string(text, font, font_size, opentype_features, (language != "") ? language : TranslationServer::get_singleton()->get_tool_locale());
+}
void LinkButton::set_text(const String &p_text) {
text = p_text;
- update();
+ _shape();
minimum_size_changed();
+ update();
}
String LinkButton::get_text() const {
return text;
}
+void LinkButton::set_structured_text_bidi_override(Control::StructuredTextParser p_parser) {
+ if (st_parser != p_parser) {
+ st_parser = p_parser;
+ _shape();
+ update();
+ }
+}
+
+Control::StructuredTextParser LinkButton::get_structured_text_bidi_override() const {
+ return st_parser;
+}
+
+void LinkButton::set_structured_text_bidi_override_options(Array p_args) {
+ st_args = p_args;
+ _shape();
+ update();
+}
+
+Array LinkButton::get_structured_text_bidi_override_options() const {
+ return st_args;
+}
+
+void LinkButton::set_text_direction(Control::TextDirection p_text_direction) {
+ ERR_FAIL_COND((int)p_text_direction < -1 || (int)p_text_direction > 3);
+ if (text_direction != p_text_direction) {
+ text_direction = p_text_direction;
+ _shape();
+ update();
+ }
+}
+
+Control::TextDirection LinkButton::get_text_direction() const {
+ return text_direction;
+}
+
+void LinkButton::clear_opentype_features() {
+ opentype_features.clear();
+ _shape();
+ update();
+}
+
+void LinkButton::set_opentype_feature(const String &p_name, int p_value) {
+ int32_t tag = TS->name_to_tag(p_name);
+ if (!opentype_features.has(tag) || (int)opentype_features[tag] != p_value) {
+ opentype_features[tag] = p_value;
+ _shape();
+ update();
+ }
+}
+
+int LinkButton::get_opentype_feature(const String &p_name) const {
+ int32_t tag = TS->name_to_tag(p_name);
+ if (!opentype_features.has(tag)) {
+ return -1;
+ }
+ return opentype_features[tag];
+}
+
+void LinkButton::set_language(const String &p_language) {
+ if (language != p_language) {
+ language = p_language;
+ _shape();
+ update();
+ }
+}
+
+String LinkButton::get_language() const {
+ return language;
+}
+
void LinkButton::set_underline_mode(UnderlineMode p_underline_mode) {
underline_mode = p_underline_mode;
update();
@@ -50,11 +136,20 @@ LinkButton::UnderlineMode LinkButton::get_underline_mode() const {
}
Size2 LinkButton::get_minimum_size() const {
- return get_theme_font("font")->get_string_size(text);
+ return text_buf->get_size();
}
void LinkButton::_notification(int p_what) {
switch (p_what) {
+ case NOTIFICATION_TRANSLATION_CHANGED:
+ case NOTIFICATION_LAYOUT_DIRECTION_CHANGED: {
+ update();
+ } break;
+ case NOTIFICATION_THEME_CHANGED: {
+ _shape();
+ minimum_size_changed();
+ update();
+ } break;
case NOTIFICATION_DRAW: {
RID ci = get_canvas_item();
Size2 size = get_size();
@@ -94,38 +189,111 @@ void LinkButton::_notification(int p_what) {
style->draw(ci, Rect2(Point2(), size));
}
- Ref<Font> font = get_theme_font("font");
+ int width = text_buf->get_line_width();
- draw_string(font, Vector2(0, font->get_ascent()), text, color);
+ if (is_layout_rtl()) {
+ text_buf->draw(get_canvas_item(), Vector2(size.width - width, 0), color);
+ } else {
+ text_buf->draw(get_canvas_item(), Vector2(0, 0), color);
+ }
if (do_underline) {
- int underline_spacing = get_theme_constant("underline_spacing") + font->get_underline_position();
- int width = font->get_string_size(text).width;
- int y = font->get_ascent() + underline_spacing;
+ int underline_spacing = get_theme_constant("underline_spacing") + text_buf->get_line_underline_position();
+ int y = text_buf->get_line_ascent() + underline_spacing;
- draw_line(Vector2(0, y), Vector2(width, y), color, font->get_underline_thickness());
+ if (is_layout_rtl()) {
+ draw_line(Vector2(size.width - width, y), Vector2(size.width, y), color, text_buf->get_line_underline_thickness());
+ } else {
+ draw_line(Vector2(0, y), Vector2(width, y), color, text_buf->get_line_underline_thickness());
+ }
}
} break;
}
}
+bool LinkButton::_set(const StringName &p_name, const Variant &p_value) {
+ String str = p_name;
+ if (str.begins_with("opentype_features/")) {
+ String name = str.get_slicec('/', 1);
+ int32_t tag = TS->name_to_tag(name);
+ double value = p_value;
+ if (value == -1) {
+ if (opentype_features.has(tag)) {
+ opentype_features.erase(tag);
+ _shape();
+ update();
+ }
+ } else {
+ if ((double)opentype_features[tag] != value) {
+ opentype_features[tag] = value;
+ _shape();
+ update();
+ }
+ }
+ _change_notify();
+ return true;
+ }
+
+ return false;
+}
+
+bool LinkButton::_get(const StringName &p_name, Variant &r_ret) const {
+ String str = p_name;
+ if (str.begins_with("opentype_features/")) {
+ String name = str.get_slicec('/', 1);
+ int32_t tag = TS->name_to_tag(name);
+ if (opentype_features.has(tag)) {
+ r_ret = opentype_features[tag];
+ return true;
+ } else {
+ r_ret = -1;
+ return true;
+ }
+ }
+ return false;
+}
+
+void LinkButton::_get_property_list(List<PropertyInfo> *p_list) const {
+ for (const Variant *ftr = opentype_features.next(nullptr); ftr != nullptr; ftr = opentype_features.next(ftr)) {
+ String name = TS->tag_to_name(*ftr);
+ p_list->push_back(PropertyInfo(Variant::FLOAT, "opentype_features/" + name));
+ }
+ p_list->push_back(PropertyInfo(Variant::NIL, "opentype_features/_new", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_EDITOR));
+}
+
void LinkButton::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_text", "text"), &LinkButton::set_text);
ClassDB::bind_method(D_METHOD("get_text"), &LinkButton::get_text);
-
+ ClassDB::bind_method(D_METHOD("set_text_direction", "direction"), &LinkButton::set_text_direction);
+ ClassDB::bind_method(D_METHOD("get_text_direction"), &LinkButton::get_text_direction);
+ ClassDB::bind_method(D_METHOD("set_opentype_feature", "tag", "value"), &LinkButton::set_opentype_feature);
+ ClassDB::bind_method(D_METHOD("get_opentype_feature", "tag"), &LinkButton::get_opentype_feature);
+ ClassDB::bind_method(D_METHOD("clear_opentype_features"), &LinkButton::clear_opentype_features);
+ ClassDB::bind_method(D_METHOD("set_language", "language"), &LinkButton::set_language);
+ ClassDB::bind_method(D_METHOD("get_language"), &LinkButton::get_language);
ClassDB::bind_method(D_METHOD("set_underline_mode", "underline_mode"), &LinkButton::set_underline_mode);
ClassDB::bind_method(D_METHOD("get_underline_mode"), &LinkButton::get_underline_mode);
+ ClassDB::bind_method(D_METHOD("set_structured_text_bidi_override", "parser"), &LinkButton::set_structured_text_bidi_override);
+ ClassDB::bind_method(D_METHOD("get_structured_text_bidi_override"), &LinkButton::get_structured_text_bidi_override);
+ ClassDB::bind_method(D_METHOD("set_structured_text_bidi_override_options", "args"), &LinkButton::set_structured_text_bidi_override_options);
+ ClassDB::bind_method(D_METHOD("get_structured_text_bidi_override_options"), &LinkButton::get_structured_text_bidi_override_options);
BIND_ENUM_CONSTANT(UNDERLINE_MODE_ALWAYS);
BIND_ENUM_CONSTANT(UNDERLINE_MODE_ON_HOVER);
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::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_");
+ ADD_PROPERTY(PropertyInfo(Variant::INT, "structured_text_bidi_override", PROPERTY_HINT_ENUM, "Default,URI,File,Email,List,None,Custom"), "set_structured_text_bidi_override", "get_structured_text_bidi_override");
+ ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "structured_text_bidi_override_options"), "set_structured_text_bidi_override_options", "get_structured_text_bidi_override_options");
}
LinkButton::LinkButton() {
+ text_buf.instance();
underline_mode = UNDERLINE_MODE_ALWAYS;
set_default_cursor_shape(CURSOR_POINTING_HAND);
}
diff --git a/scene/gui/link_button.h b/scene/gui/link_button.h
index b8469b529a..8c1daef166 100644
--- a/scene/gui/link_button.h
+++ b/scene/gui/link_button.h
@@ -33,6 +33,7 @@
#include "scene/gui/base_button.h"
#include "scene/resources/bit_map.h"
+#include "scene/resources/text_line.h"
class LinkButton : public BaseButton {
GDCLASS(LinkButton, BaseButton);
@@ -46,17 +47,46 @@ public:
private:
String text;
+ Ref<TextLine> text_buf;
UnderlineMode underline_mode;
+ Dictionary opentype_features;
+ String language;
+ TextDirection text_direction = TEXT_DIRECTION_AUTO;
+ Control::StructuredTextParser st_parser = STRUCTURED_TEXT_DEFAULT;
+ Array st_args;
+
+ void _shape();
+
protected:
virtual Size2 get_minimum_size() const override;
void _notification(int p_what);
static void _bind_methods();
+ 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;
+
public:
void set_text(const String &p_text);
String get_text() const;
+ void set_structured_text_bidi_override(Control::StructuredTextParser p_parser);
+ Control::StructuredTextParser get_structured_text_bidi_override() const;
+
+ void set_structured_text_bidi_override_options(Array p_args);
+ Array get_structured_text_bidi_override_options() const;
+
+ void set_text_direction(TextDirection p_text_direction);
+ TextDirection get_text_direction() const;
+
+ void set_opentype_feature(const String &p_name, int p_value);
+ int get_opentype_feature(const String &p_name) const;
+ void clear_opentype_features();
+
+ void set_language(const String &p_language);
+ String get_language() const;
+
void set_underline_mode(UnderlineMode p_underline_mode);
UnderlineMode get_underline_mode() const;
diff --git a/scene/gui/option_button.cpp b/scene/gui/option_button.cpp
index f0e69a94a4..902d2715d4 100644
--- a/scene/gui/option_button.cpp
+++ b/scene/gui/option_button.cpp
@@ -77,12 +77,25 @@ void OptionButton::_notification(int p_what) {
Size2 size = get_size();
- Point2 ofs(size.width - arrow->get_width() - get_theme_constant("arrow_margin"), int(Math::abs((size.height - arrow->get_height()) / 2)));
+ Point2 ofs;
+ if (is_layout_rtl()) {
+ ofs = Point2(get_theme_constant("arrow_margin"), int(Math::abs((size.height - arrow->get_height()) / 2)));
+ } else {
+ ofs = Point2(size.width - arrow->get_width() - get_theme_constant("arrow_margin"), int(Math::abs((size.height - arrow->get_height()) / 2)));
+ }
arrow->draw(ci, ofs, clr);
} break;
+ case NOTIFICATION_TRANSLATION_CHANGED:
+ case NOTIFICATION_LAYOUT_DIRECTION_CHANGED:
case NOTIFICATION_THEME_CHANGED: {
if (has_theme_icon("arrow")) {
- _set_internal_margin(MARGIN_RIGHT, Control::get_theme_icon("arrow")->get_width());
+ if (is_layout_rtl()) {
+ _set_internal_margin(MARGIN_LEFT, Control::get_theme_icon("arrow")->get_width());
+ _set_internal_margin(MARGIN_RIGHT, 0.f);
+ } else {
+ _set_internal_margin(MARGIN_LEFT, 0.f);
+ _set_internal_margin(MARGIN_RIGHT, Control::get_theme_icon("arrow")->get_width());
+ }
}
} break;
case NOTIFICATION_VISIBILITY_CHANGED: {
@@ -326,10 +339,16 @@ OptionButton::OptionButton() {
current = -1;
set_toggle_mode(true);
set_text_align(ALIGN_LEFT);
- set_action_mode(ACTION_MODE_BUTTON_PRESS);
- if (has_theme_icon("arrow")) {
- _set_internal_margin(MARGIN_RIGHT, Control::get_theme_icon("arrow")->get_width());
+ if (is_layout_rtl()) {
+ if (has_theme_icon("arrow")) {
+ _set_internal_margin(MARGIN_LEFT, Control::get_theme_icon("arrow")->get_width());
+ }
+ } else {
+ if (has_theme_icon("arrow")) {
+ _set_internal_margin(MARGIN_RIGHT, Control::get_theme_icon("arrow")->get_width());
+ }
}
+ set_action_mode(ACTION_MODE_BUTTON_PRESS);
popup = memnew(PopupMenu);
popup->hide();
diff --git a/scene/gui/popup_menu.cpp b/scene/gui/popup_menu.cpp
index 7baf32173f..6dbf005f73 100644
--- a/scene/gui/popup_menu.cpp
+++ b/scene/gui/popup_menu.cpp
@@ -36,13 +36,11 @@
#include "core/string/print_string.h"
#include "core/string/translation.h"
-String PopupMenu::_get_accel_text(int p_item) const {
- ERR_FAIL_INDEX_V(p_item, items.size(), String());
-
- if (items[p_item].shortcut.is_valid()) {
- return items[p_item].shortcut->get_as_text();
- } else if (items[p_item].accel) {
- return keycode_get_string(items[p_item].accel);
+String PopupMenu::_get_accel_text(const Item &p_item) const {
+ if (p_item.shortcut.is_valid()) {
+ return p_item.shortcut->get_as_text();
+ } else if (p_item.accel) {
+ return keycode_get_string(p_item.accel);
}
return String();
}
@@ -53,11 +51,9 @@ Size2 PopupMenu::_get_contents_minimum_size() const {
Size2 minsize = get_theme_stylebox("panel")->get_minimum_size(); // Accounts for margin in the margin container
minsize.x += scroll_container->get_v_scrollbar()->get_size().width * 2; // Adds a buffer so that the scrollbar does not render over the top of content
- Ref<Font> font = get_theme_font("font");
float max_w = 0;
float icon_w = 0;
- int font_h = font->get_height();
int check_w = MAX(get_theme_icon("checked")->get_width(), get_theme_icon("radio_checked")->get_width()) + hseparation;
int accel_max_w = 0;
bool has_check = false;
@@ -66,7 +62,7 @@ Size2 PopupMenu::_get_contents_minimum_size() const {
Size2 size;
Size2 icon_size = items[i].get_icon_size();
- size.height = MAX(icon_size.height, font_h);
+ size.height = MAX(icon_size.height, items[i].text_buf->get_size().y);
icon_w = MAX(icon_size.width, icon_w);
size.width += items[i].h_ofs;
@@ -75,15 +71,14 @@ Size2 PopupMenu::_get_contents_minimum_size() const {
has_check = true;
}
- String text = items[i].xl_text;
- size.width += font->get_string_size(text).width;
+ size.width += items[i].text_buf->get_size().x;
if (i > 0) {
size.height += vseparation;
}
if (items[i].accel || (items[i].shortcut.is_valid() && items[i].shortcut->is_valid())) {
int accel_w = hseparation * 2;
- accel_w += font->get_string_size(_get_accel_text(i)).width;
+ accel_w += items[i].accel_text_buf->get_size().x;
accel_max_w = MAX(accel_w, accel_max_w);
}
@@ -112,13 +107,12 @@ Size2 PopupMenu::_get_contents_minimum_size() const {
}
int PopupMenu::_get_items_total_height() const {
- int font_height = get_theme_font("font")->get_height();
int vsep = get_theme_constant("vseparation");
// Get total height of all items by taking max of icon height and font height
int items_total_height = 0;
for (int i = 0; i < items.size(); i++) {
- items_total_height += MAX(items[i].get_icon_size().height, font_height) + vsep;
+ items_total_height += MAX(items[i].get_icon_size().height, items[i].text_buf->get_size().y) + vsep;
}
// Subtract a separator which is not needed for the last item.
@@ -150,7 +144,6 @@ int PopupMenu::_get_mouse_over(const Point2 &p_over) const {
Ref<StyleBox> style = get_theme_stylebox("panel"); // Accounts for margin in the margin container
int vseparation = get_theme_constant("vseparation");
- float font_h = get_theme_font("font")->get_height();
Point2 ofs = style->get_offset() + Point2(0, vseparation / 2);
@@ -163,7 +156,7 @@ int PopupMenu::_get_mouse_over(const Point2 &p_over) const {
ofs.y += vseparation;
}
- ofs.y += MAX(items[i].get_icon_size().height, font_h);
+ ofs.y += MAX(items[i].get_icon_size().height, items[i].text_buf->get_size().y);
if (p_over.y - control->get_position().y < ofs.y) {
return i;
@@ -190,10 +183,19 @@ void PopupMenu::_activate_submenu(int p_over) {
float scroll_offset = control->get_position().y;
- Point2 submenu_pos = this_pos + Point2(this_rect.size.width, items[p_over]._ofs_cache + scroll_offset);
+ Point2 submenu_pos;
Size2 submenu_size = submenu_popup->get_size();
+ if (control->is_layout_rtl()) {
+ submenu_pos = this_pos + Point2(-submenu_size.width, items[p_over]._ofs_cache + scroll_offset);
+ } else {
+ submenu_pos = this_pos + Point2(this_rect.size.width, items[p_over]._ofs_cache + scroll_offset);
+ }
// Fix pos if going outside parent rect
+ if (submenu_pos.x < get_parent_rect().position.x) {
+ submenu_pos.x = this_pos.x + submenu_size.width;
+ }
+
if (submenu_pos.x + submenu_size.width > get_parent_rect().size.width) {
submenu_pos.x = this_pos.x - submenu_size.width;
}
@@ -287,7 +289,11 @@ void PopupMenu::_gui_input(const Ref<InputEvent> &p_event) {
// Make an area which does not include v scrollbar, so that items are not activated when dragging scrollbar.
Rect2 item_clickable_area = scroll_container->get_rect();
if (scroll_container->get_v_scrollbar()->is_visible_in_tree()) {
- item_clickable_area.size.width -= scroll_container->get_v_scrollbar()->get_size().width;
+ if (is_layout_rtl()) {
+ item_clickable_area.position.x += scroll_container->get_v_scrollbar()->get_size().width;
+ } else {
+ item_clickable_area.size.width -= scroll_container->get_v_scrollbar()->get_size().width;
+ }
}
Ref<InputEventMouseButton> b = p_event;
@@ -417,13 +423,19 @@ void PopupMenu::_draw_items() {
margin_size.width = margin_container->get_theme_constant("margin_right") + margin_container->get_theme_constant("margin_left");
margin_size.height = margin_container->get_theme_constant("margin_top") + margin_container->get_theme_constant("margin_bottom");
+ bool rtl = control->is_layout_rtl();
Ref<StyleBox> style = get_theme_stylebox("panel");
Ref<StyleBox> hover = get_theme_stylebox("hover");
- Ref<Font> font = get_theme_font("font");
// In Item::checkable_type enum order (less the non-checkable member)
Ref<Texture2D> check[] = { get_theme_icon("checked"), get_theme_icon("radio_checked") };
Ref<Texture2D> uncheck[] = { get_theme_icon("unchecked"), get_theme_icon("radio_unchecked") };
- Ref<Texture2D> submenu = get_theme_icon("submenu");
+ Ref<Texture2D> submenu;
+ if (rtl) {
+ submenu = get_theme_icon("submenu_mirrored");
+ } else {
+ submenu = get_theme_icon("submenu");
+ }
+
Ref<StyleBox> separator = get_theme_stylebox("separator");
Ref<StyleBox> labeled_separator_left = get_theme_stylebox("labeled_separator_left");
Ref<StyleBox> labeled_separator_right = get_theme_stylebox("labeled_separator_right");
@@ -434,7 +446,6 @@ void PopupMenu::_draw_items() {
Color font_color_disabled = get_theme_color("font_color_disabled");
Color font_color_accel = get_theme_color("font_color_accel");
Color font_color_hover = get_theme_color("font_color_hover");
- float font_h = font->get_height();
float scroll_width = scroll_container->get_v_scrollbar()->is_visible_in_tree() ? scroll_container->get_v_scrollbar()->get_size().width : 0;
float display_width = control->get_size().width - scroll_width;
@@ -467,12 +478,18 @@ void PopupMenu::_draw_items() {
ofs.y += vseparation;
}
+ _shape_item(i);
+
Point2 item_ofs = ofs;
Size2 icon_size = items[i].get_icon_size();
- float h = MAX(icon_size.height, font_h);
+ float h = MAX(icon_size.height, items[i].text_buf->get_size().y);
if (i == mouse_over) {
- hover->draw(ci, Rect2(item_ofs + Point2(-hseparation, -vseparation / 2), Size2(display_width + hseparation * 2, h + vseparation)));
+ if (rtl) {
+ hover->draw(ci, Rect2(item_ofs + Point2(-hseparation + scroll_width, -vseparation / 2), Size2(display_width + hseparation * 2, h + vseparation)));
+ } else {
+ hover->draw(ci, Rect2(item_ofs + Point2(-hseparation, -vseparation / 2), Size2(display_width + hseparation * 2, h + vseparation)));
+ }
}
String text = items[i].xl_text;
@@ -482,7 +499,7 @@ void PopupMenu::_draw_items() {
if (items[i].separator) {
int sep_h = separator->get_center_size().height + separator->get_minimum_size().height;
if (text != String()) {
- int text_size = font->get_string_size(text).width;
+ int text_size = items[i].text_buf->get_size().width;
int text_center = display_width / 2;
int text_left = text_center - text_size / 2;
int text_right = text_center + text_size / 2;
@@ -502,36 +519,54 @@ void PopupMenu::_draw_items() {
// Checkboxes
if (items[i].checkable_type) {
Texture2D *icon = (items[i].checked ? check[items[i].checkable_type - 1] : uncheck[items[i].checkable_type - 1]).ptr();
- icon->draw(ci, item_ofs + Point2(0, Math::floor((h - icon->get_height()) / 2.0)), icon_color);
+ if (rtl) {
+ icon->draw(ci, Size2(control->get_size().width - item_ofs.x - icon->get_width(), item_ofs.y) + Point2(0, Math::floor((h - icon->get_height()) / 2.0)), icon_color);
+ } else {
+ icon->draw(ci, item_ofs + Point2(0, Math::floor((h - icon->get_height()) / 2.0)), icon_color);
+ }
}
// Icon
if (!items[i].icon.is_null()) {
- items[i].icon->draw(ci, item_ofs + Size2(check_ofs, 0) + Point2(0, Math::floor((h - icon_size.height) / 2.0)), icon_color);
+ if (rtl) {
+ items[i].icon->draw(ci, Size2(control->get_size().width - item_ofs.x - check_ofs - icon_size.width, item_ofs.y) + Point2(0, Math::floor((h - icon_size.height) / 2.0)), icon_color);
+ } else {
+ items[i].icon->draw(ci, item_ofs + Size2(check_ofs, 0) + Point2(0, Math::floor((h - icon_size.height) / 2.0)), icon_color);
+ }
}
// Submenu arrow on right hand side
if (items[i].submenu != "") {
- submenu->draw(ci, Point2(display_width - submenu->get_width(), item_ofs.y + Math::floor(h - submenu->get_height()) / 2), icon_color);
+ if (rtl) {
+ submenu->draw(ci, Point2(scroll_width + style->get_margin(MARGIN_LEFT), item_ofs.y + Math::floor(h - submenu->get_height()) / 2), icon_color);
+ } else {
+ submenu->draw(ci, Point2(display_width - style->get_margin(MARGIN_RIGHT) - submenu->get_width(), item_ofs.y + Math::floor(h - submenu->get_height()) / 2), icon_color);
+ }
}
// Text
- item_ofs.y += font->get_ascent();
if (items[i].separator) {
if (text != String()) {
- int center = (display_width - font->get_string_size(text).width) / 2;
- font->draw(ci, Point2(center, item_ofs.y + Math::floor((h - font_h) / 2.0)), text, font_color_disabled);
+ int center = (display_width - items[i].text_buf->get_size().width) / 2;
+ items[i].text_buf->draw(ci, Point2(center, item_ofs.y + Math::floor((h - items[i].text_buf->get_size().y) / 2.0)), font_color_disabled);
}
} else {
item_ofs.x += icon_ofs + check_ofs;
- font->draw(ci, item_ofs + Point2(0, Math::floor((h - font_h) / 2.0)), text, items[i].disabled ? font_color_disabled : (i == mouse_over ? font_color_hover : font_color));
+ if (rtl) {
+ items[i].text_buf->draw(ci, Size2(control->get_size().width - items[i].text_buf->get_size().width - item_ofs.x, item_ofs.y) + Point2(0, Math::floor((h - items[i].text_buf->get_size().y) / 2.0)), items[i].disabled ? font_color_disabled : (i == mouse_over ? font_color_hover : font_color));
+ } else {
+ items[i].text_buf->draw(ci, item_ofs + Point2(0, Math::floor((h - items[i].text_buf->get_size().y) / 2.0)), items[i].disabled ? font_color_disabled : (i == mouse_over ? font_color_hover : font_color));
+ }
}
// Accelerator / Shortcut
if (items[i].accel || (items[i].shortcut.is_valid() && items[i].shortcut->is_valid())) {
- String sc_text = _get_accel_text(i);
- item_ofs.x = display_width - font->get_string_size(sc_text).width;
- font->draw(ci, item_ofs + Point2(0, Math::floor((h - font_h) / 2.0)), sc_text, i == mouse_over ? font_color_hover : font_color_accel);
+ if (rtl) {
+ item_ofs.x = scroll_width + style->get_margin(MARGIN_LEFT);
+ } else {
+ item_ofs.x = display_width - style->get_margin(MARGIN_RIGHT) - items[i].accel_text_buf->get_size().x;
+ }
+ items[i].accel_text_buf->draw(ci, item_ofs + Point2(0, Math::floor((h - items[i].text_buf->get_size().y) / 2.0)), i == mouse_over ? font_color_hover : font_color_accel);
}
// Cache the item vertical offset from the first item and the height
@@ -573,6 +608,27 @@ void PopupMenu::_close_pressed() {
}
}
+void PopupMenu::_shape_item(int p_item) {
+ if (items.write[p_item].dirty) {
+ items.write[p_item].text_buf->clear();
+
+ Ref<Font> font = get_theme_font("font");
+ int font_size = get_theme_font_size("font_size");
+
+ if (items[p_item].text_direction == Control::TEXT_DIRECTION_INHERITED) {
+ items.write[p_item].text_buf->set_direction(is_layout_rtl() ? TextServer::DIRECTION_RTL : TextServer::DIRECTION_LTR);
+ } else {
+ items.write[p_item].text_buf->set_direction((TextServer::Direction)items[p_item].text_direction);
+ }
+ items.write[p_item].text_buf->add_string(items.write[p_item].xl_text, font, font_size, items[p_item].opentype_features, (items[p_item].language != "") ? items[p_item].language : TranslationServer::get_singleton()->get_tool_locale());
+
+ items.write[p_item].accel_text_buf->clear();
+ items.write[p_item].accel_text_buf->set_direction(is_layout_rtl() ? TextServer::DIRECTION_RTL : TextServer::DIRECTION_LTR);
+ items.write[p_item].accel_text_buf->add_string(_get_accel_text(items.write[p_item]), font, font_size, Dictionary(), TranslationServer::get_singleton()->get_tool_locale());
+ items.write[p_item].dirty = false;
+ }
+}
+
void PopupMenu::_notification(int p_what) {
switch (p_what) {
case NOTIFICATION_ENTER_TREE: {
@@ -583,9 +639,12 @@ void PopupMenu::_notification(int p_what) {
set_submenu_popup_delay(pm_delay);
}
} break;
+ case NOTIFICATION_THEME_CHANGED:
+ case Control::NOTIFICATION_LAYOUT_DIRECTION_CHANGED:
case NOTIFICATION_TRANSLATION_CHANGED: {
for (int i = 0; i < items.size(); i++) {
items.write[i].xl_text = tr(items[i].text);
+ items.write[i].dirty = true;
}
child_controls_changed();
@@ -676,6 +735,7 @@ void PopupMenu::add_item(const String &p_label, int p_id, uint32_t p_accel) {
Item item;
ITEM_SETUP_WITH_ACCEL(p_label, p_id, p_accel);
items.push_back(item);
+ _shape_item(items.size() - 1);
control->update();
child_controls_changed();
}
@@ -685,6 +745,7 @@ void PopupMenu::add_icon_item(const Ref<Texture2D> &p_icon, const String &p_labe
ITEM_SETUP_WITH_ACCEL(p_label, p_id, p_accel);
item.icon = p_icon;
items.push_back(item);
+ _shape_item(items.size() - 1);
control->update();
child_controls_changed();
}
@@ -694,6 +755,7 @@ void PopupMenu::add_check_item(const String &p_label, int p_id, uint32_t p_accel
ITEM_SETUP_WITH_ACCEL(p_label, p_id, p_accel);
item.checkable_type = Item::CHECKABLE_TYPE_CHECK_BOX;
items.push_back(item);
+ _shape_item(items.size() - 1);
control->update();
child_controls_changed();
}
@@ -704,6 +766,7 @@ void PopupMenu::add_icon_check_item(const Ref<Texture2D> &p_icon, const String &
item.icon = p_icon;
item.checkable_type = Item::CHECKABLE_TYPE_CHECK_BOX;
items.push_back(item);
+ _shape_item(items.size() - 1);
control->update();
child_controls_changed();
}
@@ -713,6 +776,7 @@ void PopupMenu::add_radio_check_item(const String &p_label, int p_id, uint32_t p
ITEM_SETUP_WITH_ACCEL(p_label, p_id, p_accel);
item.checkable_type = Item::CHECKABLE_TYPE_RADIO_BUTTON;
items.push_back(item);
+ _shape_item(items.size() - 1);
control->update();
child_controls_changed();
}
@@ -723,6 +787,7 @@ void PopupMenu::add_icon_radio_check_item(const Ref<Texture2D> &p_icon, const St
item.icon = p_icon;
item.checkable_type = Item::CHECKABLE_TYPE_RADIO_BUTTON;
items.push_back(item);
+ _shape_item(items.size() - 1);
control->update();
child_controls_changed();
}
@@ -733,6 +798,7 @@ void PopupMenu::add_multistate_item(const String &p_label, int p_max_states, int
item.max_states = p_max_states;
item.state = p_default_state;
items.push_back(item);
+ _shape_item(items.size() - 1);
control->update();
child_controls_changed();
}
@@ -750,6 +816,7 @@ void PopupMenu::add_shortcut(const Ref<Shortcut> &p_shortcut, int p_id, bool p_g
Item item;
ITEM_SETUP_WITH_SHORTCUT(p_shortcut, p_id, p_global);
items.push_back(item);
+ _shape_item(items.size() - 1);
control->update();
child_controls_changed();
}
@@ -759,6 +826,7 @@ void PopupMenu::add_icon_shortcut(const Ref<Texture2D> &p_icon, const Ref<Shortc
ITEM_SETUP_WITH_SHORTCUT(p_shortcut, p_id, p_global);
item.icon = p_icon;
items.push_back(item);
+ _shape_item(items.size() - 1);
control->update();
child_controls_changed();
}
@@ -768,6 +836,7 @@ void PopupMenu::add_check_shortcut(const Ref<Shortcut> &p_shortcut, int p_id, bo
ITEM_SETUP_WITH_SHORTCUT(p_shortcut, p_id, p_global);
item.checkable_type = Item::CHECKABLE_TYPE_CHECK_BOX;
items.push_back(item);
+ _shape_item(items.size() - 1);
control->update();
child_controls_changed();
}
@@ -778,6 +847,7 @@ void PopupMenu::add_icon_check_shortcut(const Ref<Texture2D> &p_icon, const Ref<
item.icon = p_icon;
item.checkable_type = Item::CHECKABLE_TYPE_CHECK_BOX;
items.push_back(item);
+ _shape_item(items.size() - 1);
control->update();
child_controls_changed();
}
@@ -787,6 +857,7 @@ void PopupMenu::add_radio_check_shortcut(const Ref<Shortcut> &p_shortcut, int p_
ITEM_SETUP_WITH_SHORTCUT(p_shortcut, p_id, p_global);
item.checkable_type = Item::CHECKABLE_TYPE_RADIO_BUTTON;
items.push_back(item);
+ _shape_item(items.size() - 1);
control->update();
child_controls_changed();
}
@@ -797,6 +868,7 @@ void PopupMenu::add_icon_radio_check_shortcut(const Ref<Texture2D> &p_icon, cons
item.icon = p_icon;
item.checkable_type = Item::CHECKABLE_TYPE_RADIO_BUTTON;
items.push_back(item);
+ _shape_item(items.size() - 1);
control->update();
child_controls_changed();
}
@@ -808,6 +880,7 @@ void PopupMenu::add_submenu_item(const String &p_label, const String &p_submenu,
item.id = p_id == -1 ? items.size() : p_id;
item.submenu = p_submenu;
items.push_back(item);
+ _shape_item(items.size() - 1);
control->update();
child_controls_changed();
}
@@ -821,11 +894,48 @@ void PopupMenu::set_item_text(int p_idx, const String &p_text) {
ERR_FAIL_INDEX(p_idx, items.size());
items.write[p_idx].text = p_text;
items.write[p_idx].xl_text = tr(p_text);
+ _shape_item(p_idx);
control->update();
child_controls_changed();
}
+void PopupMenu::set_item_text_direction(int p_item, Control::TextDirection p_text_direction) {
+ ERR_FAIL_INDEX(p_item, items.size());
+ ERR_FAIL_COND((int)p_text_direction < -1 || (int)p_text_direction > 3);
+ if (items[p_item].text_direction != p_text_direction) {
+ items.write[p_item].text_direction = p_text_direction;
+ items.write[p_item].dirty = true;
+ control->update();
+ }
+}
+
+void PopupMenu::clear_item_opentype_features(int p_item) {
+ ERR_FAIL_INDEX(p_item, items.size());
+ items.write[p_item].opentype_features.clear();
+ items.write[p_item].dirty = true;
+ control->update();
+}
+
+void PopupMenu::set_item_opentype_feature(int p_item, const String &p_name, int p_value) {
+ ERR_FAIL_INDEX(p_item, items.size());
+ int32_t tag = TS->name_to_tag(p_name);
+ if (!items[p_item].opentype_features.has(tag) || (int)items[p_item].opentype_features[tag] != p_value) {
+ items.write[p_item].opentype_features[tag] = p_value;
+ items.write[p_item].dirty = true;
+ control->update();
+ }
+}
+
+void PopupMenu::set_item_language(int p_item, const String &p_language) {
+ ERR_FAIL_INDEX(p_item, items.size());
+ if (items[p_item].language != p_language) {
+ items.write[p_item].language = p_language;
+ items.write[p_item].dirty = true;
+ control->update();
+ }
+}
+
void PopupMenu::set_item_icon(int p_idx, const Ref<Texture2D> &p_icon) {
ERR_FAIL_INDEX(p_idx, items.size());
items.write[p_idx].icon = p_icon;
@@ -854,6 +964,7 @@ void PopupMenu::set_item_id(int p_idx, int p_id) {
void PopupMenu::set_item_accelerator(int p_idx, uint32_t p_accel) {
ERR_FAIL_INDEX(p_idx, items.size());
items.write[p_idx].accel = p_accel;
+ items.write[p_idx].dirty = true;
control->update();
child_controls_changed();
@@ -892,6 +1003,25 @@ String PopupMenu::get_item_text(int p_idx) const {
return items[p_idx].text;
}
+Control::TextDirection PopupMenu::get_item_text_direction(int p_item) const {
+ ERR_FAIL_INDEX_V(p_item, items.size(), Control::TEXT_DIRECTION_INHERITED);
+ return items[p_item].text_direction;
+}
+
+int PopupMenu::get_item_opentype_feature(int p_item, const String &p_name) const {
+ ERR_FAIL_INDEX_V(p_item, items.size(), -1);
+ int32_t tag = TS->name_to_tag(p_name);
+ if (!items[p_item].opentype_features.has(tag)) {
+ return -1;
+ }
+ return items[p_item].opentype_features[tag];
+}
+
+String PopupMenu::get_item_language(int p_item) const {
+ ERR_FAIL_INDEX_V(p_item, items.size(), "");
+ return items[p_item].language;
+}
+
int PopupMenu::get_item_idx_from_text(const String &text) const {
for (int idx = 0; idx < items.size(); idx++) {
if (items[idx].text == text) {
@@ -998,6 +1128,7 @@ void PopupMenu::set_item_shortcut(int p_idx, const Ref<Shortcut> &p_shortcut, bo
}
items.write[p_idx].shortcut = p_shortcut;
items.write[p_idx].shortcut_is_global = p_global;
+ items.write[p_idx].dirty = true;
if (items[p_idx].shortcut.is_valid()) {
_ref_shortcut(items[p_idx].shortcut);
@@ -1390,6 +1521,9 @@ void PopupMenu::_bind_methods() {
ClassDB::bind_method(D_METHOD("add_submenu_item", "label", "submenu", "id"), &PopupMenu::add_submenu_item, DEFVAL(-1));
ClassDB::bind_method(D_METHOD("set_item_text", "idx", "text"), &PopupMenu::set_item_text);
+ ClassDB::bind_method(D_METHOD("set_item_text_direction", "idx", "direction"), &PopupMenu::set_item_text_direction);
+ ClassDB::bind_method(D_METHOD("set_item_opentype_feature", "idx", "tag", "value"), &PopupMenu::set_item_opentype_feature);
+ ClassDB::bind_method(D_METHOD("set_item_language", "idx", "language"), &PopupMenu::set_item_language);
ClassDB::bind_method(D_METHOD("set_item_icon", "idx", "icon"), &PopupMenu::set_item_icon);
ClassDB::bind_method(D_METHOD("set_item_checked", "idx", "checked"), &PopupMenu::set_item_checked);
ClassDB::bind_method(D_METHOD("set_item_id", "idx", "id"), &PopupMenu::set_item_id);
@@ -1409,6 +1543,10 @@ void PopupMenu::_bind_methods() {
ClassDB::bind_method(D_METHOD("toggle_item_multistate", "idx"), &PopupMenu::toggle_item_multistate);
ClassDB::bind_method(D_METHOD("get_item_text", "idx"), &PopupMenu::get_item_text);
+ ClassDB::bind_method(D_METHOD("get_item_text_direction", "idx"), &PopupMenu::get_item_text_direction);
+ ClassDB::bind_method(D_METHOD("get_item_opentype_feature", "idx", "tag"), &PopupMenu::get_item_opentype_feature);
+ ClassDB::bind_method(D_METHOD("clear_item_opentype_features", "idx"), &PopupMenu::clear_item_opentype_features);
+ ClassDB::bind_method(D_METHOD("get_item_language", "idx"), &PopupMenu::get_item_language);
ClassDB::bind_method(D_METHOD("get_item_icon", "idx"), &PopupMenu::get_item_icon);
ClassDB::bind_method(D_METHOD("is_item_checked", "idx"), &PopupMenu::is_item_checked);
ClassDB::bind_method(D_METHOD("get_item_id", "idx"), &PopupMenu::get_item_id);
diff --git a/scene/gui/popup_menu.h b/scene/gui/popup_menu.h
index a2e7d7e6cd..a082fcf0e7 100644
--- a/scene/gui/popup_menu.h
+++ b/scene/gui/popup_menu.h
@@ -35,6 +35,7 @@
#include "scene/gui/popup.h"
#include "scene/gui/scroll_container.h"
#include "scene/gui/shortcut.h"
+#include "scene/resources/text_line.h"
class PopupMenu : public Popup {
GDCLASS(PopupMenu, Popup);
@@ -43,6 +44,13 @@ class PopupMenu : public Popup {
Ref<Texture2D> icon;
String text;
String xl_text;
+ Ref<TextLine> text_buf;
+ Ref<TextLine> accel_text_buf;
+
+ Dictionary opentype_features;
+ String language;
+ Control::TextDirection text_direction = Control::TEXT_DIRECTION_AUTO;
+
bool checked;
enum {
CHECKABLE_TYPE_NONE,
@@ -53,6 +61,7 @@ class PopupMenu : public Popup {
int state;
bool separator;
bool disabled;
+ bool dirty;
int id;
Variant metadata;
String submenu;
@@ -71,6 +80,9 @@ class PopupMenu : public Popup {
}
Item() {
+ text_buf.instance();
+ accel_text_buf.instance();
+ dirty = true;
checked = false;
checkable_type = CHECKABLE_TYPE_NONE;
separator = false;
@@ -97,13 +109,15 @@ class PopupMenu : public Popup {
int mouse_over;
int submenu_over;
Rect2 parent_rect;
- String _get_accel_text(int p_item) const;
+ String _get_accel_text(const Item &p_item) const;
int _get_mouse_over(const Point2 &p_over) const;
virtual Size2 _get_contents_minimum_size() const override;
int _get_items_total_height() const;
void _scroll_to_item(int p_item);
+ void _shape_item(int p_item);
+
void _gui_input(const Ref<InputEvent> &p_event);
void _activate_submenu(int p_over);
void _submenu_timeout();
@@ -161,6 +175,11 @@ public:
void add_submenu_item(const String &p_label, const String &p_submenu, int p_id = -1);
void set_item_text(int p_idx, const String &p_text);
+
+ void set_item_text_direction(int p_idx, Control::TextDirection p_text_direction);
+ void set_item_opentype_feature(int p_idx, const String &p_name, int p_value);
+ void clear_item_opentype_features(int p_idx);
+ void set_item_language(int p_idx, const String &p_language);
void set_item_icon(int p_idx, const Ref<Texture2D> &p_icon);
void set_item_checked(int p_idx, bool p_checked);
void set_item_id(int p_idx, int p_id);
@@ -181,6 +200,9 @@ public:
void toggle_item_checked(int p_idx);
String get_item_text(int p_idx) const;
+ Control::TextDirection get_item_text_direction(int p_idx) const;
+ int get_item_opentype_feature(int p_idx, const String &p_name) const;
+ String get_item_language(int p_idx) const;
int get_item_idx_from_text(const String &text) const;
Ref<Texture2D> get_item_icon(int p_idx) const;
bool is_item_checked(int p_idx) const;
diff --git a/scene/gui/progress_bar.cpp b/scene/gui/progress_bar.cpp
index 9246f1723d..1344d010ae 100644
--- a/scene/gui/progress_bar.cpp
+++ b/scene/gui/progress_bar.cpp
@@ -29,17 +29,21 @@
/*************************************************************************/
#include "progress_bar.h"
+#include "scene/resources/text_line.h"
Size2 ProgressBar::get_minimum_size() const {
Ref<StyleBox> bg = get_theme_stylebox("bg");
Ref<StyleBox> fg = get_theme_stylebox("fg");
Ref<Font> font = get_theme_font("font");
+ int font_size = get_theme_font_size("font_size");
Size2 minimum_size = bg->get_minimum_size();
minimum_size.height = MAX(minimum_size.height, fg->get_minimum_size().height);
minimum_size.width = MAX(minimum_size.width, fg->get_minimum_size().width);
if (percent_visible) {
- minimum_size.height = MAX(minimum_size.height, bg->get_minimum_size().height + font->get_height());
+ String txt = "100%";
+ TextLine tl = TextLine(txt, font, font_size);
+ minimum_size.height = MAX(minimum_size.height, bg->get_minimum_size().height + tl.get_size().y);
} else { // this is needed, else the progressbar will collapse
minimum_size.width = MAX(minimum_size.width, 1);
minimum_size.height = MAX(minimum_size.height, 1);
@@ -52,6 +56,7 @@ void ProgressBar::_notification(int p_what) {
Ref<StyleBox> bg = get_theme_stylebox("bg");
Ref<StyleBox> fg = get_theme_stylebox("fg");
Ref<Font> font = get_theme_font("font");
+ int font_size = get_theme_font_size("font_size");
Color font_color = get_theme_color("font_color");
draw_style_box(bg, Rect2(Point2(), get_size()));
@@ -59,12 +64,17 @@ void ProgressBar::_notification(int p_what) {
int mp = fg->get_minimum_size().width;
int p = r * (get_size().width - mp);
if (p > 0) {
- draw_style_box(fg, Rect2(Point2(), Size2(p + fg->get_minimum_size().width, get_size().height)));
+ if (is_layout_rtl()) {
+ draw_style_box(fg, Rect2(Point2(p, 0), Size2(fg->get_minimum_size().width, get_size().height)));
+ } else {
+ draw_style_box(fg, Rect2(Point2(0, 0), Size2(p + fg->get_minimum_size().width, get_size().height)));
+ }
}
if (percent_visible) {
- String txt = itos(int(get_as_ratio() * 100)) + "%";
- font->draw_halign(get_canvas_item(), Point2(0, font->get_ascent() + (get_size().height - font->get_height()) / 2), HALIGN_CENTER, get_size().width, txt, font_color);
+ String txt = TS->format_number(itos(int(get_as_ratio() * 100))) + TS->percent_sign();
+ TextLine tl = TextLine(txt, font, font_size);
+ tl.draw(get_canvas_item(), Point2(get_size().width - tl.get_size().x, get_size().height - tl.get_size().y) / 2, font_color);
}
}
}
diff --git a/scene/gui/rich_text_label.cpp b/scene/gui/rich_text_label.cpp
index e8acac172c..08214b958e 100644
--- a/scene/gui/rich_text_label.cpp
+++ b/scene/gui/rich_text_label.cpp
@@ -393,7 +393,7 @@ int RichTextLabel::_process_line(ItemFrame *p_frame, const Vector2 &p_ofs, int &
}
rchar = 0;
- FontDrawer drawer(font, Color(1, 1, 1));
+ //FontDrawer drawer(font, Color(1, 1, 1));
while (*c) {
int end = 0;
int w = 0;
@@ -569,19 +569,19 @@ int RichTextLabel::_process_line(ItemFrame *p_frame, const Vector2 &p_ofs, int &
if (p_font_color_shadow.a > 0) {
float x_ofs_shadow = align_ofs + pofs;
float y_ofs_shadow = y + lh - line_descent;
- font->draw_char(ci, Point2(x_ofs_shadow, y_ofs_shadow) + shadow_ofs + fx_offset, fx_char, c[i + 1], p_font_color_shadow);
+ font->draw_char(ci, Point2(x_ofs_shadow, y_ofs_shadow) + shadow_ofs + fx_offset, fx_char, c[i + 1], -1, p_font_color_shadow);
if (p_shadow_as_outline) {
- font->draw_char(ci, Point2(x_ofs_shadow, y_ofs_shadow) + Vector2(-shadow_ofs.x, shadow_ofs.y) + fx_offset, fx_char, c[i + 1], p_font_color_shadow);
- font->draw_char(ci, Point2(x_ofs_shadow, y_ofs_shadow) + Vector2(shadow_ofs.x, -shadow_ofs.y) + fx_offset, fx_char, c[i + 1], p_font_color_shadow);
- font->draw_char(ci, Point2(x_ofs_shadow, y_ofs_shadow) + Vector2(-shadow_ofs.x, -shadow_ofs.y) + fx_offset, fx_char, c[i + 1], p_font_color_shadow);
+ font->draw_char(ci, Point2(x_ofs_shadow, y_ofs_shadow) + Vector2(-shadow_ofs.x, shadow_ofs.y) + fx_offset, fx_char, c[i + 1], -1, p_font_color_shadow);
+ font->draw_char(ci, Point2(x_ofs_shadow, y_ofs_shadow) + Vector2(shadow_ofs.x, -shadow_ofs.y) + fx_offset, fx_char, c[i + 1], -1, p_font_color_shadow);
+ font->draw_char(ci, Point2(x_ofs_shadow, y_ofs_shadow) + Vector2(-shadow_ofs.x, -shadow_ofs.y) + fx_offset, fx_char, c[i + 1], -1, p_font_color_shadow);
}
}
if (selected) {
- drawer.draw_char(ci, p_ofs + Point2(align_ofs + pofs, y + lh - line_descent), fx_char, c[i + 1], override_selected_font_color ? selection_fg : fx_color);
+ font->draw_char(ci, p_ofs + Point2(align_ofs + pofs, y + lh - line_descent), fx_char, c[i + 1], -1, override_selected_font_color ? selection_fg : fx_color);
} else {
- cw = drawer.draw_char(ci, p_ofs + Point2(align_ofs + pofs, y + lh - line_descent) + fx_offset, fx_char, c[i + 1], fx_color);
+ cw = font->draw_char(ci, p_ofs + Point2(align_ofs + pofs, y + lh - line_descent) + fx_offset, fx_char, c[i + 1], -1, fx_color);
}
} else if (previously_visible && c[i] != '\t') {
backtrack += font->get_char_size(fx_char, c[i + 1]).x;
diff --git a/scene/gui/scroll_bar.h b/scene/gui/scroll_bar.h
index 6ae76e453a..75f5ad1647 100644
--- a/scene/gui/scroll_bar.h
+++ b/scene/gui/scroll_bar.h
@@ -53,8 +53,8 @@ class ScrollBar : public Range {
struct Drag {
bool active = false;
- float pos_at_click;
- float value_at_click;
+ float pos_at_click = 0;
+ float value_at_click = 0;
} drag;
double get_grabber_size() const;
diff --git a/scene/gui/scroll_container.cpp b/scene/gui/scroll_container.cpp
index 8aad5f262d..62ccd55e89 100644
--- a/scene/gui/scroll_container.cpp
+++ b/scene/gui/scroll_container.cpp
@@ -213,6 +213,10 @@ void ScrollContainer::_gui_input(const Ref<InputEvent> &p_gui_input) {
}
void ScrollContainer::_update_scrollbar_position() {
+ if (!_updating_scrollbars) {
+ return;
+ }
+
Size2 hmin = h_scroll->get_combined_minimum_size();
Size2 vmin = v_scroll->get_combined_minimum_size();
@@ -228,6 +232,8 @@ void ScrollContainer::_update_scrollbar_position() {
h_scroll->raise();
v_scroll->raise();
+
+ _updating_scrollbars = false;
}
void ScrollContainer::_ensure_focused_visible(Control *p_control) {
@@ -249,13 +255,18 @@ void ScrollContainer::_ensure_focused_visible(Control *p_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));
- 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);
+ 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));
}
}
void ScrollContainer::_notification(int p_what) {
- if (p_what == NOTIFICATION_ENTER_TREE || p_what == NOTIFICATION_THEME_CHANGED) {
+ if (p_what == NOTIFICATION_ENTER_TREE || p_what == NOTIFICATION_THEME_CHANGED || p_what == NOTIFICATION_LAYOUT_DIRECTION_CHANGED || p_what == NOTIFICATION_TRANSLATION_CHANGED) {
+ _updating_scrollbars = true;
call_deferred("_update_scrollbar_position");
};
@@ -271,6 +282,7 @@ void ScrollContainer::_notification(int p_what) {
Ref<StyleBox> sb = get_theme_stylebox("bg");
size -= sb->get_minimum_size();
ofs += sb->get_offset();
+ bool rtl = is_layout_rtl();
if (h_scroll->is_visible_in_tree() && h_scroll->get_parent() == this) { //scrolls may have been moved out for reasons
size.y -= h_scroll->get_minimum_size().y;
@@ -313,6 +325,9 @@ void ScrollContainer::_notification(int p_what) {
}
}
r.position += ofs;
+ if (rtl && v_scroll->is_visible_in_tree() && v_scroll->get_parent() == this) {
+ r.position.x += v_scroll->get_minimum_size().x;
+ }
fit_child_in_rect(c, r);
}
diff --git a/scene/gui/scroll_container.h b/scene/gui/scroll_container.h
index b28d66ed53..4bf200009e 100644
--- a/scene/gui/scroll_container.h
+++ b/scene/gui/scroll_container.h
@@ -74,6 +74,7 @@ protected:
void _scroll_moved(float);
static void _bind_methods();
+ bool _updating_scrollbars = false;
void _update_scrollbar_position();
void _ensure_focused_visible(Control *p_node);
diff --git a/scene/gui/spin_box.cpp b/scene/gui/spin_box.cpp
index ae2f99e91d..46b24efed5 100644
--- a/scene/gui/spin_box.cpp
+++ b/scene/gui/spin_box.cpp
@@ -40,7 +40,7 @@ Size2 SpinBox::get_minimum_size() const {
}
void SpinBox::_value_changed(double) {
- String value = String::num(get_value(), Math::range_step_decimals(get_step()));
+ String value = TS->format_number(String::num(get_value(), Math::range_step_decimals(get_step())));
if (prefix != "") {
value = prefix + " " + value;
}
@@ -53,8 +53,10 @@ void SpinBox::_value_changed(double) {
void SpinBox::_text_entered(const String &p_string) {
Ref<Expression> expr;
expr.instance();
+
+ String num = TS->parse_number(p_string);
// Ignore the prefix and suffix in the expression
- Error err = expr->parse(p_string.trim_prefix(prefix + " ").trim_suffix(" " + suffix));
+ Error err = expr->parse(num.trim_prefix(prefix + " ").trim_suffix(" " + suffix));
if (err != OK) {
return;
}
@@ -170,7 +172,8 @@ void SpinBox::_line_edit_focus_exit() {
inline void SpinBox::_adjust_width_for_icon(const Ref<Texture2D> &icon) {
int w = icon->get_width();
- if (w != last_w) {
+ if ((w != last_w)) {
+ line_edit->set_margin(MARGIN_LEFT, 0);
line_edit->set_margin(MARGIN_RIGHT, -w);
last_w = w;
}
@@ -185,16 +188,24 @@ void SpinBox::_notification(int p_what) {
RID ci = get_canvas_item();
Size2i size = get_size();
- updown->draw(ci, Point2i(size.width - updown->get_width(), (size.height - updown->get_height()) / 2));
+ if (is_layout_rtl()) {
+ updown->draw(ci, Point2i(0, (size.height - updown->get_height()) / 2));
+ } else {
+ updown->draw(ci, Point2i(size.width - updown->get_width(), (size.height - updown->get_height()) / 2));
+ }
} else if (p_what == NOTIFICATION_FOCUS_EXIT) {
//_value_changed(0);
} else if (p_what == NOTIFICATION_ENTER_TREE) {
_adjust_width_for_icon(get_theme_icon("updown"));
_value_changed(0);
+ } else if (p_what == NOTIFICATION_TRANSLATION_CHANGED) {
+ _value_changed(0);
} else if (p_what == NOTIFICATION_THEME_CHANGED) {
call_deferred("minimum_size_changed");
get_line_edit()->call_deferred("minimum_size_changed");
+ } else if (p_what == NOTIFICATION_LAYOUT_DIRECTION_CHANGED || p_what == NOTIFICATION_TRANSLATION_CHANGED) {
+ update();
}
}
@@ -263,6 +274,8 @@ SpinBox::SpinBox() {
line_edit->set_anchors_and_margins_preset(Control::PRESET_WIDE);
line_edit->set_mouse_filter(MOUSE_FILTER_PASS);
+ 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("focus_exited", callable_mp(this, &SpinBox::_line_edit_focus_exit), Vector<Variant>(), CONNECT_DEFERRED);
diff --git a/scene/gui/split_container.cpp b/scene/gui/split_container.cpp
index 6508be1e43..1e85bba0e3 100644
--- a/scene/gui/split_container.cpp
+++ b/scene/gui/split_container.cpp
@@ -112,9 +112,16 @@ void SplitContainer::_resort() {
int sofs = middle_sep + sep;
fit_child_in_rect(second, Rect2(Point2(0, sofs), Size2(get_size().width, get_size().height - sofs)));
} else {
- fit_child_in_rect(first, Rect2(Point2(0, 0), Size2(middle_sep, get_size().height)));
- int sofs = middle_sep + sep;
- fit_child_in_rect(second, Rect2(Point2(sofs, 0), Size2(get_size().width - sofs, get_size().height)));
+ if (is_layout_rtl()) {
+ middle_sep = get_size().width - middle_sep - sep;
+ fit_child_in_rect(second, Rect2(Point2(0, 0), Size2(middle_sep, get_size().height)));
+ int sofs = middle_sep + sep;
+ fit_child_in_rect(first, Rect2(Point2(sofs, 0), Size2(get_size().width - sofs, get_size().height)));
+ } else {
+ fit_child_in_rect(first, Rect2(Point2(0, 0), Size2(middle_sep, get_size().height)));
+ int sofs = middle_sep + sep;
+ fit_child_in_rect(second, Rect2(Point2(sofs, 0), Size2(get_size().width - sofs, get_size().height)));
+ }
}
update();
@@ -157,6 +164,10 @@ Size2 SplitContainer::get_minimum_size() const {
void SplitContainer::_notification(int p_what) {
switch (p_what) {
+ case NOTIFICATION_TRANSLATION_CHANGED:
+ case NOTIFICATION_LAYOUT_DIRECTION_CHANGED: {
+ queue_sort();
+ } break;
case NOTIFICATION_SORT_CHILDREN: {
_resort();
} break;
@@ -247,7 +258,11 @@ void SplitContainer::_gui_input(const Ref<InputEvent> &p_event) {
return;
}
- split_offset = drag_ofs + ((vertical ? mm->get_position().y : mm->get_position().x) - drag_from);
+ if (!vertical && is_layout_rtl()) {
+ split_offset = drag_ofs + (drag_from - (vertical ? mm->get_position().y : mm->get_position().x));
+ } else {
+ split_offset = drag_ofs + ((vertical ? mm->get_position().y : mm->get_position().x) - drag_from);
+ }
should_clamp_split_offset = true;
queue_sort();
emit_signal("dragged", get_split_offset());
diff --git a/scene/gui/tab_container.cpp b/scene/gui/tab_container.cpp
index d92f41af2d..d38af68935 100644
--- a/scene/gui/tab_container.cpp
+++ b/scene/gui/tab_container.cpp
@@ -31,6 +31,8 @@
#include "tab_container.h"
#include "core/object/message_queue.h"
+#include "core/string/translation.h"
+
#include "scene/gui/box_container.h"
#include "scene/gui/label.h"
#include "scene/gui/texture_rect.h"
@@ -48,11 +50,12 @@ int TabContainer::_get_top_margin() const {
int tab_height = MAX(MAX(tab_bg->get_minimum_size().height, tab_fg->get_minimum_size().height), tab_disabled->get_minimum_size().height);
// Font height or higher icon wins.
- Ref<Font> font = get_theme_font("font");
- int content_height = font->get_height();
+ int content_height = 0;
Vector<Control *> tabs = _get_tabs();
for (int i = 0; i < tabs.size(); i++) {
+ content_height = MAX(content_height, text_buf[i]->get_size().y);
+
Control *c = tabs[i];
if (!c->has_meta("_tab_icon")) {
continue;
@@ -78,23 +81,36 @@ void TabContainer::_gui_input(const Ref<InputEvent> &p_event) {
Size2 size = get_size();
// Click must be on tabs in the tab header area.
- if (pos.x < tabs_ofs_cache || pos.y > _get_top_margin()) {
+ if (pos.y > _get_top_margin()) {
return;
}
// Handle menu button.
Ref<Texture2D> menu = get_theme_icon("menu");
- if (popup && pos.x > size.width - menu->get_width()) {
- emit_signal("pre_popup_pressed");
+ if (is_layout_rtl()) {
+ if (popup && pos.x < menu->get_width()) {
+ emit_signal("pre_popup_pressed");
- Vector2 popup_pos = get_screen_position();
- popup_pos.x += size.width - popup->get_size().width;
- popup_pos.y += menu->get_height();
+ Vector2 popup_pos = get_screen_position();
+ popup_pos.y += menu->get_height();
- popup->set_position(popup_pos);
- popup->popup();
- return;
+ popup->set_position(popup_pos);
+ popup->popup();
+ return;
+ }
+ } else {
+ if (popup && pos.x > size.width - menu->get_width()) {
+ emit_signal("pre_popup_pressed");
+
+ Vector2 popup_pos = get_screen_position();
+ popup_pos.x += size.width - popup->get_size().width;
+ popup_pos.y += menu->get_height();
+
+ popup->set_position(popup_pos);
+ popup->popup();
+ return;
+ }
}
// Do not activate tabs when tabs is empty.
@@ -113,22 +129,46 @@ void TabContainer::_gui_input(const Ref<InputEvent> &p_event) {
Ref<Texture2D> increment = get_theme_icon("increment");
Ref<Texture2D> decrement = get_theme_icon("decrement");
- if (pos.x > size.width - increment->get_width() - popup_ofs) {
- if (last_tab_cache < tabs.size() - 1) {
- first_tab_cache += 1;
- update();
+ if (is_layout_rtl()) {
+ if (pos.x < popup_ofs + decrement->get_width()) {
+ if (last_tab_cache < tabs.size() - 1) {
+ first_tab_cache += 1;
+ update();
+ }
+ return;
+ } else if (pos.x < popup_ofs + increment->get_width() + decrement->get_width()) {
+ if (first_tab_cache > 0) {
+ first_tab_cache -= 1;
+ update();
+ }
+ return;
}
- return;
- } else if (pos.x > size.width - increment->get_width() - decrement->get_width() - popup_ofs) {
- if (first_tab_cache > 0) {
- first_tab_cache -= 1;
- update();
+ } else {
+ if (pos.x > size.width - increment->get_width() - popup_ofs && pos.x) {
+ if (last_tab_cache < tabs.size() - 1) {
+ first_tab_cache += 1;
+ update();
+ }
+ return;
+ } else if (pos.x > size.width - increment->get_width() - decrement->get_width() - popup_ofs) {
+ if (first_tab_cache > 0) {
+ first_tab_cache -= 1;
+ update();
+ }
+ return;
}
- return;
}
}
// Activate the clicked tab.
+ if (is_layout_rtl()) {
+ pos.x = size.width - pos.x;
+ }
+
+ if (pos.x < tabs_ofs_cache) {
+ return;
+ }
+
pos.x -= tabs_ofs_cache;
for (int i = first_tab_cache; i <= last_tab_cache; i++) {
if (get_tab_hidden(i)) {
@@ -152,7 +192,7 @@ void TabContainer::_gui_input(const Ref<InputEvent> &p_event) {
Size2 size = get_size();
// Mouse must be on tabs in the tab header area.
- if (pos.x < tabs_ofs_cache || pos.y > _get_top_margin()) {
+ if (pos.y > _get_top_margin()) {
if (menu_hovered || highlight_arrow > -1) {
menu_hovered = false;
highlight_arrow = -1;
@@ -163,16 +203,30 @@ void TabContainer::_gui_input(const Ref<InputEvent> &p_event) {
Ref<Texture2D> menu = get_theme_icon("menu");
if (popup) {
- if (pos.x >= size.width - menu->get_width()) {
- if (!menu_hovered) {
- menu_hovered = true;
- highlight_arrow = -1;
+ if (is_layout_rtl()) {
+ if (pos.x <= menu->get_width()) {
+ if (!menu_hovered) {
+ menu_hovered = true;
+ highlight_arrow = -1;
+ update();
+ return;
+ }
+ } else if (menu_hovered) {
+ menu_hovered = false;
+ update();
+ }
+ } else {
+ if (pos.x >= size.width - menu->get_width()) {
+ if (!menu_hovered) {
+ menu_hovered = true;
+ highlight_arrow = -1;
+ update();
+ return;
+ }
+ } else if (menu_hovered) {
+ menu_hovered = false;
update();
- return;
}
- } else if (menu_hovered) {
- menu_hovered = false;
- update();
}
if (menu_hovered) {
@@ -194,29 +248,43 @@ void TabContainer::_gui_input(const Ref<InputEvent> &p_event) {
Ref<Texture2D> increment = get_theme_icon("increment");
Ref<Texture2D> decrement = get_theme_icon("decrement");
- if (pos.x >= size.width - increment->get_width() - popup_ofs) {
- if (highlight_arrow != 1) {
- highlight_arrow = 1;
+
+ if (is_layout_rtl()) {
+ if (pos.x <= popup_ofs + decrement->get_width()) {
+ if (highlight_arrow != 1) {
+ highlight_arrow = 1;
+ update();
+ }
+ } else if (pos.x <= popup_ofs + increment->get_width() + decrement->get_width()) {
+ if (highlight_arrow != 0) {
+ highlight_arrow = 0;
+ update();
+ }
+ } else if (highlight_arrow > -1) {
+ highlight_arrow = -1;
update();
}
- } else if (pos.x >= size.width - increment->get_width() - decrement->get_width() - popup_ofs) {
- if (highlight_arrow != 0) {
- highlight_arrow = 0;
+ } else {
+ if (pos.x >= size.width - increment->get_width() - popup_ofs) {
+ if (highlight_arrow != 1) {
+ highlight_arrow = 1;
+ update();
+ }
+ } else if (pos.x >= size.width - increment->get_width() - decrement->get_width() - popup_ofs) {
+ if (highlight_arrow != 0) {
+ highlight_arrow = 0;
+ update();
+ }
+ } else if (highlight_arrow > -1) {
+ highlight_arrow = -1;
update();
}
- } else if (highlight_arrow > -1) {
- highlight_arrow = -1;
- update();
}
}
}
void TabContainer::_notification(int p_what) {
switch (p_what) {
- case NOTIFICATION_TRANSLATION_CHANGED: {
- minimum_size_changed();
- update();
- } break;
case NOTIFICATION_RESIZED: {
Vector<Control *> tabs = _get_tabs();
int side_margin = get_theme_constant("side_margin");
@@ -259,6 +327,7 @@ void TabContainer::_notification(int p_what) {
case NOTIFICATION_DRAW: {
RID canvas = get_canvas_item();
Size2 size = get_size();
+ bool rtl = is_layout_rtl();
// Draw only the tab area if the header is hidden.
Ref<StyleBox> panel = get_theme_stylebox("panel");
@@ -277,7 +346,6 @@ void TabContainer::_notification(int p_what) {
Ref<Texture2D> decrement_hl = get_theme_icon("decrement_highlight");
Ref<Texture2D> menu = get_theme_icon("menu");
Ref<Texture2D> menu_hl = get_theme_icon("menu_highlight");
- Ref<Font> font = get_theme_font("font");
Color font_color_fg = get_theme_color("font_color_fg");
Color font_color_bg = get_theme_color("font_color_bg");
Color font_color_disabled = get_theme_color("font_color_disabled");
@@ -357,11 +425,19 @@ void TabContainer::_notification(int p_what) {
int tab_width = tab_widths[i];
if (get_tab_disabled(i + first_tab_cache)) {
- _draw_tab(tab_disabled, font_color_disabled, i, tabs_ofs_cache + x);
+ if (rtl) {
+ _draw_tab(tab_disabled, font_color_disabled, i, size.width - (tabs_ofs_cache + x) - tab_width);
+ } else {
+ _draw_tab(tab_disabled, font_color_disabled, i, tabs_ofs_cache + x);
+ }
} else if (i + first_tab_cache == current) {
x_current = x;
} else {
- _draw_tab(tab_bg, font_color_bg, i, tabs_ofs_cache + x);
+ if (rtl) {
+ _draw_tab(tab_bg, font_color_bg, i, size.width - (tabs_ofs_cache + x) - tab_width);
+ } else {
+ _draw_tab(tab_bg, font_color_bg, i, tabs_ofs_cache + x);
+ }
}
x += tab_width;
@@ -371,41 +447,76 @@ void TabContainer::_notification(int p_what) {
// Draw the tab area.
panel->draw(canvas, Rect2(0, header_height, size.width, size.height - header_height));
- // Draw selected tab in front. Need to check tabs.size() in case of no contents at all.
+ // Draw selected tab in front
if (tabs.size() > 0) {
- _draw_tab(tab_fg, font_color_fg, current, tabs_ofs_cache + x_current);
+ if (rtl) {
+ _draw_tab(tab_fg, font_color_fg, current, size.width - (tabs_ofs_cache + x_current) - tab_widths[current]);
+ } else {
+ _draw_tab(tab_fg, font_color_fg, current, tabs_ofs_cache + x_current);
+ }
}
// Draw the popup menu.
- x = get_size().width;
+ if (rtl) {
+ x = 0;
+ } else {
+ x = get_size().width;
+ }
if (popup) {
- x -= menu->get_width();
+ if (!rtl) {
+ x -= menu->get_width();
+ }
if (menu_hovered) {
menu_hl->draw(get_canvas_item(), Size2(x, (header_height - menu_hl->get_height()) / 2));
} else {
menu->draw(get_canvas_item(), Size2(x, (header_height - menu->get_height()) / 2));
}
+ if (rtl) {
+ x += menu->get_width();
+ }
}
// Draw the navigation buttons.
if (buttons_visible_cache) {
- x -= increment->get_width();
- if (last_tab_cache < tabs.size() - 1) {
- draw_texture(highlight_arrow == 1 ? increment_hl : increment, Point2(x, (header_height - increment->get_height()) / 2));
+ if (rtl) {
+ if (last_tab_cache < tabs.size() - 1) {
+ draw_texture(highlight_arrow == 1 ? decrement_hl : decrement, Point2(x, (header_height - increment->get_height()) / 2));
+ } else {
+ draw_texture(decrement, Point2(x, (header_height - increment->get_height()) / 2), Color(1, 1, 1, 0.5));
+ }
+ x += increment->get_width();
+
+ if (first_tab_cache > 0) {
+ draw_texture(highlight_arrow == 0 ? increment_hl : increment, Point2(x, (header_height - decrement->get_height()) / 2));
+ } else {
+ draw_texture(increment, Point2(x, (header_height - decrement->get_height()) / 2), Color(1, 1, 1, 0.5));
+ }
+ x += decrement->get_width();
} else {
- draw_texture(increment, Point2(x, (header_height - increment->get_height()) / 2), Color(1, 1, 1, 0.5));
- }
-
- x -= decrement->get_width();
- if (first_tab_cache > 0) {
- draw_texture(highlight_arrow == 0 ? decrement_hl : decrement, Point2(x, (header_height - decrement->get_height()) / 2));
- } else {
- draw_texture(decrement, Point2(x, (header_height - decrement->get_height()) / 2), Color(1, 1, 1, 0.5));
+ x -= increment->get_width();
+ if (last_tab_cache < tabs.size() - 1) {
+ draw_texture(highlight_arrow == 1 ? increment_hl : increment, Point2(x, (header_height - increment->get_height()) / 2));
+ } else {
+ draw_texture(increment, Point2(x, (header_height - increment->get_height()) / 2), Color(1, 1, 1, 0.5));
+ }
+
+ x -= decrement->get_width();
+ if (first_tab_cache > 0) {
+ draw_texture(highlight_arrow == 0 ? decrement_hl : decrement, Point2(x, (header_height - decrement->get_height()) / 2));
+ } else {
+ draw_texture(decrement, Point2(x, (header_height - decrement->get_height()) / 2), Color(1, 1, 1, 0.5));
+ }
}
}
} break;
+ case NOTIFICATION_TRANSLATION_CHANGED:
+ case NOTIFICATION_LAYOUT_DIRECTION_CHANGED:
case NOTIFICATION_THEME_CHANGED: {
- minimum_size_changed();
+ Vector<Control *> tabs = _get_tabs();
+ for (int i = 0; i < tabs.size(); i++) {
+ text_buf.write[i]->clear();
+ }
+ _theme_changing = true;
call_deferred("_on_theme_changed"); // Wait until all changed theme.
} break;
}
@@ -444,15 +555,36 @@ void TabContainer::_draw_tab(Ref<StyleBox> &p_tab_style, Color &p_font_color, in
}
// Draw the tab text.
- Point2i text_pos(x_content, y_center - (font->get_height() / 2) + font->get_ascent());
- font->draw(canvas, text_pos, text, p_font_color);
+ Point2i text_pos(x_content, y_center - text_buf[p_index + first_tab_cache]->get_size().y / 2);
+ text_buf[p_index + first_tab_cache]->draw(canvas, text_pos, p_font_color);
}
void TabContainer::_on_theme_changed() {
+ if (!_theme_changing) {
+ return;
+ }
+
+ text_buf.clear();
+ bool rtl = is_layout_rtl();
+ Ref<Font> font = get_theme_font("font");
+ int font_size = get_theme_font_size("font_size");
+ Vector<Control *> tabs = _get_tabs();
+ for (int i = 0; i < tabs.size(); i++) {
+ 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->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);
+ }
+
+ minimum_size_changed();
if (get_tab_count() > 0) {
_repaint();
update();
}
+ _theme_changing = false;
}
void TabContainer::_repaint() {
@@ -494,8 +626,9 @@ int TabContainer::_get_tab_width(int p_index) const {
// Get the width of the text displayed on the tab.
Ref<Font> font = get_theme_font("font");
+ int font_size = get_theme_font_size("font_size");
String text = control->has_meta("_tab_name") ? String(tr(String(control->get_meta("_tab_name")))) : String(control->get_name());
- int width = font->get_string_size(text).width;
+ int width = font->get_string_size(text, font_size).width;
// Add space for a tab icon.
if (control->has_meta("_tab_icon")) {
@@ -537,6 +670,21 @@ Vector<Control *> TabContainer::_get_tabs() const {
}
void TabContainer::_child_renamed_callback() {
+ text_buf.clear();
+ Vector<Control *> tabs = _get_tabs();
+ bool rtl = is_layout_rtl();
+ Ref<Font> font = get_theme_font("font");
+ int font_size = get_theme_font_size("font_size");
+ for (int i = 0; i < tabs.size(); i++) {
+ 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->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);
+ }
+
update();
}
@@ -551,9 +699,24 @@ void TabContainer::add_child_notify(Node *p_child) {
return;
}
+ text_buf.clear();
+ Vector<Control *> tabs = _get_tabs();
+ bool rtl = is_layout_rtl();
+ Ref<Font> font = get_theme_font("font");
+ int font_size = get_theme_font_size("font_size");
+ for (int i = 0; i < tabs.size(); i++) {
+ 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->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);
+ }
+
bool first = false;
- if (get_tab_count() != 1) {
+ if (tabs.size() != 1) {
c->hide();
} else {
c->show();
@@ -641,7 +804,22 @@ void TabContainer::remove_child_notify(Node *p_child) {
}
void TabContainer::_update_current_tab() {
- int tc = get_tab_count();
+ text_buf.clear();
+ Vector<Control *> tabs = _get_tabs();
+ bool rtl = is_layout_rtl();
+ Ref<Font> font = get_theme_font("font");
+ int font_size = get_theme_font_size("font_size");
+ for (int i = 0; i < tabs.size(); i++) {
+ 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->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);
+ }
+
+ int tc = tabs.size();
if (current >= tc) {
current = tc - 1;
}
@@ -757,30 +935,38 @@ int TabContainer::get_tab_idx_at_point(const Point2 &p_point) const {
}
// must be on tabs in the tab header area.
- if (p_point.x < tabs_ofs_cache || p_point.y > _get_top_margin()) {
+ if (p_point.y > _get_top_margin()) {
return -1;
}
Size2 size = get_size();
- int right_ofs = 0;
+ int button_ofs = 0;
+ int px = p_point.x;
+
+ if (is_layout_rtl()) {
+ px = size.width - px;
+ }
+
+ if (px < tabs_ofs_cache) {
+ return -1;
+ }
Popup *popup = get_popup();
if (popup) {
Ref<Texture2D> menu = get_theme_icon("menu");
- right_ofs += menu->get_width();
+ button_ofs += menu->get_width();
}
if (buttons_visible_cache) {
Ref<Texture2D> increment = get_theme_icon("increment");
Ref<Texture2D> decrement = get_theme_icon("decrement");
- right_ofs += increment->get_width() + decrement->get_width();
+ button_ofs += increment->get_width() + decrement->get_width();
}
- if (p_point.x > size.width - right_ofs) {
+ if (px > size.width - button_ofs) {
return -1;
}
// get the tab at the point
Vector<Control *> tabs = _get_tabs();
- int px = p_point.x;
px -= tabs_ofs_cache;
for (int i = first_tab_cache; i <= last_tab_cache; i++) {
int tab_width = _get_tab_width(i);
@@ -953,7 +1139,7 @@ Size2 TabContainer::get_minimum_size() const {
if (tabs_visible) {
ms.y += MAX(MAX(tab_bg->get_minimum_size().y, tab_fg->get_minimum_size().y), tab_disabled->get_minimum_size().y);
- ms.y += font->get_height();
+ ms.y += _get_top_margin();
}
Ref<StyleBox> sb = get_theme_stylebox("panel");
diff --git a/scene/gui/tab_container.h b/scene/gui/tab_container.h
index f82f594875..2fef606551 100644
--- a/scene/gui/tab_container.h
+++ b/scene/gui/tab_container.h
@@ -33,6 +33,8 @@
#include "scene/gui/container.h"
#include "scene/gui/popup.h"
+#include "scene/resources/text_line.h"
+
class TabContainer : public Container {
GDCLASS(TabContainer, Container);
@@ -61,8 +63,10 @@ private:
bool use_hidden_tabs_for_min_size;
int tabs_rearrange_group;
+ Vector<Ref<TextLine>> text_buf;
Vector<Control *> _get_tabs() const;
int _get_tab_width(int p_index) const;
+ bool _theme_changing = false;
void _on_theme_changed();
void _repaint();
void _on_mouse_exited();
diff --git a/scene/gui/tabs.cpp b/scene/gui/tabs.cpp
index eefe8cc3bc..06e55deacb 100644
--- a/scene/gui/tabs.cpp
+++ b/scene/gui/tabs.cpp
@@ -31,6 +31,8 @@
#include "tabs.h"
#include "core/object/message_queue.h"
+#include "core/string/translation.h"
+
#include "scene/gui/box_container.h"
#include "scene/gui/label.h"
#include "scene/gui/texture_rect.h"
@@ -39,9 +41,10 @@ Size2 Tabs::get_minimum_size() const {
Ref<StyleBox> tab_bg = get_theme_stylebox("tab_bg");
Ref<StyleBox> tab_fg = get_theme_stylebox("tab_fg");
Ref<StyleBox> tab_disabled = get_theme_stylebox("tab_disabled");
- Ref<Font> font = get_theme_font("font");
- Size2 ms(0, MAX(MAX(tab_bg->get_minimum_size().height, tab_fg->get_minimum_size().height), tab_disabled->get_minimum_size().height) + font->get_height());
+ int y_margin = MAX(MAX(tab_bg->get_minimum_size().height, tab_fg->get_minimum_size().height), tab_disabled->get_minimum_size().height);
+
+ Size2 ms(0, 0);
for (int i = 0; i < tabs.size(); i++) {
Ref<Texture2D> tex = tabs[i].icon;
@@ -52,7 +55,8 @@ Size2 Tabs::get_minimum_size() const {
}
}
- ms.width += Math::ceil(font->get_string_size(tabs[i].xl_text).width);
+ ms.width += Math::ceil(tabs[i].text_buf->get_size().x);
+ ms.height = MAX(ms.height, tabs[i].text_buf->get_size().y + y_margin);
if (tabs[i].disabled) {
ms.width += tab_disabled->get_minimum_size().width;
@@ -94,12 +98,19 @@ void Tabs::_gui_input(const Ref<InputEvent> &p_event) {
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();
-
- if (pos.x > limit + decr->get_width()) {
- highlight_arrow = 1;
- } else if (pos.x > limit) {
- highlight_arrow = 0;
+ if (is_layout_rtl()) {
+ if (pos.x < decr->get_width()) {
+ highlight_arrow = 1;
+ } else if (pos.x < incr->get_width() + decr->get_width()) {
+ highlight_arrow = 0;
+ }
+ } else {
+ int limit = get_size().width - incr->get_width() - decr->get_width();
+ if (pos.x > limit + decr->get_width()) {
+ highlight_arrow = 1;
+ } else if (pos.x > limit) {
+ highlight_arrow = 0;
+ }
}
}
@@ -157,20 +168,35 @@ void Tabs::_gui_input(const Ref<InputEvent> &p_event) {
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();
-
- if (pos.x > limit + decr->get_width()) {
- if (missing_right) {
- offset++;
- update();
+ if (is_layout_rtl()) {
+ if (pos.x < decr->get_width()) {
+ if (missing_right) {
+ offset++;
+ update();
+ }
+ return;
+ } else if (pos.x < incr->get_width() + decr->get_width()) {
+ if (offset > 0) {
+ offset--;
+ update();
+ }
+ return;
}
- return;
- } else if (pos.x > limit) {
- if (offset > 0) {
- offset--;
- update();
+ } else {
+ int limit = get_size().width - incr->get_width() - decr->get_width();
+ if (pos.x > limit + decr->get_width()) {
+ if (missing_right) {
+ offset++;
+ update();
+ }
+ return;
+ } else if (pos.x > limit) {
+ if (offset > 0) {
+ offset--;
+ update();
+ }
+ return;
}
- return;
}
}
@@ -188,7 +214,7 @@ void Tabs::_gui_input(const Ref<InputEvent> &p_event) {
return;
}
- if (pos.x >= tabs[i].ofs_cache && pos.x < tabs[i].ofs_cache + tabs[i].size_cache) {
+ if (pos.x >= get_tab_rect(i).position.x && pos.x < get_tab_rect(i).position.x + tabs[i].size_cache) {
if (!tabs[i].disabled) {
found = i;
}
@@ -204,12 +230,32 @@ void Tabs::_gui_input(const Ref<InputEvent> &p_event) {
}
}
+void Tabs::_shape(int p_tab) {
+ Ref<Font> font = get_theme_font("font");
+ int font_size = get_theme_font_size("font_size");
+
+ tabs.write[p_tab].xl_text = tr(tabs[p_tab].text);
+ tabs.write[p_tab].text_buf->clear();
+ if (tabs[p_tab].text_direction == Control::TEXT_DIRECTION_INHERITED) {
+ tabs.write[p_tab].text_buf->set_direction(is_layout_rtl() ? TextServer::DIRECTION_RTL : TextServer::DIRECTION_LTR);
+ } else {
+ tabs.write[p_tab].text_buf->set_direction((TextServer::Direction)tabs[p_tab].text_direction);
+ }
+
+ tabs.write[p_tab].text_buf->add_string(tabs.write[p_tab].xl_text, font, font_size, tabs[p_tab].opentype_features, (tabs[p_tab].language != "") ? tabs[p_tab].language : TranslationServer::get_singleton()->get_tool_locale());
+}
+
void Tabs::_notification(int p_what) {
switch (p_what) {
+ case NOTIFICATION_LAYOUT_DIRECTION_CHANGED: {
+ _update_cache();
+ update();
+ } break;
case NOTIFICATION_TRANSLATION_CHANGED: {
for (int i = 0; i < tabs.size(); ++i) {
- tabs.write[i].xl_text = tr(tabs[i].text);
+ _shape(i);
}
+ _update_cache();
minimum_size_changed();
update();
} break;
@@ -225,11 +271,12 @@ void Tabs::_notification(int p_what) {
Ref<StyleBox> tab_bg = get_theme_stylebox("tab_bg");
Ref<StyleBox> tab_fg = get_theme_stylebox("tab_fg");
Ref<StyleBox> tab_disabled = get_theme_stylebox("tab_disabled");
- Ref<Font> font = get_theme_font("font");
Color color_fg = get_theme_color("font_color_fg");
Color color_bg = get_theme_color("font_color_bg");
Color color_disabled = get_theme_color("font_color_disabled");
Ref<Texture2D> close = get_theme_icon("close");
+ Vector2 size = get_size();
+ bool rtl = is_layout_rtl();
int h = get_size().height;
int w = 0;
@@ -286,7 +333,12 @@ void Tabs::_notification(int p_what) {
max_drawn_tab = i;
}
- Rect2 sb_rect = Rect2(w, 0, tabs[i].size_cache, h);
+ Rect2 sb_rect;
+ if (rtl) {
+ sb_rect = Rect2(size.width - w - tabs[i].size_cache, 0, tabs[i].size_cache, h);
+ } else {
+ sb_rect = Rect2(w, 0, tabs[i].size_cache, h);
+ }
sb->draw(ci, sb_rect);
w += sb->get_margin(MARGIN_LEFT);
@@ -294,13 +346,21 @@ void Tabs::_notification(int p_what) {
Size2i sb_ms = sb->get_minimum_size();
Ref<Texture2D> icon = tabs[i].icon;
if (icon.is_valid()) {
- icon->draw(ci, Point2i(w, sb->get_margin(MARGIN_TOP) + ((sb_rect.size.y - sb_ms.y) - icon->get_height()) / 2));
+ if (rtl) {
+ icon->draw(ci, Point2i(size.width - w - icon->get_width(), sb->get_margin(MARGIN_TOP) + ((sb_rect.size.y - sb_ms.y) - icon->get_height()) / 2));
+ } else {
+ icon->draw(ci, Point2i(w, sb->get_margin(MARGIN_TOP) + ((sb_rect.size.y - sb_ms.y) - icon->get_height()) / 2));
+ }
if (tabs[i].text != "") {
w += icon->get_width() + get_theme_constant("hseparation");
}
}
- font->draw(ci, Point2i(w, sb->get_margin(MARGIN_TOP) + ((sb_rect.size.y - sb_ms.y) - font->get_height()) / 2 + font->get_ascent()), tabs[i].xl_text, col, tabs[i].size_text);
+ if (rtl) {
+ tabs[i].text_buf->draw(ci, Point2i(size.width - w - tabs[i].text_buf->get_size().x, sb->get_margin(MARGIN_TOP) + ((sb_rect.size.y - sb_ms.y) - tabs[i].text_buf->get_size().y) / 2), col);
+ } else {
+ tabs[i].text_buf->draw(ci, Point2i(w, sb->get_margin(MARGIN_TOP) + ((sb_rect.size.y - sb_ms.y) - tabs[i].text_buf->get_size().y) / 2), col);
+ }
w += tabs[i].size_text;
@@ -312,7 +372,11 @@ void Tabs::_notification(int p_what) {
Rect2 rb_rect;
rb_rect.size = style->get_minimum_size() + rb->get_size();
- rb_rect.position.x = w;
+ if (rtl) {
+ rb_rect.position.x = size.width - w - rb_rect.size.x;
+ } else {
+ rb_rect.position.x = w;
+ }
rb_rect.position.y = sb->get_margin(MARGIN_TOP) + ((sb_rect.size.y - sb_ms.y) - (rb_rect.size.y)) / 2;
if (rb_hover == i) {
@@ -323,7 +387,11 @@ void Tabs::_notification(int p_what) {
}
}
- rb->draw(ci, Point2i(w + style->get_margin(MARGIN_LEFT), rb_rect.position.y + style->get_margin(MARGIN_TOP)));
+ if (rtl) {
+ rb->draw(ci, Point2i(size.width - w - rb_rect.size.x + style->get_margin(MARGIN_LEFT), rb_rect.position.y + style->get_margin(MARGIN_TOP)));
+ } else {
+ rb->draw(ci, Point2i(w + style->get_margin(MARGIN_LEFT), rb_rect.position.y + style->get_margin(MARGIN_TOP)));
+ }
w += rb->get_width();
tabs.write[i].rb_rect = rb_rect;
}
@@ -336,7 +404,11 @@ void Tabs::_notification(int p_what) {
Rect2 cb_rect;
cb_rect.size = style->get_minimum_size() + cb->get_size();
- cb_rect.position.x = w;
+ if (rtl) {
+ cb_rect.position.x = size.width - w - cb_rect.size.x;
+ } else {
+ cb_rect.position.x = w;
+ }
cb_rect.position.y = sb->get_margin(MARGIN_TOP) + ((sb_rect.size.y - sb_ms.y) - (cb_rect.size.y)) / 2;
if (!tabs[i].disabled && cb_hover == i) {
@@ -347,7 +419,11 @@ void Tabs::_notification(int p_what) {
}
}
- cb->draw(ci, Point2i(w + style->get_margin(MARGIN_LEFT), cb_rect.position.y + style->get_margin(MARGIN_TOP)));
+ if (rtl) {
+ cb->draw(ci, Point2i(size.width - w - cb_rect.size.x + style->get_margin(MARGIN_LEFT), cb_rect.position.y + style->get_margin(MARGIN_TOP)));
+ } else {
+ cb->draw(ci, Point2i(w + style->get_margin(MARGIN_LEFT), cb_rect.position.y + style->get_margin(MARGIN_TOP)));
+ }
w += cb->get_width();
tabs.write[i].cb_rect = cb_rect;
}
@@ -358,16 +434,30 @@ void Tabs::_notification(int p_what) {
if (offset > 0 || missing_right) {
int vofs = (get_size().height - incr->get_size().height) / 2;
- if (offset > 0) {
- draw_texture(highlight_arrow == 0 ? decr_hl : decr, Point2(limit, vofs));
- } else {
- draw_texture(decr, Point2(limit, vofs), Color(1, 1, 1, 0.5));
- }
+ if (rtl) {
+ if (missing_right) {
+ draw_texture(highlight_arrow == 1 ? decr_hl : decr, Point2(0, vofs));
+ } else {
+ draw_texture(decr, Point2(0, 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));
+ if (offset > 0) {
+ draw_texture(highlight_arrow == 0 ? incr_hl : incr, Point2(incr->get_size().width, vofs));
+ } else {
+ draw_texture(incr, Point2(incr->get_size().width, vofs), Color(1, 1, 1, 0.5));
+ }
} else {
- draw_texture(incr, Point2(limit + decr->get_size().width, vofs), Color(1, 1, 1, 0.5));
+ if (offset > 0) {
+ draw_texture(highlight_arrow == 0 ? decr_hl : decr, Point2(limit, vofs));
+ } else {
+ draw_texture(decr, Point2(limit, 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));
+ } else {
+ draw_texture(incr, Point2(limit + decr->get_size().width, vofs), Color(1, 1, 1, 0.5));
+ }
}
buttons_visible = true;
@@ -422,6 +512,7 @@ void Tabs::set_tab_title(int p_tab, const String &p_title) {
ERR_FAIL_INDEX(p_tab, tabs.size());
tabs.write[p_tab].text = p_title;
tabs.write[p_tab].xl_text = tr(p_title);
+ _shape(p_tab);
update();
minimum_size_changed();
}
@@ -431,6 +522,61 @@ String Tabs::get_tab_title(int p_tab) const {
return tabs[p_tab].text;
}
+void Tabs::set_tab_text_direction(int p_tab, Control::TextDirection p_text_direction) {
+ ERR_FAIL_INDEX(p_tab, tabs.size());
+ ERR_FAIL_COND((int)p_text_direction < -1 || (int)p_text_direction > 3);
+ if (tabs[p_tab].text_direction != p_text_direction) {
+ tabs.write[p_tab].text_direction = p_text_direction;
+ _shape(p_tab);
+ update();
+ }
+}
+
+Control::TextDirection Tabs::get_tab_text_direction(int p_tab) const {
+ ERR_FAIL_INDEX_V(p_tab, tabs.size(), Control::TEXT_DIRECTION_INHERITED);
+ return tabs[p_tab].text_direction;
+}
+
+void Tabs::clear_tab_opentype_features(int p_tab) {
+ ERR_FAIL_INDEX(p_tab, tabs.size());
+ tabs.write[p_tab].opentype_features.clear();
+ _shape(p_tab);
+ update();
+}
+
+void Tabs::set_tab_opentype_feature(int p_tab, const String &p_name, int p_value) {
+ ERR_FAIL_INDEX(p_tab, tabs.size());
+ int32_t tag = TS->name_to_tag(p_name);
+ if (!tabs[p_tab].opentype_features.has(tag) || (int)tabs[p_tab].opentype_features[tag] != p_value) {
+ tabs.write[p_tab].opentype_features[tag] = p_value;
+ _shape(p_tab);
+ update();
+ }
+}
+
+int Tabs::get_tab_opentype_feature(int p_tab, const String &p_name) const {
+ ERR_FAIL_INDEX_V(p_tab, tabs.size(), -1);
+ int32_t tag = TS->name_to_tag(p_name);
+ if (!tabs[p_tab].opentype_features.has(tag)) {
+ return -1;
+ }
+ return tabs[p_tab].opentype_features[tag];
+}
+
+void Tabs::set_tab_language(int p_tab, const String &p_language) {
+ ERR_FAIL_INDEX(p_tab, tabs.size());
+ if (tabs[p_tab].language != p_language) {
+ tabs.write[p_tab].language = p_language;
+ _shape(p_tab);
+ update();
+ }
+}
+
+String Tabs::get_tab_language(int p_tab) const {
+ ERR_FAIL_INDEX_V(p_tab, tabs.size(), "");
+ return tabs[p_tab].language;
+}
+
void Tabs::set_tab_icon(int p_tab, const Ref<Texture2D> &p_icon) {
ERR_FAIL_INDEX(p_tab, tabs.size());
tabs.write[p_tab].icon = p_icon;
@@ -508,7 +654,6 @@ void Tabs::_update_cache() {
Ref<StyleBox> tab_disabled = get_theme_stylebox("tab_disabled");
Ref<StyleBox> tab_bg = get_theme_stylebox("tab_bg");
Ref<StyleBox> tab_fg = get_theme_stylebox("tab_fg");
- Ref<Font> font = get_theme_font("font");
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();
@@ -520,7 +665,8 @@ void Tabs::_update_cache() {
for (int i = 0; i < tabs.size(); i++) {
tabs.write[i].ofs_cache = mw;
tabs.write[i].size_cache = get_tab_width(i);
- tabs.write[i].size_text = Math::ceil(font->get_string_size(tabs[i].xl_text).width);
+ tabs.write[i].size_text = Math::ceil(tabs[i].text_buf->get_size().x);
+ tabs.write[i].text_buf->set_width(-1);
mw += tabs[i].size_cache;
if (tabs[i].size_cache <= min_width || i == current) {
size_fixed += tabs[i].size_cache;
@@ -562,6 +708,7 @@ void Tabs::_update_cache() {
tabs.write[i].ofs_cache = w;
tabs.write[i].size_cache = lsize;
tabs.write[i].size_text = slen;
+ tabs.write[i].text_buf->set_width(slen);
w += lsize;
}
}
@@ -578,6 +725,9 @@ 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->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;
t.disabled = false;
t.ofs_cache = 0;
@@ -771,7 +921,6 @@ int Tabs::get_tab_width(int p_idx) const {
Ref<StyleBox> tab_bg = get_theme_stylebox("tab_bg");
Ref<StyleBox> tab_fg = get_theme_stylebox("tab_fg");
Ref<StyleBox> tab_disabled = get_theme_stylebox("tab_disabled");
- Ref<Font> font = get_theme_font("font");
int x = 0;
@@ -783,7 +932,7 @@ int Tabs::get_tab_width(int p_idx) const {
}
}
- x += Math::ceil(font->get_string_size(tabs[p_idx].xl_text).width);
+ x += Math::ceil(tabs[p_idx].text_buf->get_size().x);
if (tabs[p_idx].disabled) {
x += tab_disabled->get_minimum_size().width;
@@ -869,7 +1018,11 @@ void Tabs::ensure_tab_visible(int p_idx) {
Rect2 Tabs::get_tab_rect(int p_tab) const {
ERR_FAIL_INDEX_V(p_tab, tabs.size(), Rect2());
- return Rect2(tabs[p_tab].ofs_cache, 0, tabs[p_tab].size_cache, get_size().height);
+ if (is_layout_rtl()) {
+ return Rect2(get_size().width - tabs[p_tab].ofs_cache - tabs[p_tab].size_cache, 0, tabs[p_tab].size_cache, get_size().height);
+ } else {
+ return Rect2(tabs[p_tab].ofs_cache, 0, tabs[p_tab].size_cache, get_size().height);
+ }
}
void Tabs::set_tab_close_display_policy(CloseButtonDisplayPolicy p_policy) {
@@ -927,6 +1080,13 @@ void Tabs::_bind_methods() {
ClassDB::bind_method(D_METHOD("get_previous_tab"), &Tabs::get_previous_tab);
ClassDB::bind_method(D_METHOD("set_tab_title", "tab_idx", "title"), &Tabs::set_tab_title);
ClassDB::bind_method(D_METHOD("get_tab_title", "tab_idx"), &Tabs::get_tab_title);
+ ClassDB::bind_method(D_METHOD("set_tab_text_direction", "tab_idx", "direction"), &Tabs::set_tab_text_direction);
+ ClassDB::bind_method(D_METHOD("get_tab_text_direction", "tab_idx"), &Tabs::get_tab_text_direction);
+ ClassDB::bind_method(D_METHOD("set_tab_opentype_feature", "tab_idx", "tag", "values"), &Tabs::set_tab_opentype_feature);
+ ClassDB::bind_method(D_METHOD("get_tab_opentype_feature", "tab_idx", "tag"), &Tabs::get_tab_opentype_feature);
+ ClassDB::bind_method(D_METHOD("clear_tab_opentype_features", "tab_idx"), &Tabs::clear_tab_opentype_features);
+ ClassDB::bind_method(D_METHOD("set_tab_language", "tab_idx", "language"), &Tabs::set_tab_language);
+ ClassDB::bind_method(D_METHOD("get_tab_language", "tab_idx"), &Tabs::get_tab_language);
ClassDB::bind_method(D_METHOD("set_tab_icon", "tab_idx", "icon"), &Tabs::set_tab_icon);
ClassDB::bind_method(D_METHOD("get_tab_icon", "tab_idx"), &Tabs::get_tab_icon);
ClassDB::bind_method(D_METHOD("set_tab_disabled", "tab_idx", "disabled"), &Tabs::set_tab_disabled);
diff --git a/scene/gui/tabs.h b/scene/gui/tabs.h
index 62142e1cde..bf62ba7210 100644
--- a/scene/gui/tabs.h
+++ b/scene/gui/tabs.h
@@ -32,6 +32,7 @@
#define TABS_H
#include "scene/gui/control.h"
+#include "scene/resources/text_line.h"
class Tabs : public Control {
GDCLASS(Tabs, Control);
@@ -55,6 +56,12 @@ private:
struct Tab {
String text;
String xl_text;
+
+ Dictionary opentype_features;
+ String language;
+ Control::TextDirection text_direction = Control::TEXT_DIRECTION_INHERITED;
+
+ Ref<TextLine> text_buf;
Ref<Texture2D> icon;
int ofs_cache;
bool disabled;
@@ -101,6 +108,8 @@ private:
void _on_mouse_exited();
+ void _shape(int p_tab);
+
protected:
void _gui_input(const Ref<InputEvent> &p_event);
void _notification(int p_what);
@@ -117,6 +126,16 @@ public:
void set_tab_title(int p_tab, const String &p_title);
String get_tab_title(int p_tab) const;
+ void set_tab_text_direction(int p_tab, TextDirection p_text_direction);
+ TextDirection get_tab_text_direction(int p_tab) const;
+
+ void set_tab_opentype_feature(int p_tab, const String &p_name, int p_value);
+ int get_tab_opentype_feature(int p_tab, const String &p_name) const;
+ void clear_tab_opentype_features(int p_tab);
+
+ void set_tab_language(int p_tab, const String &p_language);
+ String get_tab_language(int p_tab) const;
+
void set_tab_icon(int p_tab, const Ref<Texture2D> &p_icon);
Ref<Texture2D> get_tab_icon(int p_tab) const;
diff --git a/scene/gui/text_edit.cpp b/scene/gui/text_edit.cpp
index 77ac3d6702..18b4e30abf 100644
--- a/scene/gui/text_edit.cpp
+++ b/scene/gui/text_edit.cpp
@@ -762,7 +762,7 @@ void TextEdit::_notification(int p_what) {
int cursor_wrap_index = get_cursor_wrap_index();
- FontDrawer drawer(cache.font, Color(1, 1, 1));
+ //FontDrawer drawer(cache.font, Color(1, 1, 1));
int first_visible_line = get_first_visible_line() - 1;
int draw_amount = visible_rows + (smooth_scroll_enabled ? 1 : 0);
@@ -1032,7 +1032,7 @@ void TextEdit::_notification(int p_what) {
}
int yofs = ofs_y + (get_row_height() - cache.font->get_height()) / 2;
- cache.font->draw(ci, Point2(gutter_offset + ofs_x, yofs + cache.font->get_ascent()), text, get_line_gutter_item_color(line, g));
+ cache.font->draw_string(ci, Point2(gutter_offset + ofs_x, yofs + cache.font->get_ascent()), text, HALIGN_LEFT, -1, cache.font_size, get_line_gutter_item_color(line, g));
} break;
case GUTTER_TPYE_ICON: {
const Ref<Texture2D> icon = get_line_gutter_icon(line, g);
@@ -1186,7 +1186,7 @@ void TextEdit::_notification(int p_what) {
if (brace_open_mismatch) {
color = cache.brace_mismatch_color;
}
- drawer.draw_char(ci, Point2i(char_ofs + char_margin + ofs_x, yofs + ascent), '_', str[j + 1], in_selection && override_selected_font_color ? cache.font_color_selected : color);
+ cache.font->draw_char(ci, Point2i(char_ofs + char_margin + ofs_x, yofs + ascent), '_', str[j + 1], cache.font_size, in_selection && override_selected_font_color ? cache.font_color_selected : color);
}
if ((brace_close_match_line == line && brace_close_match_column == last_wrap_column + j) ||
@@ -1194,7 +1194,7 @@ void TextEdit::_notification(int p_what) {
if (brace_close_mismatch) {
color = cache.brace_mismatch_color;
}
- drawer.draw_char(ci, Point2i(char_ofs + char_margin + ofs_x, yofs + ascent), '_', str[j + 1], in_selection && override_selected_font_color ? cache.font_color_selected : color);
+ cache.font->draw_char(ci, Point2i(char_ofs + char_margin + ofs_x, yofs + ascent), '_', str[j + 1], cache.font_size, in_selection && override_selected_font_color ? cache.font_color_selected : color);
}
}
@@ -1230,7 +1230,7 @@ void TextEdit::_notification(int p_what) {
RenderingServer::get_singleton()->canvas_item_add_rect(ci, Rect2(Point2(char_ofs + char_margin, ofs_y + get_row_height()), Size2(im_char_width, 1)), color);
}
- drawer.draw_char(ci, Point2(char_ofs + char_margin + ofs_x, ofs_y + ascent), cchar, next, color);
+ cache.font->draw_char(ci, Point2(char_ofs + char_margin + ofs_x, ofs_y + ascent), cchar, next, cache.font_size, color);
char_ofs += im_char_width;
ofs++;
@@ -1266,7 +1266,7 @@ void TextEdit::_notification(int p_what) {
if (str[j] >= 32) {
int yofs = ofs_y + (get_row_height() - cache.font->get_height()) / 2;
- int w = drawer.draw_char(ci, Point2i(char_ofs + char_margin + ofs_x, yofs + ascent), str[j], str[j + 1], in_selection && override_selected_font_color ? cache.font_color_selected : color);
+ int w = cache.font->draw_char(ci, Point2i(char_ofs + char_margin + ofs_x, yofs + ascent), str[j], str[j + 1], cache.font_size, in_selection && override_selected_font_color ? cache.font_color_selected : color);
if (underlined) {
float line_width = cache.font->get_underline_thickness();
#ifdef TOOLS_ENABLED
@@ -1326,7 +1326,7 @@ void TextEdit::_notification(int p_what) {
RenderingServer::get_singleton()->canvas_item_add_rect(ci, Rect2(Point2(char_ofs + char_margin, ofs_y + get_row_height()), Size2(im_char_width, 1)), color);
}
- drawer.draw_char(ci, Point2(char_ofs + char_margin + ofs_x, ofs_y + ascent), cchar, next, color);
+ cache.font->draw_char(ci, Point2(char_ofs + char_margin + ofs_x, ofs_y + ascent), cchar, next, cache.font_size, color);
char_ofs += im_char_width;
ofs++;
@@ -1371,11 +1371,11 @@ void TextEdit::_notification(int p_what) {
int lines = MIN(completion_options_size, maxlines);
int w = 0;
int h = lines * get_row_height();
- int nofs = cache.font->get_string_size(completion_base).width;
+ int nofs = cache.font->get_string_size(completion_base, cache.font_size).width;
if (completion_options_size < 50) {
for (int i = 0; i < completion_options_size; i++) {
- int w2 = MIN(cache.font->get_string_size(completion_options[i].display).x, cmax_width);
+ int w2 = MIN(cache.font->get_string_size(completion_options[i].display, cache.font_size).x, cmax_width);
if (w2 > w) {
w = w2;
}
@@ -1452,7 +1452,7 @@ void TextEdit::_notification(int p_what) {
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);
}
- draw_string(cache.font, title_pos, completion_options[l].display, completion_options[l].font_color, completion_rect.size.width - (icon_area_size.x + icon_hsep));
+ draw_string(cache.font, title_pos, completion_options[l].display, HALIGN_LEFT, completion_rect.size.width - (icon_area_size.x + icon_hsep), cache.font_size, completion_options[l].font_color);
}
if (scrollw) {
@@ -1490,10 +1490,10 @@ void TextEdit::_notification(int p_what) {
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).x;
+ 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)))).x;
+ offset = font->get_string_size(l.substr(0, l.find(String::chr(0xFFFF))), cache.font_size).x;
} else {
spacing += cache.line_spacing;
}
@@ -1523,13 +1523,13 @@ void TextEdit::_notification(int p_what) {
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)))).x;
- end = font->get_string_size(l.substr(0, l.rfind(String::chr(0xFFFF)))).x;
+ 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() + font->get_height() * i + spacing);
round_ofs = round_ofs.round();
- draw_string(font, round_ofs, l.replace(String::chr(0xFFFF), ""), font_color);
+ 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() + font->get_height() * i + spacing - 1);
draw_line(b, b + Vector2(end - begin, 0), font_color);
@@ -2002,11 +2002,11 @@ Vector2i TextEdit::_get_cursor_pixel_pos() {
int ix = 0;
while (ix < rows2[0].size() && ix < cursor.column) {
if (cache.font != nullptr) {
- x += cache.font->get_char_size(rows2[0].get(ix)).width;
+ x += cache.font->get_char_size(rows2[0].get(ix), cache.font_size).width;
}
ix++;
}
- x += get_indent_level(cursor.line) * cache.font->get_char_size(' ').width;
+ x += get_indent_level(cursor.line) * cache.font->get_char_size(' ', cache.font_size).width;
return Vector2i(x, y);
}
@@ -4146,7 +4146,7 @@ Vector<String> TextEdit::get_wrap_rows_text(int p_line) const {
String word_str = "";
int cur_wrap_index = 0;
- int tab_offset_px = get_indent_level(p_line) * cache.font->get_char_size(' ').width;
+ int tab_offset_px = get_indent_level(p_line) * cache.font->get_char_size(' ', cache.font_size).width;
if (tab_offset_px >= wrap_at) {
tab_offset_px = 0;
}
@@ -4475,7 +4475,7 @@ int TextEdit::get_column_x_offset_for_line(int p_char, int p_line) const {
}
int px = get_column_x_offset(n_char, rows[wrap_index]);
- int wrap_offset_px = get_indent_level(p_line) * cache.font->get_char_size(' ').width;
+ int wrap_offset_px = get_indent_level(p_line) * cache.font->get_char_size(' ', cache.font_size).width;
if (wrap_offset_px >= wrap_at) {
wrap_offset_px = 0;
}
@@ -4771,6 +4771,7 @@ void TextEdit::_update_caches() {
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.caret_color = get_theme_color("caret_color");
cache.caret_background_color = get_theme_color("caret_background_color");
cache.font_color = get_theme_color("font_color");
diff --git a/scene/gui/text_edit.h b/scene/gui/text_edit.h
index 5cfa70bc55..9255f853eb 100644
--- a/scene/gui/text_edit.h
+++ b/scene/gui/text_edit.h
@@ -444,6 +444,7 @@ protected:
Ref<StyleBox> style_focus;
Ref<StyleBox> style_readonly;
Ref<Font> font;
+ int font_size;
Color completion_background_color;
Color completion_selected_color;
Color completion_existing_color;
diff --git a/scene/gui/tree.cpp b/scene/gui/tree.cpp
index 31030765e0..6bd8003ef0 100644
--- a/scene/gui/tree.cpp
+++ b/scene/gui/tree.cpp
@@ -36,6 +36,7 @@
#include "core/os/keyboard.h"
#include "core/os/os.h"
#include "core/string/print_string.h"
+#include "core/string/translation.h"
#include "scene/main/window.h"
#include "box_container.h"
@@ -129,6 +130,7 @@ void TreeItem::set_cell_mode(int p_column, TreeCellMode p_mode) {
c.checked = false;
c.icon = Ref<Texture2D>();
c.text = "";
+ c.dirty = true;
c.icon_max_w = 0;
_changed_notify(p_column);
}
@@ -153,6 +155,7 @@ bool TreeItem::is_checked(int p_column) const {
void TreeItem::set_text(int p_column, String p_text) {
ERR_FAIL_INDEX(p_column, cells.size());
cells.write[p_column].text = p_text;
+ cells.write[p_column].dirty = true;
if (cells[p_column].mode == TreeItem::CELL_MODE_RANGE) {
Vector<String> strings = p_text.split(",");
@@ -176,6 +179,87 @@ String TreeItem::get_text(int p_column) const {
return cells[p_column].text;
}
+void TreeItem::set_text_direction(int p_column, Control::TextDirection p_text_direction) {
+ ERR_FAIL_INDEX(p_column, cells.size());
+ ERR_FAIL_COND((int)p_text_direction < -1 || (int)p_text_direction > 3);
+ if (cells[p_column].text_direction != p_text_direction) {
+ cells.write[p_column].text_direction = p_text_direction;
+ cells.write[p_column].dirty = true;
+ _changed_notify(p_column);
+ }
+}
+
+Control::TextDirection TreeItem::get_text_direction(int p_column) const {
+ ERR_FAIL_INDEX_V(p_column, cells.size(), Control::TEXT_DIRECTION_INHERITED);
+ return cells[p_column].text_direction;
+}
+
+void TreeItem::clear_opentype_features(int p_column) {
+ ERR_FAIL_INDEX(p_column, cells.size());
+ cells.write[p_column].opentype_features.clear();
+ cells.write[p_column].dirty = true;
+ _changed_notify(p_column);
+}
+
+void TreeItem::set_opentype_feature(int p_column, const String &p_name, int p_value) {
+ ERR_FAIL_INDEX(p_column, cells.size());
+ int32_t tag = TS->name_to_tag(p_name);
+ if (!cells[p_column].opentype_features.has(tag) || (int)cells[p_column].opentype_features[tag] != p_value) {
+ cells.write[p_column].opentype_features[tag] = p_value;
+ cells.write[p_column].dirty = true;
+ _changed_notify(p_column);
+ }
+}
+
+int TreeItem::get_opentype_feature(int p_column, const String &p_name) const {
+ ERR_FAIL_INDEX_V(p_column, cells.size(), -1);
+ int32_t tag = TS->name_to_tag(p_name);
+ if (!cells[p_column].opentype_features.has(tag)) {
+ return -1;
+ }
+ return cells[p_column].opentype_features[tag];
+}
+
+void TreeItem::set_structured_text_bidi_override(int p_column, Control::StructuredTextParser p_parser) {
+ ERR_FAIL_INDEX(p_column, cells.size());
+ if (cells[p_column].st_parser != p_parser) {
+ cells.write[p_column].st_parser = p_parser;
+ cells.write[p_column].dirty = true;
+ _changed_notify(p_column);
+ }
+}
+
+Control::StructuredTextParser TreeItem::get_structured_text_bidi_override(int p_column) const {
+ ERR_FAIL_INDEX_V(p_column, cells.size(), Control::STRUCTURED_TEXT_NONE);
+ return cells[p_column].st_parser;
+}
+
+void TreeItem::set_structured_text_bidi_override_options(int p_column, Array p_args) {
+ ERR_FAIL_INDEX(p_column, cells.size());
+ cells.write[p_column].st_args = p_args;
+ cells.write[p_column].dirty = true;
+ _changed_notify(p_column);
+}
+
+Array TreeItem::get_structured_text_bidi_override_options(int p_column) const {
+ ERR_FAIL_INDEX_V(p_column, cells.size(), Array());
+ return cells[p_column].st_args;
+}
+
+void TreeItem::set_language(int p_column, const String &p_language) {
+ ERR_FAIL_INDEX(p_column, cells.size());
+ if (cells[p_column].language != p_language) {
+ cells.write[p_column].language = p_language;
+ cells.write[p_column].dirty = true;
+ _changed_notify(p_column);
+ }
+}
+
+String TreeItem::get_language(int p_column) const {
+ ERR_FAIL_INDEX_V(p_column, cells.size(), "");
+ return cells[p_column].language;
+}
+
void TreeItem::set_suffix(int p_column, String p_suffix) {
ERR_FAIL_INDEX(p_column, cells.size());
cells.write[p_column].suffix = p_suffix;
@@ -246,6 +330,7 @@ void TreeItem::set_range(int p_column, double p_value) {
}
cells.write[p_column].val = p_value;
+ cells.write[p_column].dirty = true;
_changed_notify(p_column);
}
@@ -719,6 +804,22 @@ void TreeItem::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_text", "column", "text"), &TreeItem::set_text);
ClassDB::bind_method(D_METHOD("get_text", "column"), &TreeItem::get_text);
+ ClassDB::bind_method(D_METHOD("set_text_direction", "column", "direction"), &TreeItem::set_text_direction);
+ ClassDB::bind_method(D_METHOD("get_text_direction", "column"), &TreeItem::get_text_direction);
+
+ ClassDB::bind_method(D_METHOD("set_opentype_feature", "column", "tag", "value"), &TreeItem::set_opentype_feature);
+ ClassDB::bind_method(D_METHOD("get_opentype_feature", "column", "tag"), &TreeItem::get_opentype_feature);
+ ClassDB::bind_method(D_METHOD("clear_opentype_features", "column"), &TreeItem::clear_opentype_features);
+
+ ClassDB::bind_method(D_METHOD("set_structured_text_bidi_override", "column", "parser"), &TreeItem::set_structured_text_bidi_override);
+ ClassDB::bind_method(D_METHOD("get_structured_text_bidi_override", "column"), &TreeItem::get_structured_text_bidi_override);
+
+ ClassDB::bind_method(D_METHOD("set_structured_text_bidi_override_options", "column", "args"), &TreeItem::set_structured_text_bidi_override_options);
+ ClassDB::bind_method(D_METHOD("get_structured_text_bidi_override_options", "column"), &TreeItem::get_structured_text_bidi_override_options);
+
+ ClassDB::bind_method(D_METHOD("set_language", "column", "language"), &TreeItem::set_language);
+ ClassDB::bind_method(D_METHOD("get_language", "column"), &TreeItem::get_language);
+
ClassDB::bind_method(D_METHOD("set_suffix", "column", "text"), &TreeItem::set_suffix);
ClassDB::bind_method(D_METHOD("get_suffix", "column"), &TreeItem::get_suffix);
@@ -896,7 +997,9 @@ TreeItem::~TreeItem() {
void Tree::update_cache() {
cache.font = get_theme_font("font");
+ cache.font_size = get_theme_font_size("font_size");
cache.tb_font = get_theme_font("title_button_font");
+ cache.tb_font_size = get_theme_font_size("title_button_font_size");
cache.bg = get_theme_stylebox("bg");
cache.selected = get_theme_stylebox("selected");
cache.selected_focus = get_theme_stylebox("selected_focus");
@@ -906,7 +1009,11 @@ void Tree::update_cache() {
cache.checked = get_theme_icon("checked");
cache.unchecked = get_theme_icon("unchecked");
- cache.arrow_collapsed = get_theme_icon("arrow_collapsed");
+ if (is_layout_rtl()) {
+ cache.arrow_collapsed = get_theme_icon("arrow_collapsed_mirrored");
+ } else {
+ cache.arrow_collapsed = get_theme_icon("arrow_collapsed");
+ }
cache.arrow = get_theme_icon("arrow");
cache.select_arrow = get_theme_icon("select_arrow");
cache.updown = get_theme_icon("updown");
@@ -935,7 +1042,7 @@ void Tree::update_cache() {
cache.title_button_hover = get_theme_stylebox("title_button_hover");
cache.title_button_color = get_theme_color("title_button_color");
- v_scroll->set_custom_step(cache.font->get_height());
+ v_scroll->set_custom_step(cache.font->get_height(cache.font_size));
}
int Tree::compute_item_height(TreeItem *p_item) const {
@@ -944,9 +1051,13 @@ int Tree::compute_item_height(TreeItem *p_item) const {
}
ERR_FAIL_COND_V(cache.font.is_null(), 0);
- int height = cache.font->get_height();
+ int height = 0;
for (int i = 0; i < columns.size(); i++) {
+ if (p_item->cells[i].dirty) {
+ const_cast<Tree *>(this)->update_item_cell(p_item, i);
+ }
+ height = MAX(height, p_item->cells[i].text_buf->get_size().y);
for (int j = 0; j < p_item->cells[i].buttons.size(); j++) {
Size2i s; // = cache.button_pressed->get_minimum_size();
s += p_item->cells[i].buttons[j].texture->get_size();
@@ -1013,39 +1124,53 @@ int Tree::get_item_height(TreeItem *p_item) const {
return height;
}
-void Tree::draw_item_rect(const TreeItem::Cell &p_cell, const Rect2i &p_rect, const Color &p_color, const Color &p_icon_color) {
+void Tree::draw_item_rect(TreeItem::Cell &p_cell, const Rect2i &p_rect, const Color &p_color, const Color &p_icon_color) {
ERR_FAIL_COND(cache.font.is_null());
Rect2i rect = p_rect;
- Ref<Font> font = cache.font;
- String text = p_cell.text;
- if (p_cell.suffix != String()) {
- text += " " + p_cell.suffix;
- }
+ Size2 ts = p_cell.text_buf->get_size();
+ bool rtl = is_layout_rtl();
int w = 0;
if (!p_cell.icon.is_null()) {
Size2i bmsize = p_cell.get_icon_size();
-
if (p_cell.icon_max_w > 0 && bmsize.width > p_cell.icon_max_w) {
bmsize.width = p_cell.icon_max_w;
}
w += bmsize.width + cache.hseparation;
+ if (rect.size.width > 0 && (w + ts.width) > rect.size.width) {
+ ts.width = rect.size.width - w;
+ }
}
- w += font->get_string_size(text).width;
+ w += ts.width;
switch (p_cell.text_align) {
case TreeItem::ALIGN_LEFT:
- break; //do none
+ if (rtl) {
+ rect.position.x += MAX(0, (rect.size.width - w));
+ }
+ break;
case TreeItem::ALIGN_CENTER:
rect.position.x += MAX(0, (rect.size.width - w) / 2);
- break; //do none
+ break;
case TreeItem::ALIGN_RIGHT:
- rect.position.x += MAX(0, (rect.size.width - w));
- break; //do none
+ if (!rtl) {
+ rect.position.x += MAX(0, (rect.size.width - w));
+ }
+ break;
}
RID ci = get_canvas_item();
+
+ if (rtl) {
+ Point2 draw_pos = rect.position;
+ draw_pos.y += Math::floor((rect.size.y - p_cell.text_buf->get_size().y) / 2.0);
+ p_cell.text_buf->set_width(MAX(0, rect.size.width));
+ p_cell.text_buf->draw(ci, draw_pos, p_color);
+ rect.position.x += ts.width + cache.hseparation;
+ rect.size.x -= ts.width + cache.hseparation;
+ }
+
if (!p_cell.icon.is_null()) {
Size2i bmsize = p_cell.get_icon_size();
@@ -1059,8 +1184,80 @@ void Tree::draw_item_rect(const TreeItem::Cell &p_cell, const Rect2i &p_rect, co
rect.size.x -= bmsize.x + cache.hseparation;
}
- rect.position.y += Math::floor((rect.size.y - font->get_height()) / 2.0) + font->get_ascent();
- font->draw(ci, rect.position, text, p_color, MAX(0, rect.size.width));
+ if (!rtl) {
+ Point2 draw_pos = rect.position;
+ draw_pos.y += Math::floor((rect.size.y - p_cell.text_buf->get_size().y) / 2.0);
+ p_cell.text_buf->set_width(MAX(0, rect.size.width));
+ p_cell.text_buf->draw(ci, draw_pos, p_color);
+ }
+}
+
+void Tree::update_column(int p_col) {
+ columns.write[p_col].text_buf->clear();
+ if (columns[p_col].text_direction == Control::TEXT_DIRECTION_INHERITED) {
+ columns.write[p_col].text_buf->set_direction(is_layout_rtl() ? TextServer::DIRECTION_RTL : TextServer::DIRECTION_LTR);
+ } 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());
+}
+
+void Tree::update_item_cell(TreeItem *p_item, int p_col) {
+ String valtext;
+
+ p_item->cells.write[p_col].text_buf->clear();
+ if (p_item->cells[p_col].mode == TreeItem::CELL_MODE_RANGE) {
+ if (p_item->cells[p_col].text != "") {
+ if (!p_item->cells[p_col].editable) {
+ return;
+ }
+
+ int option = (int)p_item->cells[p_col].val;
+
+ valtext = RTR("(Other)");
+ Vector<String> strings = p_item->cells[p_col].text.split(",");
+ for (int j = 0; j < strings.size(); j++) {
+ int value = j;
+ if (!strings[j].get_slicec(':', 1).empty()) {
+ value = strings[j].get_slicec(':', 1).to_int();
+ }
+ if (option == value) {
+ valtext = strings[j].get_slicec(':', 0);
+ break;
+ }
+ }
+
+ } else {
+ valtext = String::num(p_item->cells[p_col].val, Math::range_step_decimals(p_item->cells[p_col].step));
+ }
+ } else {
+ valtext = p_item->cells[p_col].text;
+ }
+
+ if (p_item->cells[p_col].suffix != String()) {
+ valtext += " " + p_item->cells[p_col].suffix;
+ }
+
+ if (p_item->cells[p_col].text_direction == Control::TEXT_DIRECTION_INHERITED) {
+ p_item->cells.write[p_col].text_buf->set_direction(is_layout_rtl() ? TextServer::DIRECTION_RTL : TextServer::DIRECTION_LTR);
+ } 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());
+ 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;
+}
+
+void Tree::update_item_cache(TreeItem *p_item) {
+ for (int i = 0; i < p_item->cells.size(); i++) {
+ update_item_cell(p_item, i);
+ }
+
+ TreeItem *c = p_item->children;
+ while (c) {
+ update_item_cache(c);
+ c = c->next;
+ }
}
int Tree::draw_item(const Point2i &p_pos, const Point2 &p_draw_ofs, const Size2 &p_draw_size, TreeItem *p_item) {
@@ -1073,6 +1270,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();
/* Calculate height of the label part */
label_h += cache.vseparation;
@@ -1086,9 +1284,6 @@ int Tree::draw_item(const Point2i &p_pos, const Point2 &p_draw_ofs, const Size2
//if (p_item->get_parent()!=root || !hide_root)
ERR_FAIL_COND_V(cache.font.is_null(), -1);
- Ref<Font> font = cache.font;
-
- int font_ascent = font->get_ascent();
int ofs = p_pos.x + ((p_item->disable_folding || hide_folding) ? cache.hseparation : cache.item_margin);
int skip2 = 0;
@@ -1133,12 +1328,20 @@ int Tree::draw_item(const Point2i &p_pos, const Point2 &p_draw_ofs, const Size2
if (cache.click_type == Cache::CLICK_BUTTON && cache.click_item == p_item && cache.click_column == i && cache.click_index == j && !p_item->cells[i].buttons[j].disabled) {
//being pressed
- cache.button_pressed->draw(get_canvas_item(), Rect2(o, s));
+ Point2 od = o;
+ if (rtl) {
+ od.x = get_size().width - od.x - s.x;
+ }
+ cache.button_pressed->draw(get_canvas_item(), Rect2(od, s));
}
o.y += (label_h - s.height) / 2;
o += cache.button_pressed->get_offset();
+ if (rtl) {
+ o.x = get_size().width - o.x - b->get_width();
+ }
+
b->draw(ci, o, p_item->cells[i].buttons[j].disabled ? Color(1, 1, 1, 0.5) : p_item->cells[i].buttons[j].color);
w -= s.width + cache.button_margin;
bw += s.width + cache.button_margin;
@@ -1152,7 +1355,11 @@ int Tree::draw_item(const Point2i &p_pos, const Point2 &p_draw_ofs, const Size2
}
if (cache.draw_guides) {
- RenderingServer::get_singleton()->canvas_item_add_line(ci, Point2i(cell_rect.position.x, cell_rect.position.y + cell_rect.size.height), cell_rect.position + cell_rect.size, cache.guide_color, 1);
+ Rect2 r = cell_rect;
+ if (rtl) {
+ r.position.x = get_size().width - r.position.x - r.size.x;
+ }
+ RenderingServer::get_singleton()->canvas_item_add_line(ci, Point2i(r.position.x, r.position.y + r.size.height), r.position + r.size, cache.guide_color, 1);
}
if (i == 0) {
@@ -1160,6 +1367,9 @@ int Tree::draw_item(const Point2i &p_pos, const Point2 &p_draw_ofs, const Size2
Rect2i row_rect = Rect2i(Point2i(cache.bg->get_margin(MARGIN_LEFT), item_rect.position.y), Size2i(get_size().width - cache.bg->get_minimum_size().width, item_rect.size.y));
//Rect2 r = Rect2i(row_rect.pos,row_rect.size);
//r.grow(cache.selected->get_margin(MARGIN_LEFT));
+ if (rtl) {
+ row_rect.position.x = get_size().width - row_rect.position.x - row_rect.size.x;
+ }
if (has_focus()) {
cache.selected_focus->draw(ci, row_rect);
} else {
@@ -1171,16 +1381,12 @@ 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) {
Rect2i r(cell_rect.position, cell_rect.size);
- if (p_item->cells[i].text.size() > 0) {
- float icon_width = p_item->cells[i].get_icon_size().width;
- if (p_item->get_icon_max_width(i) > 0) {
- icon_width = p_item->get_icon_max_width(i);
- }
- r.position.x += icon_width;
- r.size.x -= icon_width;
- }
p_item->set_meta("__focus_rect", Rect2(r.position, r.size));
+ if (rtl) {
+ r.position.x = get_size().width - r.position.x - r.size.x;
+ }
+
if (p_item->cells[i].selected) {
if (has_focus()) {
cache.selected_focus->draw(ci, r);
@@ -1199,6 +1405,9 @@ int Tree::draw_item(const Point2i &p_pos, const Point2 &p_draw_ofs, const Size2
r.position.x -= cache.hseparation;
r.size.x += cache.hseparation;
}
+ if (rtl) {
+ r.position.x = get_size().width - r.position.x - r.size.x;
+ }
if (p_item->cells[i].custom_bg_outline) {
RenderingServer::get_singleton()->canvas_item_add_rect(ci, Rect2(r.position.x, r.position.y, r.size.x, 1), p_item->cells[i].bg_color);
RenderingServer::get_singleton()->canvas_item_add_rect(ci, Rect2(r.position.x, r.position.y + r.size.y - 1, r.size.x, 1), p_item->cells[i].bg_color);
@@ -1212,6 +1421,9 @@ 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;
+ if (rtl) {
+ r.position.x = get_size().width - r.position.x - r.size.x;
+ }
if (drop_mode_section == -1 || has_parent || drop_mode_section == 0) {
RenderingServer::get_singleton()->canvas_item_add_rect(ci, Rect2(r.position.x, r.position.y, r.size.x, 1), cache.drop_position_color);
@@ -1230,12 +1442,21 @@ 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_color_selected" : "font_color");
Color icon_col = p_item->cells[i].icon_color;
+ if (p_item->cells[i].dirty) {
+ const_cast<Tree *>(this)->update_item_cell(p_item, i);
+ }
+
+ if (rtl) {
+ item_rect.position.x = get_size().width - item_rect.position.x - item_rect.size.x;
+ }
+
Point2i text_pos = item_rect.position;
- text_pos.y += Math::floor((item_rect.size.y - font->get_height()) / 2) + font_ascent;
+ text_pos.y += Math::floor((item_rect.size.y - p_item->cells[i].text_buf->get_size().y) / 2);
+ int text_width = p_item->cells[i].text_buf->get_size().x;
switch (p_item->cells[i].mode) {
case TreeItem::CELL_MODE_STRING: {
- draw_item_rect(p_item->cells[i], item_rect, col, icon_col);
+ draw_item_rect(p_item->cells.write[i], item_rect, col, icon_col);
} break;
case TreeItem::CELL_MODE_CHECK: {
Ref<Texture2D> checked = cache.checked;
@@ -1256,7 +1477,7 @@ int Tree::draw_item(const Point2i &p_pos, const Point2 &p_draw_ofs, const Size2
item_rect.size.x -= check_w;
item_rect.position.x += check_w;
- draw_item_rect(p_item->cells[i], item_rect, col, icon_col);
+ draw_item_rect(p_item->cells.write[i], item_rect, col, icon_col);
} break;
case TreeItem::CELL_MODE_RANGE: {
@@ -1265,28 +1486,15 @@ int Tree::draw_item(const Point2i &p_pos, const Point2 &p_draw_ofs, const Size2
break;
}
- int option = (int)p_item->cells[i].val;
-
- String s = RTR("(Other)");
- Vector<String> strings = p_item->cells[i].text.split(",");
- for (int j = 0; j < strings.size(); j++) {
- int value = j;
- if (!strings[j].get_slicec(':', 1).empty()) {
- value = strings[j].get_slicec(':', 1).to_int();
- }
- if (option == value) {
- s = strings[j].get_slicec(':', 0);
- break;
- }
- }
-
- if (p_item->cells[i].suffix != String()) {
- s += " " + p_item->cells[i].suffix;
- }
-
Ref<Texture2D> downarrow = cache.select_arrow;
+ int cell_width = item_rect.size.x - downarrow->get_width();
- font->draw(ci, text_pos, s, col, item_rect.size.x - downarrow->get_width());
+ p_item->cells.write[i].text_buf->set_width(cell_width);
+ if (rtl) {
+ p_item->cells[i].text_buf->draw(ci, text_pos + Vector2(cell_width - text_width, 0), col);
+ } else {
+ p_item->cells[i].text_buf->draw(ci, text_pos, col);
+ }
Point2i arrow_pos = item_rect.position;
arrow_pos.x += item_rect.size.x - downarrow->get_width();
@@ -1296,14 +1504,14 @@ int Tree::draw_item(const Point2i &p_pos, const Point2 &p_draw_ofs, const Size2
} else {
Ref<Texture2D> updown = cache.updown;
- String valtext = String::num(p_item->cells[i].val, Math::range_step_decimals(p_item->cells[i].step));
+ int cell_width = item_rect.size.x - updown->get_width();
- if (p_item->cells[i].suffix != String()) {
- valtext += " " + p_item->cells[i].suffix;
+ if (rtl) {
+ p_item->cells[i].text_buf->draw(ci, text_pos + Vector2(cell_width - text_width, 0), col);
+ } else {
+ p_item->cells[i].text_buf->draw(ci, text_pos, col);
}
- font->draw(ci, text_pos, valtext, col, item_rect.size.x - updown->get_width());
-
if (!p_item->cells[i].editable) {
break;
}
@@ -1341,7 +1549,7 @@ int Tree::draw_item(const Point2i &p_pos, const Point2 &p_draw_ofs, const Size2
}
if (!p_item->cells[i].editable) {
- draw_item_rect(p_item->cells[i], item_rect, col, icon_col);
+ draw_item_rect(p_item->cells.write[i], item_rect, col, icon_col);
break;
}
@@ -1369,7 +1577,7 @@ int Tree::draw_item(const Point2i &p_pos, const Point2 &p_draw_ofs, const Size2
ir.position += cache.custom_button->get_offset();
}
- draw_item_rect(p_item->cells[i], ir, col, icon_col);
+ draw_item_rect(p_item->cells.write[i], ir, col, icon_col);
downarrow->draw(ci, arrow_pos);
@@ -1383,6 +1591,9 @@ int Tree::draw_item(const Point2i &p_pos, const Point2 &p_draw_ofs, const Size2
}
if (select_mode == SELECT_MULTI && selected_item == p_item && selected_col == i) {
+ if (is_layout_rtl()) {
+ cell_rect.position.x = get_size().width - cell_rect.position.x - cell_rect.size.x;
+ }
if (has_focus()) {
cache.cursor->draw(ci, cell_rect);
} else {
@@ -1401,7 +1612,13 @@ int Tree::draw_item(const Point2i &p_pos, const Point2 &p_draw_ofs, const Size2
arrow = cache.arrow;
}
- arrow->draw(ci, p_pos + p_draw_ofs + Point2i(0, (label_h - arrow->get_height()) / 2) - cache.offset);
+ Point2 apos = p_pos + p_draw_ofs + Point2i(0, (label_h - arrow->get_height()) / 2) - cache.offset;
+
+ if (rtl) {
+ apos.x = get_size().width - apos.x - arrow->get_width();
+ }
+
+ arrow->draw(ci, apos);
}
}
@@ -1437,6 +1654,10 @@ int Tree::draw_item(const Point2i &p_pos, const Point2 &p_draw_ofs, const Size2
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;
+ }
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);
}
@@ -1983,7 +2204,6 @@ void Tree::_text_editor_enter(String p_text) {
} else if (c.val > c.max) {
c.val = c.max;
}
-
//popup_edited_item->edited_signal.call( popup_edited_item_col );
} break;
default: {
@@ -2349,8 +2569,13 @@ void Tree::_gui_input(Ref<InputEvent> p_event) {
}
Ref<StyleBox> bg = cache.bg;
+ bool rtl = is_layout_rtl();
- Point2 pos = mm->get_position() - bg->get_offset();
+ Point2 pos = mm->get_position();
+ if (rtl) {
+ pos.x = get_size().width - pos.x;
+ }
+ pos -= cache.bg->get_offset();
Cache::ClickType old_hover = cache.hover_type;
int old_index = cache.hover_index;
@@ -2375,6 +2600,9 @@ void Tree::_gui_input(Ref<InputEvent> p_event) {
if (root) {
Point2 mpos = mm->get_position();
+ if (rtl) {
+ mpos.x = get_size().width - mpos.x;
+ }
mpos -= cache.bg->get_offset();
mpos.y -= _get_title_button_height();
if (mpos.y >= 0) {
@@ -2431,6 +2659,9 @@ void Tree::_gui_input(Ref<InputEvent> p_event) {
if (!range_drag_enabled) {
Vector2 cpos = mm->get_position();
+ if (rtl) {
+ cpos.x = get_size().width - cpos.x;
+ }
if (cpos.distance_to(pressing_pos) > 2) {
range_drag_enabled = true;
range_drag_capture_pos = cpos;
@@ -2462,9 +2693,15 @@ void Tree::_gui_input(Ref<InputEvent> p_event) {
update_cache();
}
+ bool rtl = is_layout_rtl();
+
if (!b->is_pressed()) {
if (b->get_button_index() == BUTTON_LEFT) {
- Point2 pos = b->get_position() - cache.bg->get_offset();
+ Point2 pos = b->get_position();
+ if (rtl) {
+ pos.x = get_size().width - pos.x;
+ }
+ pos -= cache.bg->get_offset();
if (show_column_titles) {
pos.y -= _get_title_button_height();
@@ -2495,7 +2732,11 @@ void Tree::_gui_input(Ref<InputEvent> p_event) {
warp_mouse(range_drag_capture_pos);
} else {
Rect2 rect = get_selected()->get_meta("__focus_rect");
- if (rect.has_point(Point2(b->get_position().x, b->get_position().y))) {
+ Point2 mpos = b->get_position();
+ if (rtl) {
+ mpos.x = get_size().width - mpos.x;
+ }
+ if (rect.has_point(mpos)) {
if (!edit_selected()) {
emit_signal("item_double_clicked");
}
@@ -2540,7 +2781,11 @@ void Tree::_gui_input(Ref<InputEvent> p_event) {
case BUTTON_LEFT: {
Ref<StyleBox> bg = cache.bg;
- Point2 pos = b->get_position() - bg->get_offset();
+ Point2 pos = b->get_position();
+ if (rtl) {
+ pos.x = get_size().width - pos.x;
+ }
+ pos -= bg->get_offset();
cache.click_type = Cache::CLICK_NONE;
if (show_column_titles) {
pos.y -= _get_title_button_height();
@@ -2580,6 +2825,9 @@ void Tree::_gui_input(Ref<InputEvent> p_event) {
if (pressing_for_editor) {
pressing_pos = b->get_position();
+ if (rtl) {
+ pressing_pos.x = get_size().width - pressing_pos.x;
+ }
}
if (b->get_button_index() == BUTTON_RIGHT) {
@@ -2643,7 +2891,11 @@ void Tree::_gui_input(Ref<InputEvent> p_event) {
v_scroll->set_value(v_scroll->get_value() + v_scroll->get_page() * pan_gesture->get_delta().y / 8);
double prev_h = h_scroll->get_value();
- h_scroll->set_value(h_scroll->get_value() + h_scroll->get_page() * pan_gesture->get_delta().x / 8);
+ if (is_layout_rtl()) {
+ h_scroll->set_value(h_scroll->get_value() + h_scroll->get_page() * -pan_gesture->get_delta().x / 8);
+ } else {
+ h_scroll->set_value(h_scroll->get_value() + h_scroll->get_page() * pan_gesture->get_delta().x / 8);
+ }
if (v_scroll->get_value() != prev_v || h_scroll->get_value() != prev_h) {
accept_event();
@@ -2790,7 +3042,13 @@ void Tree::update_scrollbars() {
int Tree::_get_title_button_height() const {
ERR_FAIL_COND_V(cache.font.is_null() || cache.title_button.is_null(), 0);
- return show_column_titles ? cache.font->get_height() + cache.title_button->get_minimum_size().height : 0;
+ int h = 0;
+ if (show_column_titles) {
+ for (int i = 0; i < columns.size(); i++) {
+ h = MAX(h, columns[i].text_buf->get_size().y + cache.title_button->get_minimum_size().height);
+ }
+ }
+ return h;
}
void Tree::_notification(int p_what) {
@@ -2919,17 +3177,22 @@ 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(MARGIN_TOP), get_column_width(i), tbh);
+ if (is_layout_rtl()) {
+ tbrect.position.x = get_size().width - tbrect.size.x - tbrect.position.x;
+ }
sb->draw(ci, tbrect);
ofs2 += tbrect.size.width;
//text
int clip_w = tbrect.size.width - sb->get_minimum_size().width;
- f->draw_halign(ci, tbrect.position + Point2i(sb->get_offset().x, (tbrect.size.height - f->get_height()) / 2 + f->get_ascent()), HALIGN_CENTER, clip_w, columns[i].title, cache.title_button_color);
+ columns.write[i].text_buf->set_width(clip_w);
+ columns[i].text_buf->draw(ci, tbrect.position + Point2i(sb->get_offset().x + (tbrect.size.width - columns[i].text_buf->get_size().x) / 2, (tbrect.size.height - columns[i].text_buf->get_size().y) / 2), cache.title_button_color);
}
}
}
- if (p_what == NOTIFICATION_THEME_CHANGED) {
+ if (p_what == NOTIFICATION_THEME_CHANGED || p_what == NOTIFICATION_LAYOUT_DIRECTION_CHANGED || p_what == NOTIFICATION_TRANSLATION_CHANGED) {
update_cache();
+ _update_all();
}
if (p_what == NOTIFICATION_RESIZED || p_what == NOTIFICATION_TRANSFORM_CHANGED) {
@@ -2947,6 +3210,15 @@ void Tree::_notification(int p_what) {
}
}
+void Tree::_update_all() {
+ for (int i = 0; i < columns.size(); i++) {
+ update_column(i);
+ }
+ if (root) {
+ update_item_cache(root);
+ }
+}
+
Size2 Tree::get_minimum_size() const {
return Size2(1, 1);
}
@@ -3022,6 +3294,9 @@ TreeItem *Tree::get_last_item() {
void Tree::item_edited(int p_column, TreeItem *p_item, bool p_lmb) {
edited_item = p_item;
edited_col = p_column;
+ if (p_item != nullptr && p_column >= 0 && p_column < p_item->cells.size()) {
+ edited_item->cells.write[p_column].dirty = true;
+ }
if (p_lmb) {
emit_signal("item_edited");
} else {
@@ -3030,6 +3305,9 @@ void Tree::item_edited(int p_column, TreeItem *p_item, bool p_lmb) {
}
void Tree::item_changed(int p_column, TreeItem *p_item) {
+ if (p_item != nullptr && p_column >= 0 && p_column < p_item->cells.size()) {
+ p_item->cells.write[p_column].dirty = true;
+ }
update();
}
@@ -3231,7 +3509,7 @@ void Tree::propagate_set_columns(TreeItem *p_item) {
TreeItem *c = p_item->get_children();
while (c) {
propagate_set_columns(c);
- c = c->get_next();
+ c = c->next;
}
}
@@ -3387,7 +3665,11 @@ bool Tree::are_column_titles_visible() const {
void Tree::set_column_title(int p_column, const String &p_title) {
ERR_FAIL_INDEX(p_column, columns.size());
+ if (cache.font.is_null()) { // avoid a strange case that may corrupt stuff
+ update_cache();
+ }
columns.write[p_column].title = p_title;
+ update_column(p_column);
update();
}
@@ -3396,6 +3678,61 @@ String Tree::get_column_title(int p_column) const {
return columns[p_column].title;
}
+void Tree::set_column_title_direction(int p_column, Control::TextDirection p_text_direction) {
+ ERR_FAIL_INDEX(p_column, columns.size());
+ ERR_FAIL_COND((int)p_text_direction < -1 || (int)p_text_direction > 3);
+ if (columns[p_column].text_direction != p_text_direction) {
+ columns.write[p_column].text_direction = p_text_direction;
+ update_column(p_column);
+ update();
+ }
+}
+
+Control::TextDirection Tree::get_column_title_direction(int p_column) const {
+ ERR_FAIL_INDEX_V(p_column, columns.size(), TEXT_DIRECTION_INHERITED);
+ return columns[p_column].text_direction;
+}
+
+void Tree::clear_column_title_opentype_features(int p_column) {
+ ERR_FAIL_INDEX(p_column, columns.size());
+ columns.write[p_column].opentype_features.clear();
+ update_column(p_column);
+ update();
+}
+
+void Tree::set_column_title_opentype_feature(int p_column, const String &p_name, int p_value) {
+ ERR_FAIL_INDEX(p_column, columns.size());
+ int32_t tag = TS->name_to_tag(p_name);
+ if (!columns[p_column].opentype_features.has(tag) || (int)columns[p_column].opentype_features[tag] != p_value) {
+ columns.write[p_column].opentype_features[tag] = p_value;
+ update_column(p_column);
+ update();
+ }
+}
+
+int Tree::get_column_title_opentype_feature(int p_column, const String &p_name) const {
+ ERR_FAIL_INDEX_V(p_column, columns.size(), -1);
+ int32_t tag = TS->name_to_tag(p_name);
+ if (!columns[p_column].opentype_features.has(tag)) {
+ return -1;
+ }
+ return columns[p_column].opentype_features[tag];
+}
+
+void Tree::set_column_title_language(int p_column, const String &p_language) {
+ ERR_FAIL_INDEX(p_column, columns.size());
+ if (columns[p_column].language != p_language) {
+ columns.write[p_column].language = p_language;
+ update_column(p_column);
+ update();
+ }
+}
+
+String Tree::get_column_title_language(int p_column) const {
+ ERR_FAIL_INDEX_V(p_column, columns.size(), "");
+ return columns[p_column].language;
+}
+
Point2 Tree::get_scroll() const {
Point2 ofs;
if (h_scroll->is_visible_in_tree()) {
@@ -3561,6 +3898,9 @@ TreeItem *Tree::_find_item_at_pos(TreeItem *p_item, const Point2 &p_pos, int &r_
int Tree::get_column_at_position(const Point2 &p_pos) const {
if (root) {
Point2 pos = p_pos;
+ if (is_layout_rtl()) {
+ pos.x = get_size().width - pos.x;
+ }
pos -= cache.bg->get_offset();
pos.y -= _get_title_button_height();
if (pos.y < 0) {
@@ -3588,6 +3928,9 @@ int Tree::get_column_at_position(const Point2 &p_pos) const {
int Tree::get_drop_section_at_position(const Point2 &p_pos) const {
if (root) {
Point2 pos = p_pos;
+ if (is_layout_rtl()) {
+ pos.x = get_size().width - pos.x;
+ }
pos -= cache.bg->get_offset();
pos.y -= _get_title_button_height();
if (pos.y < 0) {
@@ -3615,6 +3958,9 @@ int Tree::get_drop_section_at_position(const Point2 &p_pos) const {
TreeItem *Tree::get_item_at_position(const Point2 &p_pos) const {
if (root) {
Point2 pos = p_pos;
+ if (is_layout_rtl()) {
+ pos.x = get_size().width - pos.x;
+ }
pos -= cache.bg->get_offset();
pos.y -= _get_title_button_height();
if (pos.y < 0) {
@@ -3826,6 +4172,17 @@ void Tree::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_column_title", "column", "title"), &Tree::set_column_title);
ClassDB::bind_method(D_METHOD("get_column_title", "column"), &Tree::get_column_title);
+
+ ClassDB::bind_method(D_METHOD("set_column_title_direction", "column", "direction"), &Tree::set_column_title_direction);
+ ClassDB::bind_method(D_METHOD("get_column_title_direction", "column"), &Tree::get_column_title_direction);
+
+ ClassDB::bind_method(D_METHOD("set_column_title_opentype_feature", "column", "tag", "value"), &Tree::set_column_title_opentype_feature);
+ ClassDB::bind_method(D_METHOD("get_column_title_opentype_feature", "column", "tag"), &Tree::get_column_title_opentype_feature);
+ ClassDB::bind_method(D_METHOD("clear_column_title_opentype_features", "column"), &Tree::clear_column_title_opentype_features);
+
+ ClassDB::bind_method(D_METHOD("set_column_title_language", "column", "language"), &Tree::set_column_title_language);
+ ClassDB::bind_method(D_METHOD("get_column_title_language", "column"), &Tree::get_column_title_language);
+
ClassDB::bind_method(D_METHOD("get_scroll"), &Tree::get_scroll);
ClassDB::bind_method(D_METHOD("set_hide_folding", "hide"), &Tree::set_hide_folding);
diff --git a/scene/gui/tree.h b/scene/gui/tree.h
index 9554bb4665..4c3d03c91a 100644
--- a/scene/gui/tree.h
+++ b/scene/gui/tree.h
@@ -36,6 +36,7 @@
#include "scene/gui/popup_menu.h"
#include "scene/gui/scroll_bar.h"
#include "scene/gui/slider.h"
+#include "scene/resources/text_line.h"
class Tree;
@@ -67,6 +68,13 @@ private:
Rect2i icon_region;
String text;
String suffix;
+ Ref<TextLine> text_buf;
+ Dictionary opentype_features;
+ String language;
+ Control::StructuredTextParser st_parser = Control::STRUCTURED_TEXT_DEFAULT;
+ Array st_args;
+ Control::TextDirection text_direction = Control::TEXT_DIRECTION_INHERITED;
+ bool dirty;
double min, max, step, val;
int icon_max_w;
bool expr;
@@ -108,6 +116,8 @@ private:
Vector<Button> buttons;
Cell() {
+ text_buf.instance();
+ dirty = true;
custom_draw_obj = ObjectID();
custom_button = false;
mode = TreeItem::CELL_MODE_STRING;
@@ -182,6 +192,22 @@ public:
void set_text(int p_column, String p_text);
String get_text(int p_column) const;
+ void set_text_direction(int p_column, Control::TextDirection p_text_direction);
+ Control::TextDirection get_text_direction(int p_column) const;
+
+ void set_opentype_feature(int p_column, const String &p_name, int p_value);
+ int get_opentype_feature(int p_column, const String &p_name) const;
+ void clear_opentype_features(int p_column);
+
+ void set_structured_text_bidi_override(int p_column, Control::StructuredTextParser p_parser);
+ Control::StructuredTextParser get_structured_text_bidi_override(int p_column) const;
+
+ void set_structured_text_bidi_override_options(int p_column, Array p_args);
+ Array get_structured_text_bidi_override_options(int p_column) const;
+
+ void set_language(int p_column, const String &p_language);
+ String get_language(int p_column) const;
+
void set_suffix(int p_column, String p_suffix);
String get_suffix(int p_column) const;
@@ -348,7 +374,12 @@ private:
int min_width;
bool expand;
String title;
+ Ref<TextLine> text_buf;
+ Dictionary opentype_features;
+ String language;
+ Control::TextDirection text_direction = Control::TEXT_DIRECTION_INHERITED;
ColumnInfo() {
+ text_buf.instance();
min_width = 1;
expand = true;
}
@@ -374,8 +405,12 @@ private:
int compute_item_height(TreeItem *p_item) const;
int get_item_height(TreeItem *p_item) const;
+ void _update_all();
+ void update_column(int p_col);
+ void update_item_cell(TreeItem *p_item, int p_col);
+ void update_item_cache(TreeItem *p_item);
//void draw_item_text(String p_text,const Ref<Texture2D>& p_icon,int p_icon_max_w,bool p_tool,Rect2i p_rect,const Color& p_color);
- void draw_item_rect(const TreeItem::Cell &p_cell, const Rect2i &p_rect, const Color &p_color, const Color &p_icon_color);
+ void draw_item_rect(TreeItem::Cell &p_cell, const Rect2i &p_rect, const Color &p_color, const Color &p_icon_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);
@@ -400,6 +435,8 @@ private:
struct Cache {
Ref<Font> font;
Ref<Font> tb_font;
+ int font_size;
+ int tb_font_size;
Ref<StyleBox> bg;
Ref<StyleBox> selected;
Ref<StyleBox> selected_focus;
@@ -563,6 +600,16 @@ public:
void set_column_title(int p_column, const String &p_title);
String get_column_title(int p_column) const;
+ void set_column_title_direction(int p_column, Control::TextDirection p_text_direction);
+ Control::TextDirection get_column_title_direction(int p_column) const;
+
+ void set_column_title_opentype_feature(int p_column, const String &p_name, int p_value);
+ int get_column_title_opentype_feature(int p_column, const String &p_name) const;
+ void clear_column_title_opentype_features(int p_column);
+
+ void set_column_title_language(int p_column, const String &p_language);
+ String get_column_title_language(int p_column) const;
+
void set_column_titles_visible(bool p_show);
bool are_column_titles_visible() const;