summaryrefslogtreecommitdiff
path: root/scene/gui
diff options
context:
space:
mode:
Diffstat (limited to 'scene/gui')
-rw-r--r--scene/gui/box_container.cpp13
-rw-r--r--scene/gui/dialogs.cpp8
-rw-r--r--scene/gui/dialogs.h4
-rw-r--r--scene/gui/file_dialog.cpp6
-rw-r--r--scene/gui/file_dialog.h1
-rw-r--r--scene/gui/line_edit.cpp68
-rw-r--r--scene/gui/line_edit.h9
-rw-r--r--scene/gui/popup.cpp22
-rw-r--r--scene/gui/popup_menu.cpp366
-rw-r--r--scene/gui/popup_menu.h19
-rw-r--r--scene/gui/rich_text_label.cpp9
-rw-r--r--scene/gui/scroll_bar.cpp2
-rw-r--r--scene/gui/spin_box.cpp2
-rw-r--r--scene/gui/text_edit.cpp571
-rw-r--r--scene/gui/text_edit.h99
-rw-r--r--scene/gui/tree.cpp10
16 files changed, 421 insertions, 788 deletions
diff --git a/scene/gui/box_container.cpp b/scene/gui/box_container.cpp
index 75d04dba61..f130726837 100644
--- a/scene/gui/box_container.cpp
+++ b/scene/gui/box_container.cpp
@@ -96,7 +96,7 @@ void BoxContainer::_resort() {
}
stretch_avail += stretch_diff; //available stretch space.
- /** Second, pass sucessively to discard elements that can't be stretched, this will run while stretchable
+ /** Second, pass successively to discard elements that can't be stretched, this will run while stretchable
elements exist */
bool has_stretched = false;
@@ -104,6 +104,7 @@ void BoxContainer::_resort() {
has_stretched = true;
bool refit_successful = true; //assume refit-test will go well
+ float error = 0; // Keep track of accumulated error in pixels
for (int i = 0; i < get_child_count(); i++) {
Control *c = Object::cast_to<Control>(get_child(i));
@@ -119,8 +120,9 @@ void BoxContainer::_resort() {
if (msc.will_stretch) { //wants to stretch
//let's see if it can really stretch
-
- int final_pixel_size = stretch_avail * c->get_stretch_ratio() / stretch_ratio_total;
+ float final_pixel_size = stretch_avail * c->get_stretch_ratio() / stretch_ratio_total;
+ // Add leftover fractional pixels to error accumulator
+ error += final_pixel_size - (int)final_pixel_size;
if (final_pixel_size < msc.min_size) {
//if available stretching area is too small for widget,
//then remove it from stretching area
@@ -132,6 +134,11 @@ void BoxContainer::_resort() {
break;
} else {
msc.final_size = final_pixel_size;
+ // Dump accumulated error if one pixel or more
+ if (error >= 1) {
+ msc.final_size += 1;
+ error -= 1;
+ }
}
}
}
diff --git a/scene/gui/dialogs.cpp b/scene/gui/dialogs.cpp
index faef979090..9077bfa4ba 100644
--- a/scene/gui/dialogs.cpp
+++ b/scene/gui/dialogs.cpp
@@ -258,7 +258,7 @@ Button *AcceptDialog::add_cancel(const String &p_cancel) {
if (p_cancel == "") {
c = RTR("Cancel");
}
- Button *b = swap_ok_cancel ? add_button(c, true) : add_button(c);
+ Button *b = swap_cancel_ok ? add_button(c, true) : add_button(c);
b->connect("pressed", callable_mp(this, &AcceptDialog::_cancel_pressed));
return b;
}
@@ -286,9 +286,9 @@ void AcceptDialog::_bind_methods() {
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "dialog_autowrap"), "set_autowrap", "has_autowrap");
}
-bool AcceptDialog::swap_ok_cancel = false;
-void AcceptDialog::set_swap_ok_cancel(bool p_swap) {
- swap_ok_cancel = p_swap;
+bool AcceptDialog::swap_cancel_ok = false;
+void AcceptDialog::set_swap_cancel_ok(bool p_swap) {
+ swap_cancel_ok = p_swap;
}
AcceptDialog::AcceptDialog() {
diff --git a/scene/gui/dialogs.h b/scene/gui/dialogs.h
index 063090c832..de08685ce2 100644
--- a/scene/gui/dialogs.h
+++ b/scene/gui/dialogs.h
@@ -54,7 +54,7 @@ class AcceptDialog : public Window {
void _custom_action(const String &p_action);
void _update_child_rects();
- static bool swap_ok_cancel;
+ static bool swap_cancel_ok;
void _input_from_window(const Ref<InputEvent> &p_event);
void _parent_focused();
@@ -75,7 +75,7 @@ protected:
public:
Label *get_label() { return label; }
- static void set_swap_ok_cancel(bool p_swap);
+ static void set_swap_cancel_ok(bool p_swap);
void register_text_enter(Node *p_line_edit);
diff --git a/scene/gui/file_dialog.cpp b/scene/gui/file_dialog.cpp
index 1e0d3d9f7b..4aea2928f4 100644
--- a/scene/gui/file_dialog.cpp
+++ b/scene/gui/file_dialog.cpp
@@ -40,6 +40,10 @@ FileDialog::GetIconFunc FileDialog::get_large_icon_func = nullptr;
FileDialog::RegisterFunc FileDialog::register_func = nullptr;
FileDialog::RegisterFunc FileDialog::unregister_func = nullptr;
+void FileDialog::popup_file_dialog() {
+ popup_centered_clamped(Size2i(700, 500), 0.8f);
+}
+
VBoxContainer *FileDialog::get_vbox() {
return vbox;
}
@@ -268,7 +272,7 @@ void FileDialog::_action_pressed() {
}
if (dir_access->file_exists(f)) {
- confirm_save->set_text(RTR("File Exists, Overwrite?"));
+ confirm_save->set_text(RTR("File exists, overwrite?"));
confirm_save->popup_centered(Size2(200, 80));
} else {
emit_signal("file_selected", f);
diff --git a/scene/gui/file_dialog.h b/scene/gui/file_dialog.h
index c5272af233..8003650668 100644
--- a/scene/gui/file_dialog.h
+++ b/scene/gui/file_dialog.h
@@ -135,6 +135,7 @@ protected:
static void _bind_methods();
//bind helpers
public:
+ void popup_file_dialog();
void clear_filters();
void add_filter(const String &p_filter);
void set_filters(const Vector<String> &p_filters);
diff --git a/scene/gui/line_edit.cpp b/scene/gui/line_edit.cpp
index 27c2c70708..14167531a5 100644
--- a/scene/gui/line_edit.cpp
+++ b/scene/gui/line_edit.cpp
@@ -118,11 +118,11 @@ void LineEdit::_gui_input(Ref<InputEvent> p_event) {
selection.creating = false;
selection.doubleclick = false;
- if (DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_VIRTUAL_KEYBOARD)) {
+ if (DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_VIRTUAL_KEYBOARD) && virtual_keyboard_enabled) {
if (selection.enabled) {
- DisplayServer::get_singleton()->virtual_keyboard_show(text, get_global_rect(), max_length, selection.begin, selection.end);
+ DisplayServer::get_singleton()->virtual_keyboard_show(text, get_global_rect(), false, max_length, selection.begin, selection.end);
} else {
- DisplayServer::get_singleton()->virtual_keyboard_show(text, get_global_rect(), max_length, cursor_pos);
+ DisplayServer::get_singleton()->virtual_keyboard_show(text, get_global_rect(), false, max_length, cursor_pos);
}
}
}
@@ -309,10 +309,11 @@ void LineEdit::_gui_input(Ref<InputEvent> p_event) {
case KEY_KP_ENTER:
case KEY_ENTER: {
emit_signal("text_entered", text);
- if (DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_VIRTUAL_KEYBOARD)) {
+ if (DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_VIRTUAL_KEYBOARD) && virtual_keyboard_enabled) {
DisplayServer::get_singleton()->virtual_keyboard_hide();
}
+ return;
} break;
case KEY_BACKSPACE: {
@@ -577,7 +578,7 @@ void LineEdit::_gui_input(Ref<InputEvent> p_event) {
if (handled) {
accept_event();
- } else if (!k->get_command()) {
+ } else if (!k->get_command() || (k->get_command() && k->get_alt())) {
if (k->get_unicode() >= 32 && k->get_keycode() != KEY_DELETE) {
if (editable) {
selection_delete();
@@ -699,7 +700,7 @@ void LineEdit::_notification(int p_what) {
update();
} break;
case NOTIFICATION_DRAW: {
- if ((!has_focus() && !menu->has_focus()) || !window_has_focus) {
+ if ((!has_focus() && !menu->has_focus() && !caret_force_displayed) || !window_has_focus) {
draw_caret = false;
}
@@ -925,10 +926,14 @@ void LineEdit::_notification(int p_what) {
}
} break;
case NOTIFICATION_FOCUS_ENTER: {
- if (caret_blink_enabled) {
- caret_blink_timer->start();
- } else {
- draw_caret = true;
+ if (!caret_force_displayed) {
+ if (caret_blink_enabled) {
+ if (caret_blink_timer->is_stopped()) {
+ caret_blink_timer->start();
+ }
+ } else {
+ draw_caret = true;
+ }
}
if (get_viewport()->get_window_id() != DisplayServer::INVALID_WINDOW_ID) {
@@ -937,17 +942,17 @@ void LineEdit::_notification(int p_what) {
DisplayServer::get_singleton()->window_set_ime_position(get_global_position() + cursor_pos, get_viewport()->get_window_id());
}
- if (DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_VIRTUAL_KEYBOARD)) {
+ if (DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_VIRTUAL_KEYBOARD) && virtual_keyboard_enabled) {
if (selection.enabled) {
- DisplayServer::get_singleton()->virtual_keyboard_show(text, get_global_rect(), max_length, selection.begin, selection.end);
+ DisplayServer::get_singleton()->virtual_keyboard_show(text, get_global_rect(), false, max_length, selection.begin, selection.end);
} else {
- DisplayServer::get_singleton()->virtual_keyboard_show(text, get_global_rect(), max_length, cursor_pos);
+ DisplayServer::get_singleton()->virtual_keyboard_show(text, get_global_rect(), false, max_length, cursor_pos);
}
}
} break;
case NOTIFICATION_FOCUS_EXIT: {
- if (caret_blink_enabled) {
+ if (caret_blink_enabled && !caret_force_displayed) {
caret_blink_timer->stop();
}
@@ -958,7 +963,7 @@ void LineEdit::_notification(int p_what) {
ime_text = "";
ime_selection = Point2();
- if (DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_VIRTUAL_KEYBOARD)) {
+ if (DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_VIRTUAL_KEYBOARD) && virtual_keyboard_enabled) {
DisplayServer::get_singleton()->virtual_keyboard_hide();
}
@@ -1167,9 +1172,11 @@ bool LineEdit::cursor_get_blink_enabled() const {
void LineEdit::cursor_set_blink_enabled(const bool p_enabled) {
caret_blink_enabled = p_enabled;
- if (has_focus()) {
+ if (has_focus() || caret_force_displayed) {
if (p_enabled) {
- caret_blink_timer->start();
+ if (caret_blink_timer->is_stopped()) {
+ caret_blink_timer->start();
+ }
} else {
caret_blink_timer->stop();
}
@@ -1178,6 +1185,16 @@ void LineEdit::cursor_set_blink_enabled(const bool p_enabled) {
draw_caret = true;
}
+bool LineEdit::cursor_get_force_displayed() const {
+ return caret_force_displayed;
+}
+
+void LineEdit::cursor_set_force_displayed(const bool p_enabled) {
+ caret_force_displayed = p_enabled;
+ cursor_set_blink_enabled(caret_blink_enabled);
+ update();
+}
+
float LineEdit::cursor_get_blink_speed() const {
return caret_blink_timer->get_wait_time();
}
@@ -1200,7 +1217,7 @@ void LineEdit::_reset_caret_blink_timer() {
void LineEdit::_toggle_draw_caret() {
draw_caret = !draw_caret;
- if (is_visible_in_tree() && has_focus() && window_has_focus) {
+ if (is_visible_in_tree() && ((has_focus() && window_has_focus) || caret_force_displayed)) {
update();
}
}
@@ -1658,6 +1675,14 @@ bool LineEdit::is_shortcut_keys_enabled() const {
return shortcut_keys_enabled;
}
+void LineEdit::set_virtual_keyboard_enabled(bool p_enable) {
+ virtual_keyboard_enabled = p_enable;
+}
+
+bool LineEdit::is_virtual_keyboard_enabled() const {
+ return virtual_keyboard_enabled;
+}
+
void LineEdit::set_selecting_enabled(bool p_enabled) {
selecting_enabled = p_enabled;
@@ -1796,6 +1821,8 @@ void LineEdit::_bind_methods() {
ClassDB::bind_method(D_METHOD("get_expand_to_text_length"), &LineEdit::get_expand_to_text_length);
ClassDB::bind_method(D_METHOD("cursor_set_blink_enabled", "enabled"), &LineEdit::cursor_set_blink_enabled);
ClassDB::bind_method(D_METHOD("cursor_get_blink_enabled"), &LineEdit::cursor_get_blink_enabled);
+ ClassDB::bind_method(D_METHOD("cursor_set_force_displayed", "enabled"), &LineEdit::cursor_set_force_displayed);
+ ClassDB::bind_method(D_METHOD("cursor_get_force_displayed"), &LineEdit::cursor_get_force_displayed);
ClassDB::bind_method(D_METHOD("cursor_set_blink_speed", "blink_speed"), &LineEdit::cursor_set_blink_speed);
ClassDB::bind_method(D_METHOD("cursor_get_blink_speed"), &LineEdit::cursor_get_blink_speed);
ClassDB::bind_method(D_METHOD("set_max_length", "chars"), &LineEdit::set_max_length);
@@ -1813,6 +1840,8 @@ void LineEdit::_bind_methods() {
ClassDB::bind_method(D_METHOD("get_menu"), &LineEdit::get_menu);
ClassDB::bind_method(D_METHOD("set_context_menu_enabled", "enable"), &LineEdit::set_context_menu_enabled);
ClassDB::bind_method(D_METHOD("is_context_menu_enabled"), &LineEdit::is_context_menu_enabled);
+ ClassDB::bind_method(D_METHOD("set_virtual_keyboard_enabled", "enable"), &LineEdit::set_virtual_keyboard_enabled);
+ ClassDB::bind_method(D_METHOD("is_virtual_keyboard_enabled"), &LineEdit::is_virtual_keyboard_enabled);
ClassDB::bind_method(D_METHOD("set_clear_button_enabled", "enable"), &LineEdit::set_clear_button_enabled);
ClassDB::bind_method(D_METHOD("is_clear_button_enabled"), &LineEdit::is_clear_button_enabled);
ClassDB::bind_method(D_METHOD("set_shortcut_keys_enabled", "enable"), &LineEdit::set_shortcut_keys_enabled);
@@ -1848,6 +1877,7 @@ void LineEdit::_bind_methods() {
ADD_PROPERTY(PropertyInfo(Variant::STRING, "secret_character"), "set_secret_character", "get_secret_character");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "expand_to_text_length"), "set_expand_to_text_length", "get_expand_to_text_length");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "context_menu_enabled"), "set_context_menu_enabled", "is_context_menu_enabled");
+ ADD_PROPERTY(PropertyInfo(Variant::BOOL, "virtual_keyboard_enabled"), "set_virtual_keyboard_enabled", "is_virtual_keyboard_enabled");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "clear_button_enabled"), "set_clear_button_enabled", "is_clear_button_enabled");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "shortcut_keys_enabled"), "set_shortcut_keys_enabled", "is_shortcut_keys_enabled");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "selecting_enabled"), "set_selecting_enabled", "is_selecting_enabled");
@@ -1859,6 +1889,7 @@ void LineEdit::_bind_methods() {
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "caret_blink"), "cursor_set_blink_enabled", "cursor_get_blink_enabled");
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "caret_blink_speed", PROPERTY_HINT_RANGE, "0.1,10,0.01"), "cursor_set_blink_speed", "cursor_get_blink_speed");
ADD_PROPERTY(PropertyInfo(Variant::INT, "caret_position"), "set_cursor_position", "get_cursor_position");
+ ADD_PROPERTY(PropertyInfo(Variant::BOOL, "caret_force_displayed"), "cursor_set_force_displayed", "cursor_get_force_displayed");
}
LineEdit::LineEdit() {
@@ -1888,6 +1919,7 @@ LineEdit::LineEdit() {
draw_caret = true;
caret_blink_enabled = false;
+ caret_force_displayed = false;
caret_blink_timer = memnew(Timer);
add_child(caret_blink_timer);
caret_blink_timer->set_wait_time(0.65);
diff --git a/scene/gui/line_edit.h b/scene/gui/line_edit.h
index e6c964c906..d6cc1f1f11 100644
--- a/scene/gui/line_edit.h
+++ b/scene/gui/line_edit.h
@@ -90,6 +90,8 @@ private:
bool shortcut_keys_enabled;
+ bool virtual_keyboard_enabled = true;
+
Ref<Texture2D> right_icon;
struct Selection {
@@ -134,6 +136,7 @@ private:
void update_placeholder_width();
bool caret_blink_enabled;
+ bool caret_force_displayed;
bool draw_caret;
bool window_has_focus;
@@ -201,6 +204,9 @@ public:
float cursor_get_blink_speed() const;
void cursor_set_blink_speed(const float p_speed);
+ bool cursor_get_force_displayed() const;
+ void cursor_set_force_displayed(const bool p_enabled);
+
void copy_text();
void cut_text();
void paste_text();
@@ -227,6 +233,9 @@ public:
void set_shortcut_keys_enabled(bool p_enabled);
bool is_shortcut_keys_enabled() const;
+ void set_virtual_keyboard_enabled(bool p_enable);
+ bool is_virtual_keyboard_enabled() const;
+
void set_selecting_enabled(bool p_enabled);
bool is_selecting_enabled() const;
diff --git a/scene/gui/popup.cpp b/scene/gui/popup.cpp
index 5fc5f9b669..4e63cb66ea 100644
--- a/scene/gui/popup.cpp
+++ b/scene/gui/popup.cpp
@@ -126,6 +126,28 @@ Rect2i Popup::_popup_adjust_rect() const {
current.position.y = parent.position.y;
}
+ if (current.size.y > parent.size.y) {
+ current.size.y = parent.size.y;
+ }
+
+ if (current.size.x > parent.size.x) {
+ current.size.x = parent.size.x;
+ }
+
+ // Early out if max size not set.
+ Size2i max_size = get_max_size();
+ if (max_size <= Size2()) {
+ return current;
+ }
+
+ if (current.size.x > max_size.x) {
+ current.size.x = max_size.x;
+ }
+
+ if (current.size.y > max_size.y) {
+ current.size.y = max_size.y;
+ }
+
return current;
}
diff --git a/scene/gui/popup_menu.cpp b/scene/gui/popup_menu.cpp
index 6e19b820e0..40bcc243d1 100644
--- a/scene/gui/popup_menu.cpp
+++ b/scene/gui/popup_menu.cpp
@@ -35,7 +35,6 @@
#include "core/os/os.h"
#include "core/print_string.h"
#include "core/translation.h"
-#include "scene/gui/control.h"
String PopupMenu::_get_accel_text(int p_item) const {
ERR_FAIL_INDEX_V(p_item, items.size(), String());
@@ -52,7 +51,8 @@ Size2 PopupMenu::_get_contents_minimum_size() const {
int vseparation = get_theme_constant("vseparation");
int hseparation = get_theme_constant("hseparation");
- Size2 minsize = get_theme_stylebox("panel")->get_minimum_size();
+ 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;
@@ -64,13 +64,10 @@ Size2 PopupMenu::_get_contents_minimum_size() const {
for (int i = 0; i < items.size(); i++) {
Size2 size;
- if (!items[i].icon.is_null()) {
- Size2 icon_size = items[i].icon->get_size();
- size.height = MAX(icon_size.height, font_h);
- icon_w = MAX(icon_size.width + hseparation, icon_w);
- } else {
- size.height = font_h;
- }
+
+ Size2 icon_size = items[i].get_icon_size();
+ size.height = MAX(icon_size.height, font_h);
+ icon_w = MAX(icon_size.width, icon_w);
size.width += items[i].h_ofs;
@@ -104,39 +101,69 @@ Size2 PopupMenu::_get_contents_minimum_size() const {
minsize.width += check_w;
}
+ int height_limit = get_usable_parent_rect().size.height;
+ if (minsize.height > height_limit) {
+ minsize.height = height_limit;
+ }
+
return minsize;
}
+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;
+ }
+
+ // Subtract a separator which is not needed for the last item.
+ return items_total_height - vsep;
+}
+
+void PopupMenu::_scroll_to_item(int p_item) {
+ ERR_FAIL_INDEX(p_item, items.size());
+ ERR_FAIL_COND(p_item < 0);
+
+ // Scroll item into view (upwards)
+ if (items[p_item]._ofs_cache < -control->get_position().y) {
+ int amnt_over = items[p_item]._ofs_cache + control->get_position().y;
+ scroll_container->set_v_scroll(scroll_container->get_v_scroll() + amnt_over);
+ }
+
+ // Scroll item into view (downwards)
+ if (items[p_item]._ofs_cache + items[p_item]._height_cache > -control->get_position().y + scroll_container->get_size().height) {
+ int amnt_over = items[p_item]._ofs_cache + items[p_item]._height_cache + control->get_position().y - scroll_container->get_size().height;
+ scroll_container->set_v_scroll(scroll_container->get_v_scroll() + amnt_over);
+ }
+}
+
int PopupMenu::_get_mouse_over(const Point2 &p_over) const {
if (p_over.x < 0 || p_over.x >= get_size().width) {
return -1;
}
- Ref<StyleBox> style = get_theme_stylebox("panel");
+ 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 ofs = style->get_offset() + Point2(0, vseparation / 2);
if (ofs.y > p_over.y) {
return -1;
}
- Ref<Font> font = get_theme_font("font");
- int vseparation = get_theme_constant("vseparation");
- float font_h = font->get_height();
-
for (int i = 0; i < items.size(); i++) {
- ofs.y += vseparation;
- float h;
-
- if (!items[i].icon.is_null()) {
- Size2 icon_size = items[i].icon->get_size();
- h = MAX(icon_size.height, font_h);
- } else {
- h = font_h;
+ if (i > 0) {
+ ofs.y += vseparation;
}
- ofs.y += h;
- if (p_over.y < ofs.y) {
+ ofs.y += MAX(items[i].get_icon_size().height, font_h);
+
+ if (p_over.y - control->get_position().y < ofs.y) {
return i;
}
}
@@ -147,43 +174,51 @@ int PopupMenu::_get_mouse_over(const Point2 &p_over) const {
void PopupMenu::_activate_submenu(int over) {
Node *n = get_node(items[over].submenu);
ERR_FAIL_COND_MSG(!n, "Item subnode does not exist: " + items[over].submenu + ".");
- Popup *pm = Object::cast_to<Popup>(n);
- ERR_FAIL_COND_MSG(!pm, "Item subnode is not a Popup: " + items[over].submenu + ".");
- if (pm->is_visible()) {
+ Popup *submenu_popup = Object::cast_to<Popup>(n);
+ ERR_FAIL_COND_MSG(!submenu_popup, "Item subnode is not a Popup: " + items[over].submenu + ".");
+ if (submenu_popup->is_visible()) {
return; //already visible!
}
- Point2 p = get_position();
- Rect2 pr(p, get_size());
Ref<StyleBox> style = get_theme_stylebox("panel");
+ int vsep = get_theme_constant("vseparation");
- Point2 pos = p + Point2(get_size().width, items[over]._ofs_cache - style->get_offset().y);
- Size2 size = pm->get_size();
- // fix pos
- if (pos.x + size.width > get_parent_rect().size.width) {
- pos.x = p.x - size.width;
+ Point2 this_pos = get_position();
+ Rect2 this_rect(this_pos, get_size());
+
+ float scroll_offset = control->get_position().y;
+
+ Point2 submenu_pos = this_pos + Point2(this_rect.size.width, items[over]._ofs_cache + scroll_offset);
+ Size2 submenu_size = submenu_popup->get_size();
+
+ // Fix pos if going outside parent rect
+ if (submenu_pos.x + submenu_size.width > get_parent_rect().size.width) {
+ submenu_pos.x = this_pos.x - submenu_size.width;
}
- pm->set_position(pos);
- // pm->set_scale(get_global_transform().get_scale());
- pm->popup();
-
- PopupMenu *pum = Object::cast_to<PopupMenu>(pm);
- if (pum) {
- pr.position -= pum->get_position();
- pum->clear_autohide_areas();
- pum->add_autohide_area(Rect2(pr.position.x, pr.position.y, pr.size.x, items[over]._ofs_cache));
- if (over < items.size() - 1) {
- int from = items[over + 1]._ofs_cache;
- pum->add_autohide_area(Rect2(pr.position.x, pr.position.y + from, pr.size.x, pr.size.y - from));
+ submenu_popup->set_position(submenu_pos);
+ submenu_popup->set_as_minsize(); // Shrink the popup size to it's contents.
+ submenu_popup->popup();
+
+ // Set autohide areas
+ PopupMenu *submenu_pum = Object::cast_to<PopupMenu>(submenu_popup);
+ if (submenu_pum) {
+ // Make the position of the parent popup relative to submenu popup
+ this_rect.position = this_rect.position - submenu_pum->get_position();
+
+ // Autohide area above the submenu item
+ submenu_pum->clear_autohide_areas();
+ submenu_pum->add_autohide_area(Rect2(this_rect.position.x, this_rect.position.y, this_rect.size.x, items[over]._ofs_cache + scroll_offset + style->get_offset().height - vsep / 2));
+
+ // If there is an area below the submenu item, add an autohide area there.
+ if (items[over]._ofs_cache + items[over]._height_cache + scroll_offset <= control->get_size().height) {
+ int from = items[over]._ofs_cache + items[over]._height_cache + scroll_offset + vsep / 2 + style->get_offset().height;
+ submenu_pum->add_autohide_area(Rect2(this_rect.position.x, this_rect.position.y + from, this_rect.size.x, this_rect.size.y - from));
}
}
}
void PopupMenu::_submenu_timeout() {
- //if (!has_focus()) {
- // return; //do not activate if not has focus
- //}
if (mouse_over == submenu_over) {
_activate_submenu(mouse_over);
}
@@ -191,70 +226,34 @@ void PopupMenu::_submenu_timeout() {
submenu_over = -1;
}
-void PopupMenu::_scroll(float p_factor, const Point2 &p_over) {
- int vseparation = get_theme_constant("vseparation");
- Ref<Font> font = get_theme_font("font");
-
- Rect2 visible_rect = get_usable_parent_rect();
-
- int dy = (vseparation + font->get_height()) * 3 * p_factor;
- if (dy > 0) {
- const float global_top = get_position().y;
- const float limit = global_top < visible_rect.position.y ? visible_rect.position.y - global_top : 0;
- dy = MIN(dy, limit);
- } else if (dy < 0) {
- const float global_bottom = get_position().y + get_size().y;
- const float viewport_height = visible_rect.position.y + visible_rect.size.y;
- const float limit = global_bottom > viewport_height ? global_bottom - viewport_height : 0;
- dy = -MIN(-dy, limit);
- }
-
- if (dy == 0) {
- return;
- }
-
- set_position(get_position() + Vector2(0, dy));
-
- Ref<InputEventMouseMotion> ie;
- ie.instance();
- ie->set_position(p_over - Vector2(0, dy));
- _gui_input(ie);
-}
-
void PopupMenu::_gui_input(const Ref<InputEvent> &p_event) {
- if (p_event->is_action("ui_down") && p_event->is_pressed()) {
+ if (p_event->is_action("ui_down") && p_event->is_pressed() && mouse_over != items.size() - 1) {
int search_from = mouse_over + 1;
if (search_from >= items.size()) {
search_from = 0;
}
for (int i = search_from; i < items.size(); i++) {
- if (i < 0 || i >= items.size()) {
- continue;
- }
-
if (!items[i].separator && !items[i].disabled) {
mouse_over = i;
emit_signal("id_focused", i);
+ _scroll_to_item(i);
control->update();
set_input_as_handled();
break;
}
}
- } else if (p_event->is_action("ui_up") && p_event->is_pressed()) {
+ } else if (p_event->is_action("ui_up") && p_event->is_pressed() && mouse_over != 0) {
int search_from = mouse_over - 1;
if (search_from < 0) {
search_from = items.size() - 1;
}
for (int i = search_from; i >= 0; i--) {
- if (i >= items.size()) {
- continue;
- }
-
if (!items[i].separator && !items[i].disabled) {
mouse_over = i;
emit_signal("id_focused", i);
+ _scroll_to_item(i);
control->update();
set_input_as_handled();
break;
@@ -282,60 +281,60 @@ 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 = control->get_global_rect();
+ float scroll_width = scroll_container->get_v_scrollbar()->is_visible_in_tree() ? scroll_container->get_v_scrollbar()->get_size().width : 0;
+ item_clickable_area.set_size(Size2(item_clickable_area.size.width - scroll_width, item_clickable_area.size.height));
+
Ref<InputEventMouseButton> b = p_event;
if (b.is_valid()) {
- if (b->is_pressed()) {
+ if (!item_clickable_area.has_point(b->get_global_position())) {
return;
}
int button_idx = b->get_button_index();
- switch (button_idx) {
- case BUTTON_WHEEL_DOWN: {
- _scroll(-b->get_factor(), b->get_position());
- } break;
- case BUTTON_WHEEL_UP: {
- _scroll(b->get_factor(), b->get_position());
- } break;
- default: {
- // Allow activating item by releasing the LMB or any that was down when the popup appeared
- if (button_idx == BUTTON_LEFT || (initial_button_mask & (1 << (button_idx - 1)))) {
- bool was_during_grabbed_click = during_grabbed_click;
- during_grabbed_click = false;
- initial_button_mask = 0;
-
- int over = _get_mouse_over(b->get_position());
-
- if (invalidated_click) {
- invalidated_click = false;
- break;
- }
- if (over < 0) {
- if (!was_during_grabbed_click) {
- hide();
- }
- break; //non-activable
+ if (b->is_pressed() || (!b->is_pressed() && during_grabbed_click)) {
+ // Allow activating item by releasing the LMB or any that was down when the popup appeared.
+ // However, if button was not held when opening menu, do not allow release to activate item.
+ if (button_idx == BUTTON_LEFT || (initial_button_mask & (1 << (button_idx - 1)))) {
+ bool was_during_grabbed_click = during_grabbed_click;
+ during_grabbed_click = false;
+ initial_button_mask = 0;
+
+ int over = _get_mouse_over(b->get_position());
+
+ if (invalidated_click) {
+ invalidated_click = false;
+ return;
+ }
+ if (over < 0) {
+ if (!was_during_grabbed_click) {
+ hide();
}
+ return;
+ }
- if (items[over].separator || items[over].disabled) {
- break;
- }
+ if (items[over].separator || items[over].disabled) {
+ return;
+ }
- if (items[over].submenu != "") {
- _activate_submenu(over);
- return;
- }
- activate_item(over);
+ if (items[over].submenu != "") {
+ _activate_submenu(over);
+ return;
}
+ activate_item(over);
}
}
-
- //control->update();
}
Ref<InputEventMouseMotion> m = p_event;
if (m.is_valid()) {
+ if (!item_clickable_area.has_point(m->get_global_position())) {
+ return;
+ }
+
if (invalidated_click) {
moved += m->get_relative();
if (moved.length() > 4) {
@@ -370,11 +369,6 @@ void PopupMenu::_gui_input(const Ref<InputEvent> &p_event) {
}
}
- Ref<InputEventPanGesture> pan_gesture = p_event;
- if (pan_gesture.is_valid()) {
- _scroll(-pan_gesture->get_delta().y, pan_gesture->get_position());
- }
-
Ref<InputEventKey> k = p_event;
if (allow_search && k.is_valid() && k->get_unicode() && k->is_pressed()) {
@@ -407,6 +401,7 @@ void PopupMenu::_gui_input(const Ref<InputEvent> &p_event) {
if (items[i].text.findn(search_string) == 0) {
mouse_over = i;
emit_signal("id_focused", i);
+ _scroll_to_item(i);
control->update();
set_input_as_handled();
break;
@@ -415,9 +410,13 @@ void PopupMenu::_gui_input(const Ref<InputEvent> &p_event) {
}
}
-void PopupMenu::_draw() {
+void PopupMenu::_draw_items() {
+ control->set_custom_minimum_size(Size2(0, _get_items_total_height()));
RID ci = control->get_canvas_item();
- Size2 size = get_size();
+
+ Size2 margin_size;
+ 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");
Ref<StyleBox> style = get_theme_stylebox("panel");
Ref<StyleBox> hover = get_theme_stylebox("hover");
@@ -430,8 +429,6 @@ void PopupMenu::_draw() {
Ref<StyleBox> labeled_separator_left = get_theme_stylebox("labeled_separator_left");
Ref<StyleBox> labeled_separator_right = get_theme_stylebox("labeled_separator_right");
- style->draw(ci, Rect2(Point2(), get_size()));
- Point2 ofs = style->get_offset();
int vseparation = get_theme_constant("vseparation");
int hseparation = get_theme_constant("hseparation");
Color font_color = get_theme_color("font_color");
@@ -440,13 +437,14 @@ void PopupMenu::_draw() {
Color font_color_hover = get_theme_color("font_color_hover");
float font_h = font->get_height();
- // Add the check and the wider icon to the offset of all items.
+ 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;
+
+ // Find the widest icon and whether any items have a checkbox, and store the offsets for each.
float icon_ofs = 0.0;
bool has_check = false;
for (int i = 0; i < items.size(); i++) {
- if (!items[i].icon.is_null()) {
- icon_ofs = MAX(items[i].icon->get_size().width, icon_ofs);
- }
+ icon_ofs = MAX(items[i].get_icon_size().width, icon_ofs);
if (items[i].checkable_type) {
has_check = true;
@@ -461,65 +459,68 @@ void PopupMenu::_draw() {
check_ofs = MAX(get_theme_icon("checked")->get_width(), get_theme_icon("radio_checked")->get_width()) + hseparation;
}
+ Point2 ofs = Point2();
+
+ // Loop through all items and draw each.
for (int i = 0; i < items.size(); i++) {
+ // If not the first item, add the separation space between items.
if (i > 0) {
ofs.y += vseparation;
}
- Point2 item_ofs = ofs;
- Size2 icon_size;
- float h;
- if (!items[i].icon.is_null()) {
- icon_size = items[i].icon->get_size();
- h = MAX(icon_size.height, font_h);
- } else {
- h = font_h;
- }
+ Point2 item_ofs = ofs;
+ Size2 icon_size = items[i].get_icon_size();
+ float h = MAX(icon_size.height, font_h);
if (i == mouse_over) {
- hover->draw(ci, Rect2(item_ofs + Point2(-hseparation, -vseparation / 2), Size2(get_size().width - style->get_minimum_size().width + hseparation * 2, h + vseparation)));
+ hover->draw(ci, Rect2(item_ofs + Point2(-hseparation, -vseparation / 2), Size2(display_width + hseparation * 2, h + vseparation)));
}
String text = items[i].xl_text;
+ // Separator
item_ofs.x += items[i].h_ofs;
if (items[i].separator) {
int sep_h = separator->get_center_size().height + separator->get_minimum_size().height;
if (text != String()) {
- int ss = font->get_string_size(text).width;
- int center = (get_size().width) / 2;
- int l = center - ss / 2;
- int r = center + ss / 2;
- if (l > item_ofs.x) {
- labeled_separator_left->draw(ci, Rect2(item_ofs + Point2(0, Math::floor((h - sep_h) / 2.0)), Size2(MAX(0, l - item_ofs.x), sep_h)));
+ int text_size = font->get_string_size(text).width;
+ int text_center = display_width / 2;
+ int text_left = text_center - text_size / 2;
+ int text_right = text_center + text_size / 2;
+ if (text_left > item_ofs.x) {
+ labeled_separator_left->draw(ci, Rect2(item_ofs + Point2(0, Math::floor((h - sep_h) / 2.0)), Size2(MAX(0, text_left - item_ofs.x), sep_h)));
}
- if (r < get_size().width - style->get_margin(MARGIN_RIGHT)) {
- labeled_separator_right->draw(ci, Rect2(Point2(r, item_ofs.y + Math::floor((h - sep_h) / 2.0)), Size2(MAX(0, get_size().width - style->get_margin(MARGIN_RIGHT) - r), sep_h)));
+ if (text_right < display_width) {
+ labeled_separator_right->draw(ci, Rect2(Point2(text_right, item_ofs.y + Math::floor((h - sep_h) / 2.0)), Size2(MAX(0, display_width - text_right), sep_h)));
}
} else {
- separator->draw(ci, Rect2(item_ofs + Point2(0, Math::floor((h - sep_h) / 2.0)), Size2(get_size().width - style->get_minimum_size().width, sep_h)));
+ separator->draw(ci, Rect2(item_ofs + Point2(0, Math::floor((h - sep_h) / 2.0)), Size2(display_width, sep_h)));
}
}
Color icon_color(1, 1, 1, items[i].disabled ? 0.5 : 1);
+ // 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);
}
+ // 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);
}
+ // Submenu arrow on right hand side
if (items[i].submenu != "") {
- submenu->draw(ci, Point2(size.width - style->get_margin(MARGIN_RIGHT) - submenu->get_width(), item_ofs.y + Math::floor(h - submenu->get_height()) / 2), icon_color);
+ submenu->draw(ci, Point2(display_width - 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 = (get_size().width - font->get_string_size(text).width) / 2;
+ 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);
}
} else {
@@ -527,19 +528,27 @@ void PopupMenu::_draw() {
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));
}
+ // Accelerator / Shortcut
if (items[i].accel || (items[i].shortcut.is_valid() && items[i].shortcut->is_valid())) {
- //accelerator
- String text2 = _get_accel_text(i);
- item_ofs.x = size.width - style->get_margin(MARGIN_RIGHT) - font->get_string_size(text2).width;
- font->draw(ci, item_ofs + Point2(0, Math::floor((h - font_h) / 2.0)), text2, i == mouse_over ? font_color_hover : font_color_accel);
+ 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);
}
+ // Cache the item vertical offset from the first item and the height
items.write[i]._ofs_cache = ofs.y;
+ items.write[i]._height_cache = h;
ofs.y += h;
}
}
+void PopupMenu::_draw_background() {
+ Ref<StyleBox> style = get_theme_stylebox("panel");
+ RID ci2 = margin_container->get_canvas_item();
+ style->draw(ci2, Rect2(Point2(), margin_container->get_size()));
+}
+
void PopupMenu::_notification(int p_what) {
switch (p_what) {
case NOTIFICATION_ENTER_TREE: {
@@ -617,6 +626,13 @@ void PopupMenu::_notification(int p_what) {
if (get_window_id() != DisplayServer::INVALID_WINDOW_ID) {
set_process_internal(true);
}
+
+ // Set margin on the margin container
+ Ref<StyleBox> panel_style = get_theme_stylebox("panel");
+ margin_container->add_theme_constant_override("margin_top", panel_style->get_margin(Margin::MARGIN_TOP));
+ margin_container->add_theme_constant_override("margin_bottom", panel_style->get_margin(Margin::MARGIN_BOTTOM));
+ margin_container->add_theme_constant_override("margin_left", panel_style->get_margin(Margin::MARGIN_LEFT));
+ margin_container->add_theme_constant_override("margin_right", panel_style->get_margin(Margin::MARGIN_RIGHT));
}
} break;
}
@@ -1425,16 +1441,32 @@ void PopupMenu::_bind_methods() {
void PopupMenu::popup(const Rect2 &p_bounds) {
moved = Vector2();
invalidated_click = true;
+ set_as_minsize();
Popup::popup(p_bounds);
}
PopupMenu::PopupMenu() {
+ // Margin Container
+ margin_container = memnew(MarginContainer);
+ margin_container->set_anchors_and_margins_preset(Control::PRESET_WIDE);
+ add_child(margin_container);
+ margin_container->connect("draw", callable_mp(this, &PopupMenu::_draw_background));
+
+ // Scroll Container
+ scroll_container = memnew(ScrollContainer);
+ scroll_container->set_clip_contents(true);
+ margin_container->add_child(scroll_container);
+
+ // The control which will display the items
control = memnew(Control);
- add_child(control);
-
+ control->set_clip_contents(false);
control->set_anchors_and_margins_preset(Control::PRESET_WIDE);
+ control->set_h_size_flags(Control::SIZE_EXPAND_FILL);
+ control->set_v_size_flags(Control::SIZE_EXPAND_FILL);
+ scroll_container->add_child(control);
+ control->connect("draw", callable_mp(this, &PopupMenu::_draw_items));
+
connect("window_input", callable_mp(this, &PopupMenu::_gui_input));
- control->connect("draw", callable_mp(this, &PopupMenu::_draw));
mouse_over = -1;
submenu_over = -1;
diff --git a/scene/gui/popup_menu.h b/scene/gui/popup_menu.h
index 43cd7a54e9..45a3336747 100644
--- a/scene/gui/popup_menu.h
+++ b/scene/gui/popup_menu.h
@@ -31,7 +31,9 @@
#ifndef POPUP_MENU_H
#define POPUP_MENU_H
+#include "scene/gui/margin_container.h"
#include "scene/gui/popup.h"
+#include "scene/gui/scroll_container.h"
#include "scene/gui/shortcut.h"
class PopupMenu : public Popup {
@@ -57,11 +59,17 @@ class PopupMenu : public Popup {
String tooltip;
uint32_t accel;
int _ofs_cache;
+ int _height_cache;
int h_ofs;
Ref<ShortCut> shortcut;
bool shortcut_is_global;
bool shortcut_is_disabled;
+ // Returns (0,0) if icon is null.
+ Size2 get_icon_size() const {
+ return icon.is_null() ? Size2() : icon->get_size();
+ }
+
Item() {
checked = false;
checkable_type = CHECKABLE_TYPE_NONE;
@@ -71,6 +79,7 @@ class PopupMenu : public Popup {
accel = 0;
disabled = false;
_ofs_cache = 0;
+ _height_cache = 0;
h_ofs = 0;
shortcut_is_global = false;
shortcut_is_disabled = false;
@@ -88,7 +97,10 @@ class PopupMenu : public Popup {
String _get_accel_text(int p_item) const;
int _get_mouse_over(const Point2 &p_over) const;
virtual Size2 _get_contents_minimum_size() const override;
- void _scroll(float p_factor, const Point2 &p_over);
+
+ int _get_items_total_height() const;
+ void _scroll_to_item(int p_item);
+
void _gui_input(const Ref<InputEvent> &p_event);
void _activate_submenu(int over);
void _submenu_timeout();
@@ -111,9 +123,12 @@ class PopupMenu : public Popup {
uint64_t search_time_msec;
String search_string;
+ MarginContainer *margin_container;
+ ScrollContainer *scroll_container;
Control *control;
- void _draw();
+ void _draw_items();
+ void _draw_background();
protected:
friend class MenuButton;
diff --git a/scene/gui/rich_text_label.cpp b/scene/gui/rich_text_label.cpp
index 22e5bd55f3..29337e20f3 100644
--- a/scene/gui/rich_text_label.cpp
+++ b/scene/gui/rich_text_label.cpp
@@ -188,7 +188,7 @@ int RichTextLabel::_process_line(ItemFrame *p_frame, const Vector2 &p_ofs, int &
wofs += line_ofs;
}
- int begin = wofs;
+ int begin = margin;
Ref<Font> cfont = _find_font(it);
if (cfont.is_null()) {
@@ -1020,7 +1020,8 @@ void RichTextLabel::_notification(int p_what) {
visible_line_count = 0;
while (y < size.height && from_line < main->lines.size()) {
- visible_line_count += _process_line(main, text_rect.get_position(), y, text_rect.get_size().width - scroll_w, from_line, PROCESS_DRAW, base_font, base_color, font_color_shadow, use_outline, shadow_ofs, Point2i(), nullptr, nullptr, nullptr, total_chars);
+ visible_line_count++;
+ _process_line(main, text_rect.get_position(), y, text_rect.get_size().width - scroll_w, from_line, PROCESS_DRAW, base_font, base_color, font_color_shadow, use_outline, shadow_ofs, Point2i(), nullptr, nullptr, nullptr, total_chars);
total_chars += main->lines[from_line].char_count;
from_line++;
@@ -2769,7 +2770,7 @@ void RichTextLabel::_bind_methods() {
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "selection_enabled"), "set_selection_enabled", "is_selection_enabled");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "override_selected_font_color"), "set_override_selected_font_color", "is_overriding_selected_font_color");
- ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "custom_effects", PROPERTY_HINT_RESOURCE_TYPE, "17/17:RichTextEffect", (PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_SCRIPT_VARIABLE), "RichTextEffect"), "set_effects", "get_effects");
+ ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "custom_effects", PROPERTY_HINT_ARRAY_TYPE, "RichTextEffect", (PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_SCRIPT_VARIABLE)), "set_effects", "get_effects");
ADD_SIGNAL(MethodInfo("meta_clicked", PropertyInfo(Variant::NIL, "meta", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NIL_IS_VARIANT)));
ADD_SIGNAL(MethodInfo("meta_hover_started", PropertyInfo(Variant::NIL, "meta", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NIL_IS_VARIANT)));
@@ -2897,7 +2898,7 @@ Dictionary RichTextLabel::parse_expressions_for_values(Vector<String> p_expressi
a.append(false);
}
} else if (!decimal.search(values[j]).is_null()) {
- a.append(values[j].to_double());
+ a.append(values[j].to_float());
} else if (!numerical.search(values[j]).is_null()) {
a.append(values[j].to_int());
} else {
diff --git a/scene/gui/scroll_bar.cpp b/scene/gui/scroll_bar.cpp
index 4db6ca2949..0e9ef71892 100644
--- a/scene/gui/scroll_bar.cpp
+++ b/scene/gui/scroll_bar.cpp
@@ -522,7 +522,7 @@ void ScrollBar::_drag_node_input(const Ref<InputEvent> &p_input) {
drag_node_accum = Vector2();
last_drag_node_accum = Vector2();
drag_node_from = Vector2(orientation == HORIZONTAL ? get_value() : 0, orientation == VERTICAL ? get_value() : 0);
- drag_node_touching = !DisplayServer::get_singleton()->screen_is_touchscreen(DisplayServer::get_singleton()->window_get_current_screen(get_viewport()->get_window_id()));
+ drag_node_touching = DisplayServer::get_singleton()->screen_is_touchscreen(DisplayServer::get_singleton()->window_get_current_screen(get_viewport()->get_window_id()));
drag_node_touching_deaccel = false;
time_since_motion = 0;
diff --git a/scene/gui/spin_box.cpp b/scene/gui/spin_box.cpp
index 3670f13705..ae2f99e91d 100644
--- a/scene/gui/spin_box.cpp
+++ b/scene/gui/spin_box.cpp
@@ -62,8 +62,8 @@ void SpinBox::_text_entered(const String &p_string) {
Variant value = expr->execute(Array(), nullptr, false);
if (value.get_type() != Variant::NIL) {
set_value(value);
- _value_changed(0);
}
+ _value_changed(0);
}
LineEdit *SpinBox::get_line_edit() {
diff --git a/scene/gui/text_edit.cpp b/scene/gui/text_edit.cpp
index 3860ce61e9..d6d8e74748 100644
--- a/scene/gui/text_edit.cpp
+++ b/scene/gui/text_edit.cpp
@@ -60,14 +60,6 @@ static bool _is_char(CharType c) {
return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || c == '_';
}
-static bool _is_number(CharType c) {
- return (c >= '0' && c <= '9');
-}
-
-static bool _is_hex_symbol(CharType c) {
- return ((c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F'));
-}
-
static bool _is_pair_right_symbol(CharType c) {
return c == '"' ||
c == '\'' ||
@@ -136,94 +128,7 @@ void TextEdit::Text::_update_line_cache(int p_line) const {
}
text.write[p_line].width_cache = w;
-
text.write[p_line].wrap_amount_cache = -1;
-
- // Update regions.
-
- text.write[p_line].region_info.clear();
-
- for (int i = 0; i < len; i++) {
- if (!_is_symbol(str[i])) {
- continue;
- }
- if (str[i] == '\\') {
- i++; // Skip quoted anything.
- continue;
- }
-
- int left = len - i;
-
- for (int j = 0; j < color_regions->size(); j++) {
- const ColorRegion &cr = color_regions->operator[](j);
-
- /* BEGIN */
-
- int lr = cr.begin_key.length();
- const CharType *kc;
- bool match;
-
- if (lr != 0 && lr <= left) {
- kc = cr.begin_key.c_str();
-
- match = true;
-
- for (int k = 0; k < lr; k++) {
- if (kc[k] != str[i + k]) {
- match = false;
- break;
- }
- }
-
- if (match) {
- ColorRegionInfo cri;
- cri.end = false;
- cri.region = j;
- text.write[p_line].region_info[i] = cri;
- i += lr - 1;
-
- break;
- }
- }
-
- /* END */
-
- lr = cr.end_key.length();
- if (lr != 0 && lr <= left) {
- kc = cr.end_key.c_str();
-
- match = true;
-
- for (int k = 0; k < lr; k++) {
- if (kc[k] != str[i + k]) {
- match = false;
- break;
- }
- }
-
- if (match) {
- ColorRegionInfo cri;
- cri.end = true;
- cri.region = j;
- text.write[p_line].region_info[i] = cri;
- i += lr - 1;
-
- break;
- }
- }
- }
- }
-}
-
-const Map<int, TextEdit::Text::ColorRegionInfo> &TextEdit::Text::get_color_region_info(int p_line) const {
- static Map<int, ColorRegionInfo> cri;
- ERR_FAIL_INDEX_V(p_line, text.size(), cri);
-
- if (text[p_line].width_cache == -1) {
- _update_line_cache(p_line);
- }
-
- return text[p_line].region_info;
}
int TextEdit::Text::get_line_width(int p_line) const {
@@ -601,7 +506,6 @@ void TextEdit::_notification(int p_what) {
case NOTIFICATION_THEME_CHANGED: {
_update_caches();
_update_wrap_at();
- syntax_highlighting_cache.clear();
} break;
case NOTIFICATION_WM_WINDOW_FOCUS_IN: {
window_has_focus = true;
@@ -640,6 +544,14 @@ void TextEdit::_notification(int p_what) {
adjust_viewport_to_cursor();
first_draw = false;
}
+
+ /* Prevent the resource getting lost between the editor and game. */
+ if (Engine::get_singleton()->is_editor_hint()) {
+ if (syntax_highlighter.is_valid() && syntax_highlighter->get_text_edit() != this) {
+ syntax_highlighter->set_text_edit(this);
+ }
+ }
+
Size2 size = get_size();
if ((!has_focus() && !menu->has_focus()) || !window_has_focus) {
draw_caret = false;
@@ -711,10 +623,8 @@ void TextEdit::_notification(int p_what) {
Color color = readonly ? cache.font_color_readonly : cache.font_color;
- if (syntax_coloring) {
- if (cache.background_color.a > 0.01) {
- RenderingServer::get_singleton()->canvas_item_add_rect(ci, Rect2(Point2i(), get_size()), cache.background_color);
- }
+ if (cache.background_color.a > 0.01) {
+ RenderingServer::get_singleton()->canvas_item_add_rect(ci, Rect2(Point2i(), get_size()), cache.background_color);
}
if (line_length_guidelines) {
@@ -937,10 +847,7 @@ void TextEdit::_notification(int p_what) {
break;
}
- Map<int, HighlighterInfo> color_map;
- if (syntax_coloring) {
- color_map = _get_line_syntax_highlighting(minimap_line);
- }
+ Dictionary color_map = _get_line_syntax_highlighting(minimap_line);
Color current_color = cache.font_color;
if (readonly) {
@@ -978,15 +885,13 @@ void TextEdit::_notification(int p_what) {
int characters = 0;
int tabs = 0;
for (int j = 0; j < str.length(); j++) {
- if (syntax_coloring) {
- if (color_map.has(last_wrap_column + j)) {
- current_color = color_map[last_wrap_column + j].color;
- if (readonly) {
- current_color.a = cache.font_color_readonly.a;
- }
+ if (color_map.has(last_wrap_column + j)) {
+ current_color = color_map[last_wrap_column + j].get("color");
+ if (readonly) {
+ current_color.a = cache.font_color_readonly.a;
}
- color = current_color;
}
+ color = current_color;
if (j == 0) {
previous_color = color;
@@ -1060,10 +965,8 @@ void TextEdit::_notification(int p_what) {
const String &fullstr = text[line];
- Map<int, HighlighterInfo> color_map;
- if (syntax_coloring) {
- color_map = _get_line_syntax_highlighting(line);
- }
+ Dictionary color_map = _get_line_syntax_highlighting(line);
+
// Ensure we at least use the font color.
Color current_color = readonly ? cache.font_color_readonly : cache.font_color;
@@ -1253,15 +1156,13 @@ void TextEdit::_notification(int p_what) {
// Loop through characters in one line.
int j = 0;
for (; j < str.length(); j++) {
- if (syntax_coloring) {
- if (color_map.has(last_wrap_column + j)) {
- current_color = color_map[last_wrap_column + j].color;
- if (readonly && current_color.a > cache.font_color_readonly.a) {
- current_color.a = cache.font_color_readonly.a;
- }
+ if (color_map.has(last_wrap_column + j)) {
+ current_color = color_map[last_wrap_column + j].get("color");
+ if (readonly && current_color.a > cache.font_color_readonly.a) {
+ current_color.a = cache.font_color_readonly.a;
}
- color = current_color;
}
+ color = current_color;
int char_w;
@@ -1447,7 +1348,7 @@ void TextEdit::_notification(int p_what) {
if (cursor.column == last_wrap_column + j && cursor.line == line && cursor_wrap_index == line_wrap_index && block_caret && draw_caret && !insert_mode) {
color = cache.caret_background_color;
- } else if (!syntax_coloring && block_caret) {
+ } else if (block_caret) {
color = readonly ? cache.font_color_readonly : cache.font_color;
}
@@ -1609,12 +1510,6 @@ void TextEdit::_notification(int p_what) {
for (int i = 0; i < lines; i++) {
int l = line_from + i;
ERR_CONTINUE(l < 0 || l >= completion_options_size);
- Color text_color = cache.completion_font_color;
- for (int j = 0; j < color_regions.size(); j++) {
- if (completion_options[l].insert_text.begins_with(color_regions[j].begin_key)) {
- text_color = color_regions[j].color;
- }
- }
int yofs = (get_row_height() - cache.font->get_height()) / 2;
Point2 title_pos(completion_rect.position.x, completion_rect.position.y + i * get_row_height() + cache.font->get_ascent() + yofs);
@@ -1630,7 +1525,7 @@ void TextEdit::_notification(int p_what) {
}
title_pos.x = icon_area.position.x + icon_area.size.width + icon_hsep;
- draw_string(cache.font, title_pos, completion_options[l].display, text_color, completion_rect.size.width - (icon_area_size.x + icon_hsep));
+ 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));
}
if (scrollw) {
@@ -1736,8 +1631,8 @@ void TextEdit::_notification(int p_what) {
DisplayServer::get_singleton()->window_set_ime_position(get_global_position() + cursor_pos, get_viewport()->get_window_id());
}
- if (DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_VIRTUAL_KEYBOARD)) {
- DisplayServer::get_singleton()->virtual_keyboard_show(get_text(), get_global_rect());
+ if (DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_VIRTUAL_KEYBOARD) && virtual_keyboard_enabled) {
+ DisplayServer::get_singleton()->virtual_keyboard_show(get_text(), get_global_rect(), true);
}
} break;
case NOTIFICATION_FOCUS_EXIT: {
@@ -1752,7 +1647,7 @@ void TextEdit::_notification(int p_what) {
ime_text = "";
ime_selection = Point2();
- if (DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_VIRTUAL_KEYBOARD)) {
+ if (DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_VIRTUAL_KEYBOARD) && virtual_keyboard_enabled) {
DisplayServer::get_singleton()->virtual_keyboard_hide();
}
} break;
@@ -1977,6 +1872,9 @@ void TextEdit::indent_right() {
for (int i = start_line; i <= end_line; i++) {
String line_text = get_line(i);
+ if (line_text.size() == 0 && is_selection_active()) {
+ continue;
+ }
if (indent_using_spaces) {
// We don't really care where selection is - we just need to know indentation level at the beginning of the line.
int left = _find_first_non_whitespace_column_of_line(line_text);
@@ -2851,7 +2749,6 @@ void TextEdit::_gui_input(const Ref<InputEvent> &p_gui_input) {
// Indent once again if previous line will end with ':','{','[','(' and the line is not a comment
// (i.e. colon/brace precedes current cursor position).
if (cursor.column > 0) {
- const Map<int, Text::ColorRegionInfo> &cri_map = text.get_color_region_info(cursor.line);
bool indent_char_found = false;
bool should_indent = false;
char indent_char = ':';
@@ -2870,7 +2767,7 @@ void TextEdit::_gui_input(const Ref<InputEvent> &p_gui_input) {
continue;
}
- if (indent_char_found && cri_map.has(i) && (color_regions[cri_map[i].region].begin_key == "#" || color_regions[cri_map[i].region].begin_key == "//")) {
+ if (indent_char_found && is_line_comment(i)) {
should_indent = true;
break;
} else if (indent_char_found && !_is_whitespace(c)) {
@@ -3710,7 +3607,7 @@ void TextEdit::_gui_input(const Ref<InputEvent> &p_gui_input) {
return;
}
- if (!keycode_handled && !k->get_command()) { // For German keyboards.
+ if (!keycode_handled && (!k->get_command() || (k->get_command() && k->get_alt()))) { // For German keyboards.
if (k->get_unicode() >= 32) {
if (readonly) {
@@ -3943,7 +3840,7 @@ void TextEdit::_base_insert_text(int p_line, int p_char, const String &p_text, i
}
text_changed_dirty = true;
}
- _line_edited_from(p_line);
+ emit_signal("line_edited_from", p_line);
}
String TextEdit::_base_get_text(int p_from_line, int p_from_column, int p_to_line, int p_to_column) const {
@@ -4006,7 +3903,7 @@ void TextEdit::_base_remove_text(int p_from_line, int p_from_column, int p_to_li
}
text_changed_dirty = true;
}
- _line_edited_from(p_from_line);
+ emit_signal("line_edited_from", p_from_line);
}
void TextEdit::_insert_text(int p_line, int p_char, const String &p_text, int *r_end_line, int *r_end_char) {
@@ -4126,22 +4023,6 @@ void TextEdit::_insert_text_at_cursor(const String &p_text) {
update();
}
-void TextEdit::_line_edited_from(int p_line) {
- int cache_size = color_region_cache.size();
- for (int i = p_line; i < cache_size; i++) {
- color_region_cache.erase(i);
- }
-
- if (syntax_highlighting_cache.size() > 0) {
- cache_size = syntax_highlighting_cache.back()->key();
- for (int i = p_line - 1; i <= cache_size; i++) {
- if (syntax_highlighting_cache.has(i)) {
- syntax_highlighting_cache.erase(i);
- }
- }
- }
-}
-
int TextEdit::get_char_count() {
int totalsize = 0;
@@ -4985,10 +4866,6 @@ void TextEdit::_update_caches() {
cache.font_color = get_theme_color("font_color");
cache.font_color_selected = get_theme_color("font_color_selected");
cache.font_color_readonly = get_theme_color("font_color_readonly");
- cache.keyword_color = get_theme_color("keyword_color");
- cache.function_color = get_theme_color("function_color");
- cache.member_variable_color = get_theme_color("member_variable_color");
- cache.number_color = get_theme_color("number_color");
cache.selection_color = get_theme_color("selection_color");
cache.mark_color = get_theme_color("mark_color");
cache.current_line_color = get_theme_color("current_line_color");
@@ -5001,7 +4878,6 @@ void TextEdit::_update_caches() {
cache.word_highlighted_color = get_theme_color("word_highlighted_color");
cache.search_result_color = get_theme_color("search_result_color");
cache.search_result_border_color = get_theme_color("search_result_border_color");
- cache.symbol_color = get_theme_color("symbol_color");
cache.background_color = get_theme_color("background_color");
#ifdef TOOLS_ENABLED
cache.line_spacing = get_theme_constant("line_spacing") * EDSCALE;
@@ -5016,142 +4892,31 @@ void TextEdit::_update_caches() {
cache.folded_eol_icon = get_theme_icon("GuiEllipsis", "EditorIcons");
cache.executing_icon = get_theme_icon("MainPlay", "EditorIcons");
text.set_font(cache.font);
+ text.clear_width_cache();
- if (syntax_highlighter) {
- syntax_highlighter->_update_cache();
+ if (syntax_highlighter.is_valid()) {
+ syntax_highlighter->set_text_edit(this);
}
}
-SyntaxHighlighter *TextEdit::_get_syntax_highlighting() {
+Ref<SyntaxHighlighter> TextEdit::get_syntax_highlighter() {
return syntax_highlighter;
}
-void TextEdit::_set_syntax_highlighting(SyntaxHighlighter *p_syntax_highlighter) {
+void TextEdit::set_syntax_highlighter(Ref<SyntaxHighlighter> p_syntax_highlighter) {
syntax_highlighter = p_syntax_highlighter;
- if (syntax_highlighter) {
- syntax_highlighter->set_text_editor(this);
- syntax_highlighter->_update_cache();
+ if (syntax_highlighter.is_valid()) {
+ syntax_highlighter->set_text_edit(this);
}
- syntax_highlighting_cache.clear();
update();
}
-int TextEdit::_is_line_in_region(int p_line) {
- // Do we have in cache?
- if (color_region_cache.has(p_line)) {
- return color_region_cache[p_line];
- }
-
- // If not find the closest line we have.
- int previous_line = p_line - 1;
- for (; previous_line > -1; previous_line--) {
- if (color_region_cache.has(p_line)) {
- break;
- }
- }
-
- // Calculate up to line we need and update the cache along the way.
- int in_region = color_region_cache[previous_line];
- if (previous_line == -1) {
- in_region = -1;
- }
- for (int i = previous_line; i < p_line; i++) {
- const Map<int, Text::ColorRegionInfo> &cri_map = _get_line_color_region_info(i);
- for (const Map<int, Text::ColorRegionInfo>::Element *E = cri_map.front(); E; E = E->next()) {
- const Text::ColorRegionInfo &cri = E->get();
- if (in_region == -1) {
- if (!cri.end) {
- in_region = cri.region;
- }
- } else if (in_region == cri.region && !_get_color_region(cri.region).line_only) {
- if (cri.end || _get_color_region(cri.region).eq) {
- in_region = -1;
- }
- }
- }
-
- if (in_region >= 0 && _get_color_region(in_region).line_only) {
- in_region = -1;
- }
-
- color_region_cache[i + 1] = in_region;
- }
- return in_region;
-}
-
-TextEdit::ColorRegion TextEdit::_get_color_region(int p_region) const {
- if (p_region < 0 || p_region >= color_regions.size()) {
- return ColorRegion();
- }
- return color_regions[p_region];
+void TextEdit::add_keyword(const String &p_keyword) {
+ keywords.insert(p_keyword);
}
-Map<int, TextEdit::Text::ColorRegionInfo> TextEdit::_get_line_color_region_info(int p_line) const {
- if (p_line < 0 || p_line > text.size() - 1) {
- return Map<int, Text::ColorRegionInfo>();
- }
- return text.get_color_region_info(p_line);
-}
-
-void TextEdit::clear_colors() {
+void TextEdit::clear_keywords() {
keywords.clear();
- member_keywords.clear();
- color_regions.clear();
- color_region_cache.clear();
- syntax_highlighting_cache.clear();
- text.clear_width_cache();
- update();
-}
-
-void TextEdit::add_keyword_color(const String &p_keyword, const Color &p_color) {
- keywords[p_keyword] = p_color;
- syntax_highlighting_cache.clear();
- update();
-}
-
-bool TextEdit::has_keyword_color(String p_keyword) const {
- return keywords.has(p_keyword);
-}
-
-Color TextEdit::get_keyword_color(String p_keyword) const {
- ERR_FAIL_COND_V(!keywords.has(p_keyword), Color());
- return keywords[p_keyword];
-}
-
-void TextEdit::add_color_region(const String &p_begin_key, const String &p_end_key, const Color &p_color, bool p_line_only) {
- color_regions.push_back(ColorRegion(p_begin_key, p_end_key, p_color, p_line_only));
- syntax_highlighting_cache.clear();
- text.clear_width_cache();
- update();
-}
-
-void TextEdit::add_member_keyword(const String &p_keyword, const Color &p_color) {
- member_keywords[p_keyword] = p_color;
- syntax_highlighting_cache.clear();
- update();
-}
-
-bool TextEdit::has_member_color(String p_member) const {
- return member_keywords.has(p_member);
-}
-
-Color TextEdit::get_member_color(String p_member) const {
- return member_keywords[p_member];
-}
-
-void TextEdit::clear_member_keywords() {
- member_keywords.clear();
- syntax_highlighting_cache.clear();
- update();
-}
-
-void TextEdit::set_syntax_coloring(bool p_enabled) {
- syntax_coloring = p_enabled;
- update();
-}
-
-bool TextEdit::is_syntax_coloring_enabled() const {
- return syntax_coloring;
}
void TextEdit::set_auto_indent(bool p_auto_indent) {
@@ -5827,18 +5592,19 @@ bool TextEdit::is_line_comment(int p_line) const {
// Checks to see if this line is the start of a comment.
ERR_FAIL_INDEX_V(p_line, text.size(), false);
- const Map<int, Text::ColorRegionInfo> &cri_map = text.get_color_region_info(p_line);
-
int line_length = text[p_line].size();
for (int i = 0; i < line_length - 1; i++) {
- if (_is_symbol(text[p_line][i]) && cri_map.has(i)) {
- const Text::ColorRegionInfo &cri = cri_map[i];
- return color_regions[cri.region].begin_key == "#" || color_regions[cri.region].begin_key == "//";
- } else if (_is_whitespace(text[p_line][i])) {
+ if (_is_whitespace(text[p_line][i])) {
continue;
- } else {
- break;
}
+ if (_is_symbol(text[p_line][i])) {
+ if (text[p_line][i] == '\\') {
+ i++; // Skip quoted anything.
+ continue;
+ }
+ return text[p_line][i] == '#' || (i + 1 < line_length && text[p_line][i] == '/' && text[p_line][i + 1] == '/');
+ }
+ break;
}
return false;
}
@@ -6930,6 +6696,10 @@ void TextEdit::set_shortcut_keys_enabled(bool p_enabled) {
_generate_context_menu();
}
+void TextEdit::set_virtual_keyboard_enabled(bool p_enable) {
+ virtual_keyboard_enabled = p_enable;
+}
+
void TextEdit::set_selecting_enabled(bool p_enabled) {
selecting_enabled = p_enabled;
@@ -6948,6 +6718,10 @@ bool TextEdit::is_shortcut_keys_enabled() const {
return shortcut_keys_enabled;
}
+bool TextEdit::is_virtual_keyboard_enabled() const {
+ return virtual_keyboard_enabled;
+}
+
PopupMenu *TextEdit::get_menu() const {
return menu;
}
@@ -7000,6 +6774,8 @@ void TextEdit::_bind_methods() {
ClassDB::bind_method(D_METHOD("is_context_menu_enabled"), &TextEdit::is_context_menu_enabled);
ClassDB::bind_method(D_METHOD("set_shortcut_keys_enabled", "enable"), &TextEdit::set_shortcut_keys_enabled);
ClassDB::bind_method(D_METHOD("is_shortcut_keys_enabled"), &TextEdit::is_shortcut_keys_enabled);
+ ClassDB::bind_method(D_METHOD("set_virtual_keyboard_enabled", "enable"), &TextEdit::set_virtual_keyboard_enabled);
+ ClassDB::bind_method(D_METHOD("is_virtual_keyboard_enabled"), &TextEdit::is_virtual_keyboard_enabled);
ClassDB::bind_method(D_METHOD("set_selecting_enabled", "enable"), &TextEdit::set_selecting_enabled);
ClassDB::bind_method(D_METHOD("is_selecting_enabled"), &TextEdit::is_selecting_enabled);
@@ -7053,8 +6829,8 @@ void TextEdit::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_override_selected_font_color", "override"), &TextEdit::set_override_selected_font_color);
ClassDB::bind_method(D_METHOD("is_overriding_selected_font_color"), &TextEdit::is_overriding_selected_font_color);
- ClassDB::bind_method(D_METHOD("set_syntax_coloring", "enable"), &TextEdit::set_syntax_coloring);
- ClassDB::bind_method(D_METHOD("is_syntax_coloring_enabled"), &TextEdit::is_syntax_coloring_enabled);
+ ClassDB::bind_method(D_METHOD("set_syntax_highlighter", "syntax_highlighter"), &TextEdit::set_syntax_highlighter);
+ ClassDB::bind_method(D_METHOD("get_syntax_highlighter"), &TextEdit::get_syntax_highlighter);
ClassDB::bind_method(D_METHOD("set_highlight_current_line", "enabled"), &TextEdit::set_highlight_current_line);
ClassDB::bind_method(D_METHOD("is_highlight_current_line_enabled"), &TextEdit::is_highlight_current_line_enabled);
@@ -7068,11 +6844,6 @@ void TextEdit::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_h_scroll", "value"), &TextEdit::set_h_scroll);
ClassDB::bind_method(D_METHOD("get_h_scroll"), &TextEdit::get_h_scroll);
- ClassDB::bind_method(D_METHOD("add_keyword_color", "keyword", "color"), &TextEdit::add_keyword_color);
- ClassDB::bind_method(D_METHOD("has_keyword_color", "keyword"), &TextEdit::has_keyword_color);
- ClassDB::bind_method(D_METHOD("get_keyword_color", "keyword"), &TextEdit::get_keyword_color);
- ClassDB::bind_method(D_METHOD("add_color_region", "begin_key", "end_key", "color", "line_only"), &TextEdit::add_color_region, DEFVAL(false));
- ClassDB::bind_method(D_METHOD("clear_colors"), &TextEdit::clear_colors);
ClassDB::bind_method(D_METHOD("menu_option", "option"), &TextEdit::menu_option);
ClassDB::bind_method(D_METHOD("get_menu"), &TextEdit::get_menu);
@@ -7087,7 +6858,6 @@ void TextEdit::_bind_methods() {
ADD_PROPERTY(PropertyInfo(Variant::STRING, "text", PROPERTY_HINT_MULTILINE_TEXT), "set_text", "get_text");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "readonly"), "set_readonly", "is_readonly");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "highlight_current_line"), "set_highlight_current_line", "is_highlight_current_line_enabled");
- ADD_PROPERTY(PropertyInfo(Variant::BOOL, "syntax_highlighting"), "set_syntax_coloring", "is_syntax_coloring_enabled");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "show_line_numbers"), "set_show_line_numbers", "is_show_line_numbers_enabled");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "draw_tabs"), "set_draw_tabs", "is_drawing_tabs");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "draw_spaces"), "set_draw_spaces", "is_drawing_spaces");
@@ -7097,6 +6867,7 @@ void TextEdit::_bind_methods() {
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "override_selected_font_color"), "set_override_selected_font_color", "is_overriding_selected_font_color");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "context_menu_enabled"), "set_context_menu_enabled", "is_context_menu_enabled");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "shortcut_keys_enabled"), "set_shortcut_keys_enabled", "is_shortcut_keys_enabled");
+ ADD_PROPERTY(PropertyInfo(Variant::BOOL, "virtual_keyboard_enabled"), "set_virtual_keyboard_enabled", "is_virtual_keyboard_enabled");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "selecting_enabled"), "set_selecting_enabled", "is_selecting_enabled");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "smooth_scrolling"), "set_smooth_scroll_enable", "is_smooth_scroll_enabled");
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "v_scroll_speed"), "set_v_scroll_speed", "get_v_scroll_speed");
@@ -7105,6 +6876,8 @@ void TextEdit::_bind_methods() {
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "scroll_vertical"), "set_v_scroll", "get_v_scroll");
ADD_PROPERTY(PropertyInfo(Variant::INT, "scroll_horizontal"), "set_h_scroll", "get_h_scroll");
+ ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "syntax_highlighter", PROPERTY_HINT_RESOURCE_TYPE, "SyntaxHighlighter", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_DO_NOT_SHARE_ON_DUPLICATE), "set_syntax_highlighter", "get_syntax_highlighter");
+
ADD_GROUP("Minimap", "minimap_");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "minimap_draw"), "draw_minimap", "is_drawing_minimap");
ADD_PROPERTY(PropertyInfo(Variant::INT, "minimap_width"), "set_minimap_width", "get_minimap_width");
@@ -7117,6 +6890,7 @@ void TextEdit::_bind_methods() {
ADD_SIGNAL(MethodInfo("cursor_changed"));
ADD_SIGNAL(MethodInfo("text_changed"));
+ ADD_SIGNAL(MethodInfo("line_edited_from", PropertyInfo(Variant::INT, "line")));
ADD_SIGNAL(MethodInfo("request_completion"));
ADD_SIGNAL(MethodInfo("breakpoint_toggled", PropertyInfo(Variant::INT, "row")));
ADD_SIGNAL(MethodInfo("symbol_lookup", PropertyInfo(Variant::STRING, "symbol"), PropertyInfo(Variant::INT, "row"), PropertyInfo(Variant::INT, "column")));
@@ -7150,7 +6924,6 @@ TextEdit::TextEdit() {
wrap_at = 0;
wrap_right_offset = 10;
set_focus_mode(FOCUS_ALL);
- syntax_highlighter = nullptr;
_update_caches();
cache.row_height = 1;
cache.line_spacing = 1;
@@ -7166,7 +6939,6 @@ TextEdit::TextEdit() {
indent_size = 4;
text.set_indent_size(indent_size);
text.clear();
- text.set_color_regions(&color_regions);
h_scroll = memnew(HScrollBar);
v_scroll = memnew(VScrollBar);
@@ -7190,7 +6962,6 @@ TextEdit::TextEdit() {
selection.selecting_column = 0;
selection.selecting_text = false;
selection.active = false;
- syntax_coloring = false;
block_caret = false;
caret_blink_enabled = false;
@@ -7281,204 +7052,6 @@ TextEdit::~TextEdit() {
///////////////////////////////////////////////////////////////////////////////
-Map<int, TextEdit::HighlighterInfo> TextEdit::_get_line_syntax_highlighting(int p_line) {
- if (syntax_highlighting_cache.has(p_line)) {
- return syntax_highlighting_cache[p_line];
- }
-
- if (syntax_highlighter != nullptr) {
- Map<int, HighlighterInfo> color_map = syntax_highlighter->_get_line_syntax_highlighting(p_line);
- syntax_highlighting_cache[p_line] = color_map;
- return color_map;
- }
-
- Map<int, HighlighterInfo> color_map;
-
- bool prev_is_char = false;
- bool prev_is_number = false;
- bool in_keyword = false;
- bool in_word = false;
- bool in_function_name = false;
- bool in_member_variable = false;
- bool is_hex_notation = false;
- Color keyword_color;
- Color color;
-
- int in_region = _is_line_in_region(p_line);
- int deregion = 0;
-
- const Map<int, TextEdit::Text::ColorRegionInfo> cri_map = text.get_color_region_info(p_line);
- const String &str = text[p_line];
- Color prev_color;
- for (int j = 0; j < str.length(); j++) {
- HighlighterInfo highlighter_info;
-
- if (deregion > 0) {
- deregion--;
- if (deregion == 0) {
- in_region = -1;
- }
- }
-
- if (deregion != 0) {
- if (color != prev_color) {
- prev_color = color;
- highlighter_info.color = color;
- color_map[j] = highlighter_info;
- }
- continue;
- }
-
- color = cache.font_color;
-
- bool is_char = _is_text_char(str[j]);
- bool is_symbol = _is_symbol(str[j]);
- bool is_number = _is_number(str[j]);
-
- // Allow ABCDEF in hex notation.
- if (is_hex_notation && (_is_hex_symbol(str[j]) || is_number)) {
- is_number = true;
- } else {
- is_hex_notation = false;
- }
-
- // Check for dot or underscore or 'x' for hex notation in floating point number or 'e' for scientific notation.
- if ((str[j] == '.' || str[j] == 'x' || str[j] == '_' || str[j] == 'f' || str[j] == 'e') && !in_word && prev_is_number && !is_number) {
- is_number = true;
- is_symbol = false;
- is_char = false;
-
- if (str[j] == 'x' && str[j - 1] == '0') {
- is_hex_notation = true;
- }
- }
-
- if (!in_word && _is_char(str[j]) && !is_number) {
- in_word = true;
- }
-
- if ((in_keyword || in_word) && !is_hex_notation) {
- is_number = false;
- }
-
- if (is_symbol && str[j] != '.' && in_word) {
- in_word = false;
- }
-
- if (is_symbol && cri_map.has(j)) {
- const TextEdit::Text::ColorRegionInfo &cri = cri_map[j];
-
- if (in_region == -1) {
- if (!cri.end) {
- in_region = cri.region;
- }
- } else if (in_region == cri.region && !color_regions[cri.region].line_only) { // Ignore otherwise.
- if (cri.end || color_regions[cri.region].eq) {
- deregion = color_regions[cri.region].eq ? color_regions[cri.region].begin_key.length() : color_regions[cri.region].end_key.length();
- }
- }
- }
-
- if (!is_char) {
- in_keyword = false;
- }
-
- if (in_region == -1 && !in_keyword && is_char && !prev_is_char) {
- int to = j;
- while (to < str.length() && _is_text_char(str[to])) {
- to++;
- }
-
- uint32_t hash = String::hash(&str[j], to - j);
- StrRange range(&str[j], to - j);
-
- const Color *col = keywords.custom_getptr(range, hash);
-
- if (!col) {
- col = member_keywords.custom_getptr(range, hash);
-
- if (col) {
- for (int k = j - 1; k >= 0; k--) {
- if (str[k] == '.') {
- col = nullptr; // Member indexing not allowed.
- break;
- } else if (str[k] > 32) {
- break;
- }
- }
- }
- }
-
- if (col) {
- in_keyword = true;
- keyword_color = *col;
- }
- }
-
- if (!in_function_name && in_word && !in_keyword) {
- int k = j;
- while (k < str.length() && !_is_symbol(str[k]) && str[k] != '\t' && str[k] != ' ') {
- k++;
- }
-
- // Check for space between name and bracket.
- while (k < str.length() && (str[k] == '\t' || str[k] == ' ')) {
- k++;
- }
-
- if (str[k] == '(') {
- in_function_name = true;
- }
- }
-
- if (!in_function_name && !in_member_variable && !in_keyword && !is_number && in_word) {
- int k = j;
- while (k > 0 && !_is_symbol(str[k]) && str[k] != '\t' && str[k] != ' ') {
- k--;
- }
-
- if (str[k] == '.') {
- in_member_variable = true;
- }
- }
-
- if (is_symbol) {
- in_function_name = false;
- in_member_variable = false;
- }
-
- if (in_region >= 0) {
- color = color_regions[in_region].color;
- } else if (in_keyword) {
- color = keyword_color;
- } else if (in_member_variable) {
- color = cache.member_variable_color;
- } else if (in_function_name) {
- color = cache.function_color;
- } else if (is_symbol) {
- color = cache.symbol_color;
- } else if (is_number) {
- color = cache.number_color;
- }
-
- prev_is_char = is_char;
- prev_is_number = is_number;
-
- if (color != prev_color) {
- prev_color = color;
- highlighter_info.color = color;
- color_map[j] = highlighter_info;
- }
- }
-
- syntax_highlighting_cache[p_line] = color_map;
- return color_map;
-}
-
-void SyntaxHighlighter::set_text_editor(TextEdit *p_text_editor) {
- text_editor = p_text_editor;
-}
-
-TextEdit *SyntaxHighlighter::get_text_editor() {
- return text_editor;
+Dictionary TextEdit::_get_line_syntax_highlighting(int p_line) {
+ return syntax_highlighter.is_null() && !setting_text ? Dictionary() : syntax_highlighter->get_line_syntax_highlighting(p_line);
}
diff --git a/scene/gui/text_edit.h b/scene/gui/text_edit.h
index 68aec9cfac..a6bc9963cc 100644
--- a/scene/gui/text_edit.h
+++ b/scene/gui/text_edit.h
@@ -35,43 +35,14 @@
#include "scene/gui/popup_menu.h"
#include "scene/gui/scroll_bar.h"
#include "scene/main/timer.h"
-
-class SyntaxHighlighter;
+#include "scene/resources/syntax_highlighter.h"
class TextEdit : public Control {
GDCLASS(TextEdit, Control);
public:
- struct HighlighterInfo {
- Color color;
- };
-
- struct ColorRegion {
- Color color;
- String begin_key;
- String end_key;
- bool line_only;
- bool eq;
- ColorRegion(const String &p_begin_key = "", const String &p_end_key = "", const Color &p_color = Color(), bool p_line_only = false) {
- begin_key = p_begin_key;
- end_key = p_end_key;
- color = p_color;
- line_only = p_line_only || p_end_key == "";
- eq = begin_key == end_key;
- }
- };
-
class Text {
public:
- struct ColorRegionInfo {
- int region;
- bool end;
- ColorRegionInfo() {
- region = 0;
- end = false;
- }
- };
-
struct Line {
int width_cache : 24;
bool marked : 1;
@@ -81,7 +52,6 @@ public:
bool safe : 1;
bool has_info : 1;
int wrap_amount_cache : 24;
- Map<int, ColorRegionInfo> region_info;
Ref<Texture2D> info_icon;
String info;
String data;
@@ -98,7 +68,6 @@ public:
};
private:
- const Vector<ColorRegion> *color_regions;
mutable Vector<Line> text;
Ref<Font> font;
int indent_size;
@@ -108,13 +77,11 @@ public:
public:
void set_indent_size(int p_indent_size);
void set_font(const Ref<Font> &p_font);
- void set_color_regions(const Vector<ColorRegion> *p_regions) { color_regions = p_regions; }
int get_line_width(int p_line) const;
int get_max_width(bool p_exclude_hidden = false) const;
int get_char_width(CharType c, CharType next_c, int px) const;
void set_line_wrap_amount(int p_line, int p_wrap_amount) const;
int get_line_wrap_amount(int p_line) const;
- const Map<int, ColorRegionInfo> &get_color_region_info(int p_line) const;
void set(int p_line, const String &p_text);
void set_marked(int p_line, bool p_marked) { text.write[p_line].marked = p_marked; }
bool is_marked(int p_line) const { return text[p_line].marked; }
@@ -224,10 +191,6 @@ private:
Color font_color;
Color font_color_selected;
Color font_color_readonly;
- Color keyword_color;
- Color number_color;
- Color function_color;
- Color member_variable_color;
Color selection_color;
Color mark_color;
Color bookmark_color;
@@ -240,7 +203,6 @@ private:
Color word_highlighted_color;
Color search_result_color;
Color search_result_border_color;
- Color symbol_color;
Color background_color;
int row_height;
@@ -261,8 +223,7 @@ private:
}
} cache;
- Map<int, int> color_region_cache;
- Map<int, Map<int, HighlighterInfo>> syntax_highlighting_cache;
+ Map<int, Dictionary> syntax_highlighting_cache;
struct TextOperation {
enum Type {
@@ -305,13 +266,10 @@ private:
void _do_text_op(const TextOperation &p_op, bool p_reverse);
//syntax coloring
- SyntaxHighlighter *syntax_highlighter;
- HashMap<String, Color> keywords;
- HashMap<String, Color> member_keywords;
+ Ref<SyntaxHighlighter> syntax_highlighter;
+ Set<String> keywords;
- Map<int, HighlighterInfo> _get_line_syntax_highlighting(int p_line);
-
- Vector<ColorRegion> color_regions;
+ Dictionary _get_line_syntax_highlighting(int p_line);
Set<String> completion_prefixes;
bool completion_enabled;
@@ -337,7 +295,6 @@ private:
int max_chars;
bool readonly;
- bool syntax_coloring;
bool indent_using_spaces;
int indent_size;
String space_indent;
@@ -429,6 +386,8 @@ private:
bool context_menu_enabled;
bool shortcut_keys_enabled;
+ bool virtual_keyboard_enabled = true;
+
int executing_line;
void _generate_context_menu();
@@ -496,7 +455,6 @@ private:
void _update_caches();
void _cursor_changed_emit();
void _text_changed_emit();
- void _line_edited_from(int p_line);
void _push_current_op();
@@ -536,12 +494,8 @@ protected:
static void _bind_methods();
public:
- SyntaxHighlighter *_get_syntax_highlighting();
- void _set_syntax_highlighting(SyntaxHighlighter *p_syntax_highlighter);
-
- int _is_line_in_region(int p_line);
- ColorRegion _get_color_region(int p_region) const;
- Map<int, Text::ColorRegionInfo> _get_line_color_region_info(int p_line) const;
+ Ref<SyntaxHighlighter> get_syntax_highlighter();
+ void set_syntax_highlighter(Ref<SyntaxHighlighter> p_syntax_highlighter);
enum MenuItems {
MENU_CUT,
@@ -671,9 +625,6 @@ public:
void clear();
- void set_syntax_coloring(bool p_enabled);
- bool is_syntax_coloring_enabled() const;
-
void cut();
void copy();
void paste();
@@ -718,17 +669,8 @@ public:
void set_insert_mode(bool p_enabled);
bool is_insert_mode() const;
- void add_keyword_color(const String &p_keyword, const Color &p_color);
- bool has_keyword_color(String p_keyword) const;
- Color get_keyword_color(String p_keyword) const;
-
- void add_color_region(const String &p_begin_key = String(), const String &p_end_key = String(), const Color &p_color = Color(), bool p_line_only = false);
- void clear_colors();
-
- void add_member_keyword(const String &p_keyword, const Color &p_color);
- bool has_member_color(String p_member) const;
- Color get_member_color(String p_member) const;
- void clear_member_keywords();
+ void add_keyword(const String &p_keyword);
+ void clear_keywords();
double get_v_scroll() const;
void set_v_scroll(double p_scroll);
@@ -809,6 +751,9 @@ public:
void set_shortcut_keys_enabled(bool p_enabled);
bool is_shortcut_keys_enabled() const;
+ void set_virtual_keyboard_enabled(bool p_enable);
+ bool is_virtual_keyboard_enabled() const;
+
PopupMenu *get_menu() const;
String get_text_for_completion();
@@ -822,20 +767,4 @@ public:
VARIANT_ENUM_CAST(TextEdit::MenuItems);
VARIANT_ENUM_CAST(TextEdit::SearchFlags);
-class SyntaxHighlighter {
-protected:
- TextEdit *text_editor;
-
-public:
- virtual ~SyntaxHighlighter() {}
- virtual void _update_cache() = 0;
- virtual Map<int, TextEdit::HighlighterInfo> _get_line_syntax_highlighting(int p_line) = 0;
-
- virtual String get_name() const = 0;
- virtual List<String> get_supported_languages() = 0;
-
- void set_text_editor(TextEdit *p_text_editor);
- TextEdit *get_text_editor();
-};
-
#endif // TEXT_EDIT_H
diff --git a/scene/gui/tree.cpp b/scene/gui/tree.cpp
index 47761d724e..5057f84192 100644
--- a/scene/gui/tree.cpp
+++ b/scene/gui/tree.cpp
@@ -1971,7 +1971,7 @@ void Tree::_text_editor_enter(String p_text) {
//popup_edited_item->edited_signal.call( popup_edited_item_col );
} break;
case TreeItem::CELL_MODE_RANGE: {
- c.val = p_text.to_double();
+ c.val = p_text.to_float();
if (c.step > 0) {
c.val = Math::stepify(c.val, c.step);
}
@@ -3419,6 +3419,8 @@ void Tree::scroll_to_item(TreeItem *p_item) {
TreeItem *Tree::_search_item_text(TreeItem *p_at, const String &p_find, int *r_col, bool p_selectable, bool p_backwards) {
TreeItem *from = p_at;
+ TreeItem *loop = nullptr; // Safe-guard against infinite loop.
+
while (p_at) {
for (int i = 0; i < columns.size(); i++) {
if (p_at->get_text(i).findn(p_find) == 0 && (!p_selectable || p_at->is_selectable(i))) {
@@ -3438,6 +3440,12 @@ TreeItem *Tree::_search_item_text(TreeItem *p_at, const String &p_find, int *r_c
if ((p_at) == from) {
break;
}
+
+ if (!loop) {
+ loop = p_at;
+ } else if (loop == p_at) {
+ break;
+ }
}
return nullptr;