diff options
Diffstat (limited to 'scene/gui')
-rw-r--r-- | scene/gui/base_button.cpp | 2 | ||||
-rw-r--r-- | scene/gui/code_edit.cpp | 444 | ||||
-rw-r--r-- | scene/gui/code_edit.h | 135 | ||||
-rw-r--r-- | scene/gui/graph_edit.cpp | 1 | ||||
-rw-r--r-- | scene/gui/popup_menu.cpp | 20 | ||||
-rw-r--r-- | scene/gui/popup_menu.h | 2 | ||||
-rw-r--r-- | scene/gui/rich_text_label.cpp | 12 | ||||
-rw-r--r-- | scene/gui/rich_text_label.h | 1 | ||||
-rw-r--r-- | scene/gui/text_edit.cpp | 844 | ||||
-rw-r--r-- | scene/gui/text_edit.h | 289 |
10 files changed, 1097 insertions, 653 deletions
diff --git a/scene/gui/base_button.cpp b/scene/gui/base_button.cpp index 211082d610..2e77d20d4e 100644 --- a/scene/gui/base_button.cpp +++ b/scene/gui/base_button.cpp @@ -345,7 +345,7 @@ String BaseButton::get_tooltip(const Point2 &p_pos) const { String tooltip = Control::get_tooltip(p_pos); if (shortcut_in_tooltip && shortcut.is_valid() && shortcut->is_valid()) { String text = shortcut->get_name() + " (" + shortcut->get_as_text() + ")"; - if (shortcut->get_name().nocasecmp_to(tooltip) != 0) { + if (tooltip != String() && shortcut->get_name().nocasecmp_to(tooltip) != 0) { text += "\n" + tooltip; } tooltip = text; diff --git a/scene/gui/code_edit.cpp b/scene/gui/code_edit.cpp new file mode 100644 index 0000000000..56cf432b38 --- /dev/null +++ b/scene/gui/code_edit.cpp @@ -0,0 +1,444 @@ +/*************************************************************************/ +/* code_edit.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#include "code_edit.h" + +void CodeEdit::_notification(int p_what) { + switch (p_what) { + case NOTIFICATION_THEME_CHANGED: + case NOTIFICATION_ENTER_TREE: { + set_gutter_width(main_gutter, cache.row_height); + set_gutter_width(line_number_gutter, (line_number_digits + 1) * cache.font->get_char_size('0').width); + set_gutter_width(fold_gutter, cache.row_height / 1.2); + + breakpoint_color = get_theme_color("breakpoint_color"); + breakpoint_icon = get_theme_icon("breakpoint"); + + bookmark_color = get_theme_color("bookmark_color"); + bookmark_icon = get_theme_icon("bookmark"); + + executing_line_color = get_theme_color("executing_line_color"); + executing_line_icon = get_theme_icon("executing_line"); + + line_number_color = get_theme_color("line_number_color"); + + folding_color = get_theme_color("code_folding_color"); + can_fold_icon = get_theme_icon("can_fold"); + folded_icon = get_theme_icon("folded"); + } break; + case NOTIFICATION_DRAW: { + } break; + } +} + +/* Main Gutter */ +void CodeEdit::_update_draw_main_gutter() { + set_gutter_draw(main_gutter, draw_breakpoints || draw_bookmarks || draw_executing_lines); +} + +void CodeEdit::set_draw_breakpoints_gutter(bool p_draw) { + draw_breakpoints = p_draw; + set_gutter_clickable(main_gutter, p_draw); + _update_draw_main_gutter(); +} + +bool CodeEdit::is_drawing_breakpoints_gutter() const { + return draw_breakpoints; +} + +void CodeEdit::set_draw_bookmarks_gutter(bool p_draw) { + draw_bookmarks = p_draw; + _update_draw_main_gutter(); +} + +bool CodeEdit::is_drawing_bookmarks_gutter() const { + return draw_bookmarks; +} + +void CodeEdit::set_draw_executing_lines_gutter(bool p_draw) { + draw_executing_lines = p_draw; + _update_draw_main_gutter(); +} + +bool CodeEdit::is_drawing_executing_lines_gutter() const { + return draw_executing_lines; +} + +void CodeEdit::_main_gutter_draw_callback(int p_line, int p_gutter, const Rect2 &p_region) { + if (draw_breakpoints && is_line_breakpointed(p_line)) { + int padding = p_region.size.x / 6; + + Rect2 breakpoint_region = p_region; + breakpoint_region.position += Point2(padding, padding); + breakpoint_region.size -= Point2(padding, padding) * 2; + breakpoint_icon->draw_rect(get_canvas_item(), breakpoint_region, false, breakpoint_color); + } + + if (draw_bookmarks && is_line_bookmarked(p_line)) { + int horizontal_padding = p_region.size.x / 2; + int vertical_padding = p_region.size.y / 4; + + Rect2 bookmark_region = p_region; + bookmark_region.position += Point2(horizontal_padding, 0); + bookmark_region.size -= Point2(horizontal_padding * 1.1, vertical_padding); + bookmark_icon->draw_rect(get_canvas_item(), bookmark_region, false, bookmark_color); + } + + if (draw_executing_lines && is_line_executing(p_line)) { + int horizontal_padding = p_region.size.x / 10; + int vertical_padding = p_region.size.y / 4; + + Rect2 executing_line_region = p_region; + executing_line_region.position += Point2(horizontal_padding, vertical_padding); + executing_line_region.size -= Point2(horizontal_padding, vertical_padding) * 2; + executing_line_icon->draw_rect(get_canvas_item(), executing_line_region, false, executing_line_color); + } +} + +// Breakpoints +void CodeEdit::set_line_as_breakpoint(int p_line, bool p_breakpointed) { + int mask = get_line_gutter_metadata(p_line, main_gutter); + set_line_gutter_metadata(p_line, main_gutter, p_breakpointed ? mask | MAIN_GUTTER_BREAKPOINT : mask & ~MAIN_GUTTER_BREAKPOINT); + if (p_breakpointed) { + breakpointed_lines[p_line] = true; + } else if (breakpointed_lines.has(p_line)) { + breakpointed_lines.erase(p_line); + } + emit_signal("breakpoint_toggled", p_line); +} + +bool CodeEdit::is_line_breakpointed(int p_line) const { + return (int)get_line_gutter_metadata(p_line, main_gutter) & MAIN_GUTTER_BREAKPOINT; +} + +void CodeEdit::clear_breakpointed_lines() { + for (int i = 0; i < get_line_count(); i++) { + if (is_line_breakpointed(i)) { + set_line_as_breakpoint(i, false); + } + } +} + +Array CodeEdit::get_breakpointed_lines() const { + Array ret; + for (int i = 0; i < get_line_count(); i++) { + if (is_line_breakpointed(i)) { + ret.append(i); + } + } + return ret; +} + +// Bookmarks +void CodeEdit::set_line_as_bookmarked(int p_line, bool p_bookmarked) { + int mask = get_line_gutter_metadata(p_line, main_gutter); + set_line_gutter_metadata(p_line, main_gutter, p_bookmarked ? mask | MAIN_GUTTER_BOOKMARK : mask & ~MAIN_GUTTER_BOOKMARK); +} + +bool CodeEdit::is_line_bookmarked(int p_line) const { + return (int)get_line_gutter_metadata(p_line, main_gutter) & MAIN_GUTTER_BOOKMARK; +} + +void CodeEdit::clear_bookmarked_lines() { + for (int i = 0; i < get_line_count(); i++) { + if (is_line_bookmarked(i)) { + set_line_as_bookmarked(i, false); + } + } +} + +Array CodeEdit::get_bookmarked_lines() const { + Array ret; + for (int i = 0; i < get_line_count(); i++) { + if (is_line_bookmarked(i)) { + ret.append(i); + } + } + return ret; +} + +// executing lines +void CodeEdit::set_line_as_executing(int p_line, bool p_executing) { + int mask = get_line_gutter_metadata(p_line, main_gutter); + set_line_gutter_metadata(p_line, main_gutter, p_executing ? mask | MAIN_GUTTER_EXECUTING : mask & ~MAIN_GUTTER_EXECUTING); +} + +bool CodeEdit::is_line_executing(int p_line) const { + return (int)get_line_gutter_metadata(p_line, main_gutter) & MAIN_GUTTER_EXECUTING; +} + +void CodeEdit::clear_executing_lines() { + for (int i = 0; i < get_line_count(); i++) { + if (is_line_executing(i)) { + set_line_as_executing(i, false); + } + } +} + +Array CodeEdit::get_executing_lines() const { + Array ret; + for (int i = 0; i < get_line_count(); i++) { + if (is_line_executing(i)) { + ret.append(i); + } + } + return ret; +} + +/* Line numbers */ +void CodeEdit::set_draw_line_numbers(bool p_draw) { + set_gutter_draw(line_number_gutter, p_draw); +} + +bool CodeEdit::is_draw_line_numbers_enabled() const { + return is_gutter_drawn(line_number_gutter); +} + +void CodeEdit::set_line_numbers_zero_padded(bool p_zero_padded) { + p_zero_padded ? line_number_padding = "0" : line_number_padding = " "; + update(); +} + +bool CodeEdit::is_line_numbers_zero_padded() const { + return line_number_padding == "0"; +} + +void CodeEdit::_line_number_draw_callback(int p_line, int p_gutter, const Rect2 &p_region) { + String fc = String::num(p_line + 1).lpad(line_number_digits, line_number_padding); + + int yofs = p_region.position.y + (cache.row_height - cache.font->get_height()) / 2; + Color number_color = get_line_gutter_item_color(p_line, line_number_gutter); + 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); +} + +/* Fold Gutter */ +void CodeEdit::set_draw_fold_gutter(bool p_draw) { + set_gutter_draw(fold_gutter, p_draw); +} + +bool CodeEdit::is_drawing_fold_gutter() const { + return is_gutter_drawn(fold_gutter); +} + +void CodeEdit::_fold_gutter_draw_callback(int p_line, int p_gutter, Rect2 p_region) { + if (!can_fold(p_line) && !is_folded(p_line)) { + set_line_gutter_clickable(p_line, fold_gutter, false); + return; + } + set_line_gutter_clickable(p_line, fold_gutter, true); + + int horizontal_padding = p_region.size.x / 10; + int vertical_padding = p_region.size.y / 6; + + p_region.position += Point2(horizontal_padding, vertical_padding); + p_region.size -= Point2(horizontal_padding, vertical_padding) * 2; + + if (can_fold(p_line)) { + can_fold_icon->draw_rect(get_canvas_item(), p_region, false, folding_color); + return; + } + folded_icon->draw_rect(get_canvas_item(), p_region, false, folding_color); +} + +void CodeEdit::_bind_methods() { + /* Main Gutter */ + ClassDB::bind_method(D_METHOD("_main_gutter_draw_callback"), &CodeEdit::_main_gutter_draw_callback); + + ClassDB::bind_method(D_METHOD("set_draw_breakpoints_gutter", "enable"), &CodeEdit::set_draw_breakpoints_gutter); + ClassDB::bind_method(D_METHOD("is_drawing_breakpoints_gutter"), &CodeEdit::is_drawing_breakpoints_gutter); + + ClassDB::bind_method(D_METHOD("set_draw_bookmarks_gutter", "enable"), &CodeEdit::set_draw_bookmarks_gutter); + ClassDB::bind_method(D_METHOD("is_drawing_bookmarks_gutter"), &CodeEdit::is_drawing_bookmarks_gutter); + + ClassDB::bind_method(D_METHOD("set_draw_executing_lines_gutter", "enable"), &CodeEdit::set_draw_executing_lines_gutter); + ClassDB::bind_method(D_METHOD("is_drawing_executing_lines_gutter"), &CodeEdit::is_drawing_executing_lines_gutter); + + // Breakpoints + ClassDB::bind_method(D_METHOD("set_line_as_breakpoint", "line", "breakpointed"), &CodeEdit::set_line_as_breakpoint); + ClassDB::bind_method(D_METHOD("is_line_breakpointed", "line"), &CodeEdit::is_line_breakpointed); + ClassDB::bind_method(D_METHOD("clear_breakpointed_lines"), &CodeEdit::clear_breakpointed_lines); + ClassDB::bind_method(D_METHOD("get_breakpointed_lines"), &CodeEdit::get_breakpointed_lines); + + // Bookmarks + ClassDB::bind_method(D_METHOD("set_line_as_bookmarked", "line", "bookmarked"), &CodeEdit::set_line_as_bookmarked); + ClassDB::bind_method(D_METHOD("is_line_bookmarked", "line"), &CodeEdit::is_line_bookmarked); + ClassDB::bind_method(D_METHOD("clear_bookmarked_lines"), &CodeEdit::clear_bookmarked_lines); + ClassDB::bind_method(D_METHOD("get_bookmarked_lines"), &CodeEdit::get_bookmarked_lines); + + // executing lines + ClassDB::bind_method(D_METHOD("set_line_as_executing", "line", "executing"), &CodeEdit::set_line_as_executing); + ClassDB::bind_method(D_METHOD("is_line_executing", "line"), &CodeEdit::is_line_executing); + ClassDB::bind_method(D_METHOD("clear_executing_lines"), &CodeEdit::clear_executing_lines); + ClassDB::bind_method(D_METHOD("get_executing_lines"), &CodeEdit::get_executing_lines); + + /* Line numbers */ + ClassDB::bind_method(D_METHOD("_line_number_draw_callback"), &CodeEdit::_line_number_draw_callback); + + ClassDB::bind_method(D_METHOD("set_draw_line_numbers", "enable"), &CodeEdit::set_draw_line_numbers); + ClassDB::bind_method(D_METHOD("is_draw_line_numbers_enabled"), &CodeEdit::is_draw_line_numbers_enabled); + ClassDB::bind_method(D_METHOD("set_line_numbers_zero_padded", "enable"), &CodeEdit::set_line_numbers_zero_padded); + ClassDB::bind_method(D_METHOD("is_line_numbers_zero_padded"), &CodeEdit::is_line_numbers_zero_padded); + + /* Fold Gutter */ + ClassDB::bind_method(D_METHOD("_fold_gutter_draw_callback"), &CodeEdit::_fold_gutter_draw_callback); + + ClassDB::bind_method(D_METHOD("set_draw_fold_gutter", "enable"), &CodeEdit::set_draw_fold_gutter); + ClassDB::bind_method(D_METHOD("is_drawing_fold_gutter"), &CodeEdit::is_drawing_fold_gutter); + + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "draw_breakpoints_gutter"), "set_draw_breakpoints_gutter", "is_drawing_breakpoints_gutter"); + + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "draw_bookmarks"), "set_draw_bookmarks_gutter", "is_drawing_bookmarks_gutter"); + + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "draw_executing_lines"), "set_draw_executing_lines_gutter", "is_drawing_executing_lines_gutter"); + + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "draw_line_numbers"), "set_draw_line_numbers", "is_draw_line_numbers_enabled"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "zero_pad_line_numbers"), "set_line_numbers_zero_padded", "is_line_numbers_zero_padded"); + + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "draw_fold_gutter"), "set_draw_fold_gutter", "is_drawing_fold_gutter"); + + ADD_SIGNAL(MethodInfo("breakpoint_toggled", PropertyInfo(Variant::INT, "line"))); +} + +void CodeEdit::_gutter_clicked(int p_line, int p_gutter) { + if (p_gutter == main_gutter) { + if (draw_breakpoints) { + set_line_as_breakpoint(p_line, !is_line_breakpointed(p_line)); + } + return; + } + + if (p_gutter == line_number_gutter) { + cursor_set_line(p_line); + return; + } + + if (p_gutter == fold_gutter) { + if (is_folded(p_line)) { + unfold_line(p_line); + } else if (can_fold(p_line)) { + fold_line(p_line); + } + return; + } +} + +void CodeEdit::_lines_edited_from(int p_from_line, int p_to_line) { + if (p_from_line == p_to_line) { + return; + } + + int lc = get_line_count(); + line_number_digits = 1; + while (lc /= 10) { + line_number_digits++; + } + set_gutter_width(line_number_gutter, (line_number_digits + 1) * cache.font->get_char_size('0').width); + + int from_line = MIN(p_from_line, p_to_line); + int line_count = (p_to_line - p_from_line); + List<int> breakpoints; + breakpointed_lines.get_key_list(&breakpoints); + for (const List<int>::Element *E = breakpoints.front(); E; E = E->next()) { + int line = E->get(); + if (line <= from_line) { + continue; + } + breakpointed_lines.erase(line); + + emit_signal("breakpoint_toggled", line); + if (line_count > 0 || line >= p_from_line) { + emit_signal("breakpoint_toggled", line + line_count); + breakpointed_lines[line + line_count] = true; + continue; + } + } +} + +void CodeEdit::_update_gutter_indexes() { + for (int i = 0; i < get_gutter_count(); i++) { + if (get_gutter_name(i) == "main_gutter") { + main_gutter = i; + continue; + } + + if (get_gutter_name(i) == "line_numbers") { + line_number_gutter = i; + continue; + } + + if (get_gutter_name(i) == "fold_gutter") { + fold_gutter = i; + continue; + } + } +} + +CodeEdit::CodeEdit() { + /* Gutters */ + int gutter_idx = 0; + + /* Main Gutter */ + add_gutter(); + set_gutter_name(gutter_idx, "main_gutter"); + set_gutter_draw(gutter_idx, false); + set_gutter_overwritable(gutter_idx, true); + set_gutter_type(gutter_idx, GUTTER_TPYE_CUSTOM); + set_gutter_custom_draw(gutter_idx, this, "_main_gutter_draw_callback"); + gutter_idx++; + + /* Line numbers */ + add_gutter(); + set_gutter_name(gutter_idx, "line_numbers"); + set_gutter_draw(gutter_idx, false); + set_gutter_type(gutter_idx, GUTTER_TPYE_CUSTOM); + set_gutter_custom_draw(gutter_idx, this, "_line_number_draw_callback"); + gutter_idx++; + + /* Fold Gutter */ + add_gutter(); + set_gutter_name(gutter_idx, "fold_gutter"); + set_gutter_draw(gutter_idx, false); + set_gutter_type(gutter_idx, GUTTER_TPYE_CUSTOM); + set_gutter_custom_draw(gutter_idx, this, "_fold_gutter_draw_callback"); + gutter_idx++; + + connect("lines_edited_from", callable_mp(this, &CodeEdit::_lines_edited_from)); + connect("gutter_clicked", callable_mp(this, &CodeEdit::_gutter_clicked)); + + connect("gutter_added", callable_mp(this, &CodeEdit::_update_gutter_indexes)); + connect("gutter_removed", callable_mp(this, &CodeEdit::_update_gutter_indexes)); + _update_gutter_indexes(); +} + +CodeEdit::~CodeEdit() { +} diff --git a/scene/gui/code_edit.h b/scene/gui/code_edit.h new file mode 100644 index 0000000000..c989e5ed79 --- /dev/null +++ b/scene/gui/code_edit.h @@ -0,0 +1,135 @@ +/*************************************************************************/ +/* code_edit.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#ifndef CODEEDIT_H +#define CODEEDIT_H + +#include "scene/gui/text_edit.h" + +class CodeEdit : public TextEdit { + GDCLASS(CodeEdit, TextEdit) + +private: + /* Main Gutter */ + enum MainGutterType { + MAIN_GUTTER_BREAKPOINT = 0x01, + MAIN_GUTTER_BOOKMARK = 0x02, + MAIN_GUTTER_EXECUTING = 0x04 + }; + + int main_gutter = -1; + void _update_draw_main_gutter(); + void _main_gutter_draw_callback(int p_line, int p_gutter, const Rect2 &p_region); + + // breakpoints + HashMap<int, bool> breakpointed_lines; + bool draw_breakpoints = false; + Color breakpoint_color = Color(1, 1, 1); + Ref<Texture2D> breakpoint_icon = Ref<Texture2D>(); + + // bookmarks + bool draw_bookmarks = false; + Color bookmark_color = Color(1, 1, 1); + Ref<Texture2D> bookmark_icon = Ref<Texture2D>(); + + // executing lines + bool draw_executing_lines = false; + Color executing_line_color = Color(1, 1, 1); + Ref<Texture2D> executing_line_icon = Ref<Texture2D>(); + + /* Line numbers */ + int line_number_gutter = -1; + int line_number_digits = 0; + String line_number_padding = " "; + Color line_number_color = Color(1, 1, 1); + void _line_number_draw_callback(int p_line, int p_gutter, const Rect2 &p_region); + + /* Fold Gutter */ + int fold_gutter = -1; + bool draw_fold_gutter = false; + Color folding_color = Color(1, 1, 1); + Ref<Texture2D> can_fold_icon = Ref<Texture2D>(); + Ref<Texture2D> folded_icon = Ref<Texture2D>(); + void _fold_gutter_draw_callback(int p_line, int p_gutter, Rect2 p_region); + + void _gutter_clicked(int p_line, int p_gutter); + void _lines_edited_from(int p_from_line, int p_to_line); + + void _update_gutter_indexes(); + +protected: + void _notification(int p_what); + + static void _bind_methods(); + +public: + /* Main Gutter */ + void set_draw_breakpoints_gutter(bool p_draw); + bool is_drawing_breakpoints_gutter() const; + + void set_draw_bookmarks_gutter(bool p_draw); + bool is_drawing_bookmarks_gutter() const; + + void set_draw_executing_lines_gutter(bool p_draw); + bool is_drawing_executing_lines_gutter() const; + + // breakpoints + void set_line_as_breakpoint(int p_line, bool p_breakpointed); + bool is_line_breakpointed(int p_line) const; + void clear_breakpointed_lines(); + Array get_breakpointed_lines() const; + + // bookmarks + void set_line_as_bookmarked(int p_line, bool p_bookmarked); + bool is_line_bookmarked(int p_line) const; + void clear_bookmarked_lines(); + Array get_bookmarked_lines() const; + + // executing lines + void set_line_as_executing(int p_line, bool p_executing); + bool is_line_executing(int p_line) const; + void clear_executing_lines(); + Array get_executing_lines() const; + + /* Line numbers */ + void set_draw_line_numbers(bool p_draw); + bool is_draw_line_numbers_enabled() const; + void set_line_numbers_zero_padded(bool p_zero_padded); + bool is_line_numbers_zero_padded() const; + + /* Fold gutter */ + void set_draw_fold_gutter(bool p_draw); + bool is_drawing_fold_gutter() const; + + CodeEdit(); + ~CodeEdit(); +}; + +#endif // CODEEDIT_H diff --git a/scene/gui/graph_edit.cpp b/scene/gui/graph_edit.cpp index 11b4c9e857..467d252c81 100644 --- a/scene/gui/graph_edit.cpp +++ b/scene/gui/graph_edit.cpp @@ -262,6 +262,7 @@ void GraphEdit::remove_child_notify(Node *p_child) { if (gn) { gn->disconnect("offset_changed", callable_mp(this, &GraphEdit::_graph_node_moved)); gn->disconnect("raise_request", callable_mp(this, &GraphEdit::_graph_node_raised)); + gn->disconnect("item_rect_changed", callable_mp((CanvasItem *)connections_layer, &CanvasItem::update)); } } diff --git a/scene/gui/popup_menu.cpp b/scene/gui/popup_menu.cpp index bc70809ad5..578d8a96e8 100644 --- a/scene/gui/popup_menu.cpp +++ b/scene/gui/popup_menu.cpp @@ -305,12 +305,14 @@ void PopupMenu::_gui_input(const Ref<InputEvent> &p_event) { during_grabbed_click = false; initial_button_mask = 0; - int over = _get_mouse_over(b->get_position()); - - if (invalidated_click) { - invalidated_click = false; + // Disable clicks under a time threshold to avoid selection right when opening the popup. + uint64_t now = OS::get_singleton()->get_ticks_msec(); + uint64_t diff = now - popup_time_msec; + if (diff < 100) { return; } + + int over = _get_mouse_over(b->get_position()); if (over < 0) { if (!was_during_grabbed_click) { hide(); @@ -338,13 +340,6 @@ void PopupMenu::_gui_input(const Ref<InputEvent> &p_event) { return; } - if (invalidated_click) { - moved += m->get_relative(); - if (moved.length() > 4) { - invalidated_click = false; - } - } - for (List<Rect2>::Element *E = autohide_areas.front(); E; E = E->next()) { if (!Rect2(Point2(), get_size()).has_point(m->get_position()) && E->get().has_point(m->get_position())) { _close_pressed(); @@ -1443,7 +1438,7 @@ void PopupMenu::_bind_methods() { void PopupMenu::popup(const Rect2 &p_bounds) { moved = Vector2(); - invalidated_click = true; + popup_time_msec = OS::get_singleton()->get_ticks_msec(); set_as_minsize(); Popup::popup(p_bounds); } @@ -1475,7 +1470,6 @@ PopupMenu::PopupMenu() { submenu_over = -1; initial_button_mask = 0; during_grabbed_click = false; - invalidated_click = false; allow_search = true; search_time_msec = 0; diff --git a/scene/gui/popup_menu.h b/scene/gui/popup_menu.h index 9535fd07d7..e8f82ba869 100644 --- a/scene/gui/popup_menu.h +++ b/scene/gui/popup_menu.h @@ -105,7 +105,7 @@ class PopupMenu : public Popup { void _activate_submenu(int over); void _submenu_timeout(); - bool invalidated_click; + uint64_t popup_time_msec = 0; bool hide_on_item_selection; bool hide_on_checkable_item_selection; bool hide_on_multistate_item_selection; diff --git a/scene/gui/rich_text_label.cpp b/scene/gui/rich_text_label.cpp index c62fd24a5e..e8acac172c 100644 --- a/scene/gui/rich_text_label.cpp +++ b/scene/gui/rich_text_label.cpp @@ -256,7 +256,7 @@ int RichTextLabel::_process_line(ItemFrame *p_frame, const Vector2 &p_ofs, int & lh = line < l.height_caches.size() ? l.height_caches[line] : 1; \ line_ascent = line < l.ascent_caches.size() ? l.ascent_caches[line] : 1; \ line_descent = line < l.descent_caches.size() ? l.descent_caches[line] : 1; \ - if ((p_mode == PROCESS_DRAW) && (align != ALIGN_FILL)) { \ + if (align != ALIGN_FILL) { \ if (line < l.offset_caches.size()) { \ wofs = l.offset_caches[line]; \ } \ @@ -2529,9 +2529,9 @@ bool RichTextLabel::search(const String &p_string, bool p_from_selection, bool p return false; } -void RichTextLabel::selection_copy() { +String RichTextLabel::get_selected_text() { if (!selection.active || !selection.enabled) { - return; + return ""; } String text; @@ -2561,6 +2561,12 @@ void RichTextLabel::selection_copy() { item = _get_next_item(item, true); } + return text; +} + +void RichTextLabel::selection_copy() { + String text = get_selected_text(); + if (text != "") { DisplayServer::get_singleton()->clipboard_set(text); } diff --git a/scene/gui/rich_text_label.h b/scene/gui/rich_text_label.h index 2bda7c7fce..c5ed1cb3ef 100644 --- a/scene/gui/rich_text_label.h +++ b/scene/gui/rich_text_label.h @@ -475,6 +475,7 @@ public: void set_selection_enabled(bool p_enabled); bool is_selection_enabled() const; + String get_selected_text(); void selection_copy(); Error parse_bbcode(const String &p_bbcode); diff --git a/scene/gui/text_edit.cpp b/scene/gui/text_edit.cpp index 1d732bec5b..957e1c11c7 100644 --- a/scene/gui/text_edit.cpp +++ b/scene/gui/text_edit.cpp @@ -107,6 +107,8 @@ static int _find_first_non_whitespace_column_of_line(const String &line) { return left; } +/////////////////////////////////////////////////////////////////////////////// + void TextEdit::Text::set_font(const Ref<Font> &p_font) { font = p_font; } @@ -165,12 +167,6 @@ void TextEdit::Text::clear_wrap_cache() { } } -void TextEdit::Text::clear_info_icons() { - for (int i = 0; i < text.size(); i++) { - text.write[i].has_info = false; - } -} - void TextEdit::Text::clear() { text.clear(); insert(0, ""); @@ -198,12 +194,9 @@ void TextEdit::Text::set(int p_line, const String &p_text) { void TextEdit::Text::insert(int p_at, const String &p_text) { Line line; + line.gutters.resize(gutter_count); line.marked = false; - line.safe = false; - line.breakpoint = false; - line.bookmark = false; line.hidden = false; - line.has_info = false; line.width_cache = -1; line.wrap_amount_cache = -1; line.data = p_text; @@ -231,6 +224,32 @@ int TextEdit::Text::get_char_width(char32_t c, char32_t next_c, int px) const { return w; } +void TextEdit::Text::add_gutter(int p_at) { + for (int i = 0; i < text.size(); i++) { + if (p_at < 0 || p_at > gutter_count) { + text.write[i].gutters.push_back(Gutter()); + } else { + text.write[i].gutters.insert(p_at, Gutter()); + } + } + gutter_count++; +} + +void TextEdit::Text::remove_gutter(int p_gutter) { + for (int i = 0; i < text.size(); i++) { + text.write[i].gutters.remove(p_gutter); + } + gutter_count--; +} + +void TextEdit::Text::move_gutters(int p_from_line, int p_to_line) { + text.write[p_to_line].gutters = text[p_from_line].gutters; + text.write[p_from_line].gutters.clear(); + text.write[p_from_line].gutters.resize(gutter_count); +} + +//////////////////////////////////////////////////////////////////////////////// + void TextEdit::_update_scrollbars() { Size2 size = get_size(); Size2 hmin = h_scroll->get_combined_minimum_size(); @@ -249,23 +268,7 @@ void TextEdit::_update_scrollbars() { } int visible_width = size.width - cache.style_normal->get_minimum_size().width; - int total_width = text.get_max_width(true) + vmin.x; - - if (line_numbers) { - total_width += cache.line_number_w; - } - - if (draw_breakpoint_gutter || draw_bookmark_gutter) { - total_width += cache.breakpoint_gutter_width; - } - - if (draw_info_gutter) { - total_width += cache.info_gutter_width; - } - - if (draw_fold_gutter) { - total_width += cache.fold_gutter_width; - } + int total_width = text.get_max_width(true) + vmin.x + gutters_width + gutter_padding; if (draw_minimap) { total_width += cache.minimap_width; @@ -557,54 +560,16 @@ void TextEdit::_notification(int p_what) { draw_caret = false; } - if (draw_breakpoint_gutter || draw_bookmark_gutter) { - breakpoint_gutter_width = (get_row_height() * 55) / 100; - cache.breakpoint_gutter_width = breakpoint_gutter_width; - } else { - cache.breakpoint_gutter_width = 0; - } - - if (draw_info_gutter) { - info_gutter_width = (get_row_height()); - cache.info_gutter_width = info_gutter_width; - } else { - cache.info_gutter_width = 0; - } - - if (draw_fold_gutter) { - fold_gutter_width = (get_row_height() * 55) / 100; - cache.fold_gutter_width = fold_gutter_width; - } else { - cache.fold_gutter_width = 0; - } - cache.minimap_width = 0; if (draw_minimap) { cache.minimap_width = minimap_width; } - int line_number_char_count = 0; - - { - int lc = text.size(); - cache.line_number_w = 0; - while (lc) { - cache.line_number_w += 1; - lc /= 10; - }; - - if (line_numbers) { - line_number_char_count = cache.line_number_w; - cache.line_number_w = (cache.line_number_w + 1) * cache.font->get_char_size('0').width; - } else { - cache.line_number_w = 0; - } - } _update_scrollbars(); RID ci = get_canvas_item(); RenderingServer::get_singleton()->canvas_item_set_clip(get_canvas_item(), true); - int xmargin_beg = cache.style_normal->get_margin(MARGIN_LEFT) + cache.line_number_w + cache.breakpoint_gutter_width + cache.fold_gutter_width + cache.info_gutter_width; + int xmargin_beg = cache.style_normal->get_margin(MARGIN_LEFT) + gutters_width + gutter_padding; int xmargin_end = size.width - cache.style_normal->get_margin(MARGIN_RIGHT) - cache.minimap_width; // Let's do it easy for now. @@ -795,8 +760,6 @@ void TextEdit::_notification(int p_what) { // Check if highlighted words contains only whitespaces (tabs or spaces). bool only_whitespaces_highlighted = highlighted_text.strip_edges() == String(); - String line_num_padding = line_numbers_zero_padded ? "0" : " "; - int cursor_wrap_index = get_cursor_wrap_index(); FontDrawer drawer(cache.font, Color(1, 1, 1)); @@ -1053,103 +1016,52 @@ void TextEdit::_notification(int p_what) { if (line_wrap_index == 0) { // Only do these if we are on the first wrapped part of a line. - if (text.is_breakpoint(line) && !draw_breakpoint_gutter) { -#ifdef TOOLS_ENABLED - RenderingServer::get_singleton()->canvas_item_add_rect(ci, Rect2(xmargin_beg + ofs_x, ofs_y + get_row_height() - EDSCALE, xmargin_end - xmargin_beg, EDSCALE), cache.breakpoint_color); -#else - RenderingServer::get_singleton()->canvas_item_add_rect(ci, Rect2(xmargin_beg + ofs_x, ofs_y, xmargin_end - xmargin_beg, get_row_height()), cache.breakpoint_color); -#endif - } + int gutter_offset = cache.style_normal->get_margin(MARGIN_LEFT); + for (int g = 0; g < gutters.size(); g++) { + const GutterInfo gutter = gutters[g]; - // Draw bookmark marker. - if (text.is_bookmark(line)) { - if (draw_bookmark_gutter) { - int vertical_gap = (get_row_height() * 40) / 100; - int horizontal_gap = (cache.breakpoint_gutter_width * 30) / 100; - int marker_radius = get_row_height() - (vertical_gap * 2); - RenderingServer::get_singleton()->canvas_item_add_circle(ci, Point2(cache.style_normal->get_margin(MARGIN_LEFT) + horizontal_gap - 2 + marker_radius / 2, ofs_y + vertical_gap + marker_radius / 2), marker_radius, Color(cache.bookmark_color.r, cache.bookmark_color.g, cache.bookmark_color.b)); + if (!gutter.draw || gutter.width <= 0) { + continue; } - } - // Draw breakpoint marker. - if (text.is_breakpoint(line)) { - if (draw_breakpoint_gutter) { - int vertical_gap = (get_row_height() * 40) / 100; - int horizontal_gap = (cache.breakpoint_gutter_width * 30) / 100; - int marker_height = get_row_height() - (vertical_gap * 2); - int marker_width = cache.breakpoint_gutter_width - (horizontal_gap * 2); - // No transparency on marker. - RenderingServer::get_singleton()->canvas_item_add_rect(ci, Rect2(cache.style_normal->get_margin(MARGIN_LEFT) + horizontal_gap - 2, ofs_y + vertical_gap, marker_width, marker_height), Color(cache.breakpoint_color.r, cache.breakpoint_color.g, cache.breakpoint_color.b)); - } - } - - // Draw info icons. - if (draw_info_gutter && text.has_info_icon(line)) { - int vertical_gap = (get_row_height() * 40) / 100; - int horizontal_gap = (cache.info_gutter_width * 30) / 100; - int gutter_left = cache.style_normal->get_margin(MARGIN_LEFT) + cache.breakpoint_gutter_width; - - Ref<Texture2D> info_icon = text.get_info_icon(line); - // Ensure the icon fits the gutter size. - Size2i icon_size = info_icon->get_size(); - if (icon_size.width > cache.info_gutter_width - horizontal_gap) { - icon_size.width = cache.info_gutter_width - horizontal_gap; - } - if (icon_size.height > get_row_height() - horizontal_gap) { - icon_size.height = get_row_height() - horizontal_gap; - } + switch (gutter.type) { + case GUTTER_TYPE_STRING: { + const String &text = get_line_gutter_text(line, g); + if (text == "") { + break; + } - Size2i icon_pos; - int xofs = horizontal_gap - (info_icon->get_width() / 4); - int yofs = vertical_gap - (info_icon->get_height() / 4); - icon_pos.x = gutter_left + xofs + ofs_x; - icon_pos.y = ofs_y + yofs; + 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)); + } break; + case GUTTER_TPYE_ICON: { + const Ref<Texture2D> icon = get_line_gutter_icon(line, g); + if (icon.is_null()) { + break; + } - draw_texture_rect(info_icon, Rect2(icon_pos, icon_size)); - } + Rect2i gutter_rect = Rect2i(Point2i(gutter_offset, ofs_y), Size2i(gutter.width, get_row_height())); - // Draw execution marker. - if (executing_line == line) { - if (draw_breakpoint_gutter) { - int icon_extra_size = 4; - int vertical_gap = (get_row_height() * 40) / 100; - int horizontal_gap = (cache.breakpoint_gutter_width * 30) / 100; - int marker_height = get_row_height() - (vertical_gap * 2) + icon_extra_size; - int marker_width = cache.breakpoint_gutter_width - (horizontal_gap * 2) + icon_extra_size; - cache.executing_icon->draw_rect(ci, Rect2(cache.style_normal->get_margin(MARGIN_LEFT) + horizontal_gap - 2 - icon_extra_size / 2, ofs_y + vertical_gap - icon_extra_size / 2, marker_width, marker_height), false, Color(cache.executing_line_color.r, cache.executing_line_color.g, cache.executing_line_color.b)); - } else { -#ifdef TOOLS_ENABLED - RenderingServer::get_singleton()->canvas_item_add_rect(ci, Rect2(xmargin_beg + ofs_x, ofs_y + get_row_height() - EDSCALE, xmargin_end - xmargin_beg, EDSCALE), cache.executing_line_color); -#else - RenderingServer::get_singleton()->canvas_item_add_rect(ci, Rect2(xmargin_beg + ofs_x, ofs_y, xmargin_end - xmargin_beg, get_row_height()), cache.executing_line_color); -#endif - } - } + int horizontal_padding = gutter_rect.size.x / 6; + int vertical_padding = gutter_rect.size.y / 6; - // Draw fold markers. - if (draw_fold_gutter) { - int horizontal_gap = (cache.fold_gutter_width * 30) / 100; - int gutter_left = cache.style_normal->get_margin(MARGIN_LEFT) + cache.breakpoint_gutter_width + cache.line_number_w + cache.info_gutter_width; - if (is_folded(line)) { - int xofs = horizontal_gap - (cache.can_fold_icon->get_width()) / 2; - int yofs = (get_row_height() - cache.folded_icon->get_height()) / 2; - cache.folded_icon->draw(ci, Point2(gutter_left + xofs + ofs_x, ofs_y + yofs), cache.code_folding_color); - } else if (can_fold(line)) { - int xofs = -cache.can_fold_icon->get_width() / 2 - horizontal_gap + 3; - int yofs = (get_row_height() - cache.can_fold_icon->get_height()) / 2; - cache.can_fold_icon->draw(ci, Point2(gutter_left + xofs + ofs_x, ofs_y + yofs), cache.code_folding_color); - } - } + gutter_rect.position += Point2(horizontal_padding, vertical_padding); + gutter_rect.size -= Point2(horizontal_padding, vertical_padding) * 2; - // Draw line numbers. - if (cache.line_number_w) { - int yofs = ofs_y + (get_row_height() - cache.font->get_height()) / 2; - String fc = String::num(line + 1); - while (fc.length() < line_number_char_count) { - fc = line_num_padding + fc; + icon->draw_rect(ci, gutter_rect, false, get_line_gutter_item_color(line, g)); + } break; + case GUTTER_TPYE_CUSTOM: { + if (gutter.custom_draw_obj.is_valid()) { + Object *cdo = ObjectDB::get_instance(gutter.custom_draw_obj); + if (cdo) { + Rect2i gutter_rect = Rect2i(Point2i(gutter_offset, ofs_y), Size2i(gutter.width, get_row_height())); + cdo->call(gutter.custom_draw_callback, line, g, Rect2(gutter_rect)); + } + } + } break; } - cache.font->draw(ci, Point2(cache.style_normal->get_margin(MARGIN_LEFT) + cache.breakpoint_gutter_width + cache.info_gutter_width + ofs_x, yofs + cache.font->get_ascent()), fc, text.is_safe(line) ? cache.safe_line_number_color : cache.line_number_color); + gutter_offset += gutter.width; } } @@ -1797,18 +1709,34 @@ void TextEdit::backspace_at_cursor() { int prev_line = cursor.column ? cursor.line : cursor.line - 1; int prev_column = cursor.column ? (cursor.column - 1) : (text[cursor.line - 1].length()); - if (is_line_hidden(cursor.line)) { - set_line_as_hidden(prev_line, true); - } - if (is_line_set_as_breakpoint(cursor.line)) { - if (!text.is_breakpoint(prev_line)) { - emit_signal("breakpoint_toggled", prev_line); + if (cursor.line != prev_line) { + for (int i = 0; i < gutters.size(); i++) { + if (!gutters[i].overwritable) { + continue; + } + + if (text.get_line_gutter_text(cursor.line, i) != "") { + text.set_line_gutter_text(prev_line, i, text.get_line_gutter_text(cursor.line, i)); + text.set_line_gutter_item_color(prev_line, i, text.get_line_gutter_item_color(cursor.line, i)); + } + + if (text.get_line_gutter_icon(cursor.line, i).is_valid()) { + text.set_line_gutter_icon(prev_line, i, text.get_line_gutter_icon(cursor.line, i)); + text.set_line_gutter_item_color(prev_line, i, text.get_line_gutter_item_color(cursor.line, i)); + } + + if (text.get_line_gutter_metadata(cursor.line, i) != "") { + text.set_line_gutter_metadata(prev_line, i, text.get_line_gutter_metadata(cursor.line, i)); + } + + if (text.is_line_gutter_clickable(cursor.line, i)) { + text.set_line_gutter_clickable(prev_line, i, true); + } } - set_line_as_breakpoint(prev_line, true); } - if (text.has_info_icon(cursor.line)) { - set_line_info_icon(prev_line, text.get_info_icon(cursor.line), text.get_info(cursor.line)); + if (is_line_hidden(cursor.line)) { + set_line_as_hidden(prev_line, true); } if (auto_brace_completion_enabled && @@ -1998,7 +1926,7 @@ void TextEdit::_get_mouse_pos(const Point2i &p_mouse, int &r_row, int &r_col) co row = text.size() - 1; col = text[row].size(); } else { - int colx = p_mouse.x - (cache.style_normal->get_margin(MARGIN_LEFT) + cache.line_number_w + cache.breakpoint_gutter_width + cache.fold_gutter_width + cache.info_gutter_width); + int colx = p_mouse.x - (cache.style_normal->get_margin(MARGIN_LEFT) + gutters_width + gutter_padding); colx += cursor.x_ofs; col = get_char_pos_for_line(colx, row, wrap_index); if (is_wrap_enabled() && wrap_index < times_line_wraps(row)) { @@ -2043,7 +1971,7 @@ Vector2i TextEdit::_get_cursor_pixel_pos() { // Calculate final pixel position int y = (row - get_v_scroll_offset() + 1 /*Bottom of line*/) * get_row_height(); - int x = cache.style_normal->get_margin(MARGIN_LEFT) + cache.line_number_w + cache.breakpoint_gutter_width + cache.fold_gutter_width + cache.info_gutter_width - cursor.x_ofs; + int x = cache.style_normal->get_margin(MARGIN_LEFT) + gutters_width + gutter_padding - cursor.x_ofs; int ix = 0; while (ix < rows2[0].size() && ix < cursor.column) { if (cache.font != nullptr) { @@ -2178,45 +2106,24 @@ void TextEdit::_gui_input(const Ref<InputEvent> &p_gui_input) { int row, col; _get_mouse_pos(Point2i(mb->get_position().x, mb->get_position().y), row, col); - // Toggle breakpoint on gutter click. - if (draw_breakpoint_gutter) { - int gutter = cache.style_normal->get_margin(MARGIN_LEFT); - if (mb->get_position().x > gutter - 6 && mb->get_position().x <= gutter + cache.breakpoint_gutter_width - 3) { - set_line_as_breakpoint(row, !is_line_set_as_breakpoint(row)); - emit_signal("breakpoint_toggled", row); - return; + int left_margin = cache.style_normal->get_margin(MARGIN_LEFT); + for (int i = 0; i < gutters.size(); i++) { + if (!gutters[i].draw || gutters[i].width <= 0) { + continue; } - } - // Emit info clicked. - if (draw_info_gutter && text.has_info_icon(row)) { - int left_margin = cache.style_normal->get_margin(MARGIN_LEFT); - int gutter_left = left_margin + cache.breakpoint_gutter_width; - if (mb->get_position().x > gutter_left - 6 && mb->get_position().x <= gutter_left + cache.info_gutter_width - 3) { - emit_signal("info_clicked", row, text.get_info(row)); + if (mb->get_position().x > left_margin && mb->get_position().x <= (left_margin + gutters[i].width) - 3) { + emit_signal("gutter_clicked", row, i); return; } - } - // Toggle fold on gutter click if can. - if (draw_fold_gutter) { - int left_margin = cache.style_normal->get_margin(MARGIN_LEFT); - int gutter_left = left_margin + cache.breakpoint_gutter_width + cache.line_number_w + cache.info_gutter_width; - if (mb->get_position().x > gutter_left - 6 && mb->get_position().x <= gutter_left + cache.fold_gutter_width - 3) { - if (is_folded(row)) { - unfold_line(row); - } else if (can_fold(row)) { - fold_line(row); - } - return; - } + left_margin += gutters[i].width; } // Unfold on folded icon click. if (is_folded(row)) { - int line_width = text.get_line_width(row); - line_width += cache.style_normal->get_margin(MARGIN_LEFT) + cache.line_number_w + cache.breakpoint_gutter_width + cache.info_gutter_width + cache.fold_gutter_width - cursor.x_ofs; - if (mb->get_position().x > line_width - 3 && mb->get_position().x <= line_width + cache.folded_eol_icon->get_width() + 3) { + left_margin += gutter_padding + text.get_line_width(row) - cursor.x_ofs; + if (mb->get_position().x > left_margin && mb->get_position().x <= left_margin + cache.folded_eol_icon->get_width() + 3) { unfold_line(row); return; } @@ -3775,30 +3682,15 @@ void TextEdit::_base_insert_text(int p_line, int p_char, const String &p_text, i Vector<String> substrings = p_text.replace("\r", "").split("\n"); - /* STEP 2: Fire breakpoint_toggled signals. */ - // Is this just a new empty line? bool shift_first_line = p_char == 0 && p_text.replace("\r", "") == "\n"; - int i = p_line + !shift_first_line; - int lines = substrings.size() - 1; - for (; i < text.size(); i++) { - if (text.is_breakpoint(i)) { - if ((i - lines < p_line || !text.is_breakpoint(i - lines)) || (i - lines == p_line && !shift_first_line)) { - emit_signal("breakpoint_toggled", i); - } - if (i + lines >= text.size() || !text.is_breakpoint(i + lines)) { - emit_signal("breakpoint_toggled", i + lines); - } - } - } - - /* STEP 3: Add spaces if the char is greater than the end of the line. */ + /* STEP 2: Add spaces if the char is greater than the end of the line. */ while (p_char > text[p_line].length()) { text.set(p_line, text[p_line] + String::chr(' ')); } - /* STEP 4: Separate dest string in pre and post text. */ + /* STEP 3: Separate dest string in pre and post text. */ String preinsert_text = text[p_line].substr(0, p_char); String postinsert_text = text[p_line].substr(p_char, text[p_line].size()); @@ -3818,15 +3710,10 @@ void TextEdit::_base_insert_text(int p_line, int p_char, const String &p_text, i } if (shift_first_line) { - text.set_breakpoint(p_line + 1, text.is_breakpoint(p_line)); + text.move_gutters(p_line, p_line + 1); text.set_hidden(p_line + 1, text.is_hidden(p_line)); - if (text.has_info_icon(p_line)) { - text.set_info_icon(p_line + 1, text.get_info_icon(p_line), text.get_info(p_line)); - } - text.set_breakpoint(p_line, false); text.set_hidden(p_line, false); - text.set_info_icon(p_line, nullptr, ""); } text.set_line_wrap_amount(p_line, -1); @@ -3840,7 +3727,7 @@ void TextEdit::_base_insert_text(int p_line, int p_char, const String &p_text, i } text_changed_dirty = true; } - emit_signal("line_edited_from", p_line); + emit_signal("lines_edited_from", p_line, r_end_line); } String TextEdit::_base_get_text(int p_from_line, int p_from_column, int p_to_line, int p_to_column) const { @@ -3877,19 +3764,6 @@ void TextEdit::_base_remove_text(int p_from_line, int p_from_column, int p_to_li String pre_text = text[p_from_line].substr(0, p_from_column); String post_text = text[p_to_line].substr(p_to_column, text[p_to_line].length()); - int lines = p_to_line - p_from_line; - - for (int i = p_from_line + 1; i < text.size(); i++) { - if (text.is_breakpoint(i)) { - if (i + lines >= text.size() || !text.is_breakpoint(i + lines)) { - emit_signal("breakpoint_toggled", i); - } - if (i > p_to_line && (i - lines < 0 || !text.is_breakpoint(i - lines))) { - emit_signal("breakpoint_toggled", i - lines); - } - } - } - for (int i = p_from_line; i < p_to_line; i++) { text.remove(p_from_line + 1); } @@ -3903,7 +3777,7 @@ void TextEdit::_base_remove_text(int p_from_line, int p_from_column, int p_to_li } text_changed_dirty = true; } - emit_signal("line_edited_from", p_from_line); + emit_signal("lines_edited_from", p_to_line, 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) { @@ -4097,7 +3971,7 @@ int TextEdit::get_total_visible_rows() const { } void TextEdit::_update_wrap_at() { - wrap_at = get_size().width - cache.style_normal->get_minimum_size().width - cache.line_number_w - cache.breakpoint_gutter_width - cache.fold_gutter_width - cache.info_gutter_width - cache.minimap_width - wrap_right_offset; + wrap_at = get_size().width - cache.style_normal->get_minimum_size().width - gutters_width - gutter_padding - cache.minimap_width - wrap_right_offset; update_cursor_wrap_offset(); text.clear_wrap_cache(); @@ -4132,7 +4006,7 @@ void TextEdit::adjust_viewport_to_cursor() { set_line_as_last_visible(cur_line, cur_wrap); } - int visible_width = get_size().width - cache.style_normal->get_minimum_size().width - cache.line_number_w - cache.breakpoint_gutter_width - cache.fold_gutter_width - cache.info_gutter_width - cache.minimap_width; + int visible_width = get_size().width - cache.style_normal->get_minimum_size().width - gutters_width - gutter_padding - cache.minimap_width; if (v_scroll->is_visible_in_tree()) { visible_width -= v_scroll->get_combined_minimum_size().width; } @@ -4167,7 +4041,7 @@ void TextEdit::center_viewport_to_cursor() { } set_line_as_center_visible(cursor.line, get_cursor_wrap_index()); - int visible_width = get_size().width - cache.style_normal->get_minimum_size().width - cache.line_number_w - cache.breakpoint_gutter_width - cache.fold_gutter_width - cache.info_gutter_width - cache.minimap_width; + int visible_width = get_size().width - cache.style_normal->get_minimum_size().width - gutters_width - gutter_padding - cache.minimap_width; if (v_scroll->is_visible_in_tree()) { visible_width -= v_scroll->get_combined_minimum_size().width; } @@ -4614,54 +4488,41 @@ Control::CursorShape TextEdit::get_cursor_shape(const Point2 &p_pos) const { return CURSOR_POINTING_HAND; } - int gutter = cache.style_normal->get_margin(MARGIN_LEFT) + cache.line_number_w + cache.breakpoint_gutter_width + cache.fold_gutter_width + cache.info_gutter_width; if ((completion_active && completion_rect.has_point(p_pos))) { return CURSOR_ARROW; } - if (p_pos.x < gutter) { - int row, col; - _get_mouse_pos(p_pos, row, col); - int left_margin = cache.style_normal->get_margin(MARGIN_LEFT); - // Breakpoint icon. - if (draw_breakpoint_gutter && p_pos.x > left_margin - 6 && p_pos.x <= left_margin + cache.breakpoint_gutter_width - 3) { - return CURSOR_POINTING_HAND; - } + int row, col; + _get_mouse_pos(p_pos, row, col); - // Info icons. - int gutter_left = left_margin + cache.breakpoint_gutter_width + cache.info_gutter_width; - if (draw_info_gutter && p_pos.x > left_margin + cache.breakpoint_gutter_width - 6 && p_pos.x <= gutter_left - 3) { - if (text.has_info_icon(row)) { - return CURSOR_POINTING_HAND; + int left_margin = cache.style_normal->get_margin(MARGIN_LEFT); + int gutter = left_margin + gutters_width; + if (p_pos.x < gutter) { + for (int i = 0; i < gutters.size(); i++) { + if (!gutters[i].draw) { + continue; } - return CURSOR_ARROW; - } - // Fold icon. - if (draw_fold_gutter && p_pos.x > gutter_left + cache.line_number_w - 6 && p_pos.x <= gutter_left + cache.line_number_w + cache.fold_gutter_width - 3) { - if (is_folded(row) || can_fold(row)) { - return CURSOR_POINTING_HAND; - } else { - return CURSOR_ARROW; + if (p_pos.x > left_margin && p_pos.x <= (left_margin + gutters[i].width) - 3) { + if (gutters[i].clickable || is_line_gutter_clickable(row, i)) { + return CURSOR_POINTING_HAND; + } } + left_margin += gutters[i].width; } + return CURSOR_ARROW; + } + int xmargin_end = get_size().width - cache.style_normal->get_margin(MARGIN_RIGHT); + if (draw_minimap && p_pos.x > xmargin_end - minimap_width && p_pos.x <= xmargin_end) { return CURSOR_ARROW; - } else { - int xmargin_end = get_size().width - cache.style_normal->get_margin(MARGIN_RIGHT); - if (draw_minimap && p_pos.x > xmargin_end - minimap_width && p_pos.x <= xmargin_end) { - return CURSOR_ARROW; - } - - int row, col; - _get_mouse_pos(p_pos, row, col); - // EOL fold icon. - if (is_folded(row)) { - int line_width = text.get_line_width(row); - line_width += cache.style_normal->get_margin(MARGIN_LEFT) + cache.line_number_w + cache.breakpoint_gutter_width + cache.fold_gutter_width + cache.info_gutter_width - cursor.x_ofs; - if (p_pos.x > line_width - 3 && p_pos.x <= line_width + cache.folded_eol_icon->get_width() + 3) { - return CURSOR_POINTING_HAND; - } + } + + // EOL fold icon. + if (is_folded(row)) { + gutter += gutter_padding + text.get_line_width(row) - cursor.x_ofs; + if (p_pos.x > gutter - 3 && p_pos.x <= gutter + cache.folded_eol_icon->get_width() + 3) { + return CURSOR_POINTING_HAND; } } @@ -4861,8 +4722,6 @@ void TextEdit::_update_caches() { cache.font = get_theme_font("font"); cache.caret_color = get_theme_color("caret_color"); cache.caret_background_color = get_theme_color("caret_background_color"); - cache.line_number_color = get_theme_color("line_number_color"); - cache.safe_line_number_color = get_theme_color("safe_line_number_color"); 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"); @@ -4870,9 +4729,6 @@ void TextEdit::_update_caches() { cache.mark_color = get_theme_color("mark_color"); cache.current_line_color = get_theme_color("current_line_color"); cache.line_length_guideline_color = get_theme_color("line_length_guideline_color"); - cache.bookmark_color = get_theme_color("bookmark_color"); - cache.breakpoint_color = get_theme_color("breakpoint_color"); - cache.executing_line_color = get_theme_color("executing_line_color"); cache.code_folding_color = get_theme_color("code_folding_color"); cache.brace_mismatch_color = get_theme_color("brace_mismatch_color"); cache.word_highlighted_color = get_theme_color("word_highlighted_color"); @@ -4887,10 +4743,7 @@ void TextEdit::_update_caches() { cache.row_height = cache.font->get_height() + cache.line_spacing; cache.tab_icon = get_theme_icon("tab"); cache.space_icon = get_theme_icon("space"); - cache.folded_icon = get_theme_icon("folded"); - cache.can_fold_icon = get_theme_icon("fold"); 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(); @@ -4899,6 +4752,7 @@ void TextEdit::_update_caches() { } } +/* Syntax Highlighting. */ Ref<SyntaxHighlighter> TextEdit::get_syntax_highlighter() { return syntax_highlighter; } @@ -4911,6 +4765,187 @@ void TextEdit::set_syntax_highlighter(Ref<SyntaxHighlighter> p_syntax_highlighte update(); } +/* Gutters. */ +void TextEdit::_update_gutter_width() { + gutters_width = 0; + for (int i = 0; i < gutters.size(); i++) { + if (gutters[i].draw) { + gutters_width += gutters[i].width; + } + } + if (gutters_width > 0) { + gutter_padding = 2; + } + update(); +} + +void TextEdit::add_gutter(int p_at) { + if (p_at < 0 || p_at > gutters.size()) { + gutters.push_back(GutterInfo()); + } else { + gutters.insert(p_at, GutterInfo()); + } + + for (int i = 0; i < text.size() + 1; i++) { + text.add_gutter(p_at); + } + emit_signal("gutter_added"); + update(); +} + +void TextEdit::remove_gutter(int p_gutter) { + ERR_FAIL_INDEX(p_gutter, gutters.size()); + + gutters.remove(p_gutter); + + for (int i = 0; i < text.size() + 1; i++) { + text.remove_gutter(p_gutter); + } + emit_signal("gutter_removed"); + update(); +} + +int TextEdit::get_gutter_count() const { + return gutters.size(); +} + +void TextEdit::set_gutter_name(int p_gutter, const String &p_name) { + ERR_FAIL_INDEX(p_gutter, gutters.size()); + gutters.write[p_gutter].name = p_name; +} + +String TextEdit::get_gutter_name(int p_gutter) const { + ERR_FAIL_INDEX_V(p_gutter, gutters.size(), ""); + return gutters[p_gutter].name; +} + +void TextEdit::set_gutter_type(int p_gutter, GutterType p_type) { + ERR_FAIL_INDEX(p_gutter, gutters.size()); + gutters.write[p_gutter].type = p_type; + update(); +} + +TextEdit::GutterType TextEdit::get_gutter_type(int p_gutter) const { + ERR_FAIL_INDEX_V(p_gutter, gutters.size(), GUTTER_TYPE_STRING); + return gutters[p_gutter].type; +} + +void TextEdit::set_gutter_width(int p_gutter, int p_width) { + ERR_FAIL_INDEX(p_gutter, gutters.size()); + gutters.write[p_gutter].width = p_width; + _update_gutter_width(); +} + +int TextEdit::get_gutter_width(int p_gutter) const { + ERR_FAIL_INDEX_V(p_gutter, gutters.size(), -1); + return gutters[p_gutter].width; +} + +void TextEdit::set_gutter_draw(int p_gutter, bool p_draw) { + ERR_FAIL_INDEX(p_gutter, gutters.size()); + gutters.write[p_gutter].draw = p_draw; + _update_gutter_width(); +} + +bool TextEdit::is_gutter_drawn(int p_gutter) const { + ERR_FAIL_INDEX_V(p_gutter, gutters.size(), false); + return gutters[p_gutter].draw; +} + +void TextEdit::set_gutter_clickable(int p_gutter, bool p_clickable) { + ERR_FAIL_INDEX(p_gutter, gutters.size()); + gutters.write[p_gutter].clickable = p_clickable; + update(); +} + +bool TextEdit::is_gutter_clickable(int p_gutter) const { + ERR_FAIL_INDEX_V(p_gutter, gutters.size(), false); + return gutters[p_gutter].clickable; +} + +void TextEdit::set_gutter_overwritable(int p_gutter, bool p_overwritable) { + ERR_FAIL_INDEX(p_gutter, gutters.size()); + gutters.write[p_gutter].overwritable = p_overwritable; +} + +bool TextEdit::is_gutter_overwritable(int p_gutter) const { + ERR_FAIL_INDEX_V(p_gutter, gutters.size(), false); + return gutters[p_gutter].overwritable; +} + +void TextEdit::set_gutter_custom_draw(int p_gutter, Object *p_object, const StringName &p_callback) { + ERR_FAIL_INDEX(p_gutter, gutters.size()); + ERR_FAIL_NULL(p_object); + + gutters.write[p_gutter].custom_draw_obj = p_object->get_instance_id(); + gutters.write[p_gutter].custom_draw_callback = p_callback; + update(); +} + +// Line gutters. +void TextEdit::set_line_gutter_metadata(int p_line, int p_gutter, const Variant &p_metadata) { + ERR_FAIL_INDEX(p_line, text.size()); + ERR_FAIL_INDEX(p_gutter, gutters.size()); + text.set_line_gutter_metadata(p_line, p_gutter, p_metadata); +} + +Variant TextEdit::get_line_gutter_metadata(int p_line, int p_gutter) const { + ERR_FAIL_INDEX_V(p_line, text.size(), ""); + ERR_FAIL_INDEX_V(p_gutter, gutters.size(), ""); + return text.get_line_gutter_metadata(p_line, p_gutter); +} + +void TextEdit::set_line_gutter_text(int p_line, int p_gutter, const String &p_text) { + ERR_FAIL_INDEX(p_line, text.size()); + ERR_FAIL_INDEX(p_gutter, gutters.size()); + text.set_line_gutter_text(p_line, p_gutter, p_text); + update(); +} + +String TextEdit::get_line_gutter_text(int p_line, int p_gutter) const { + ERR_FAIL_INDEX_V(p_line, text.size(), ""); + ERR_FAIL_INDEX_V(p_gutter, gutters.size(), ""); + return text.get_line_gutter_text(p_line, p_gutter); +} + +void TextEdit::set_line_gutter_icon(int p_line, int p_gutter, Ref<Texture2D> p_icon) { + ERR_FAIL_INDEX(p_line, text.size()); + ERR_FAIL_INDEX(p_gutter, gutters.size()); + text.set_line_gutter_icon(p_line, p_gutter, p_icon); + update(); +} + +Ref<Texture2D> TextEdit::get_line_gutter_icon(int p_line, int p_gutter) const { + ERR_FAIL_INDEX_V(p_line, text.size(), Ref<Texture2D>()); + ERR_FAIL_INDEX_V(p_gutter, gutters.size(), Ref<Texture2D>()); + return text.get_line_gutter_icon(p_line, p_gutter); +} + +void TextEdit::set_line_gutter_item_color(int p_line, int p_gutter, const Color &p_color) { + ERR_FAIL_INDEX(p_line, text.size()); + ERR_FAIL_INDEX(p_gutter, gutters.size()); + text.set_line_gutter_item_color(p_line, p_gutter, p_color); + update(); +} + +Color TextEdit::get_line_gutter_item_color(int p_line, int p_gutter) { + ERR_FAIL_INDEX_V(p_line, text.size(), Color()); + ERR_FAIL_INDEX_V(p_gutter, gutters.size(), Color()); + return text.get_line_gutter_item_color(p_line, p_gutter); +} + +void TextEdit::set_line_gutter_clickable(int p_line, int p_gutter, bool p_clickable) { + ERR_FAIL_INDEX(p_line, text.size()); + ERR_FAIL_INDEX(p_gutter, gutters.size()); + text.set_line_gutter_clickable(p_line, p_gutter, p_clickable); +} + +bool TextEdit::is_line_gutter_clickable(int p_line, int p_gutter) const { + ERR_FAIL_INDEX_V(p_line, text.size(), false); + ERR_FAIL_INDEX_V(p_gutter, gutters.size(), false); + return text.is_line_gutter_clickable(p_line, p_gutter); +} + void TextEdit::add_keyword(const String &p_keyword) { keywords.insert(p_keyword); } @@ -5341,106 +5376,6 @@ void TextEdit::set_line_as_marked(int p_line, bool p_marked) { update(); } -void TextEdit::set_line_as_safe(int p_line, bool p_safe) { - ERR_FAIL_INDEX(p_line, text.size()); - text.set_safe(p_line, p_safe); - update(); -} - -bool TextEdit::is_line_set_as_safe(int p_line) const { - ERR_FAIL_INDEX_V(p_line, text.size(), false); - return text.is_safe(p_line); -} - -void TextEdit::set_executing_line(int p_line) { - ERR_FAIL_INDEX(p_line, text.size()); - executing_line = p_line; - update(); -} - -void TextEdit::clear_executing_line() { - executing_line = -1; - update(); -} - -bool TextEdit::is_line_set_as_bookmark(int p_line) const { - ERR_FAIL_INDEX_V(p_line, text.size(), false); - return text.is_bookmark(p_line); -} - -void TextEdit::set_line_as_bookmark(int p_line, bool p_bookmark) { - ERR_FAIL_INDEX(p_line, text.size()); - text.set_bookmark(p_line, p_bookmark); - update(); -} - -void TextEdit::get_bookmarks(List<int> *p_bookmarks) const { - for (int i = 0; i < text.size(); i++) { - if (text.is_bookmark(i)) { - p_bookmarks->push_back(i); - } - } -} - -Array TextEdit::get_bookmarks_array() const { - Array arr; - for (int i = 0; i < text.size(); i++) { - if (text.is_bookmark(i)) { - arr.append(i); - } - } - return arr; -} - -bool TextEdit::is_line_set_as_breakpoint(int p_line) const { - ERR_FAIL_INDEX_V(p_line, text.size(), false); - return text.is_breakpoint(p_line); -} - -void TextEdit::set_line_as_breakpoint(int p_line, bool p_breakpoint) { - ERR_FAIL_INDEX(p_line, text.size()); - text.set_breakpoint(p_line, p_breakpoint); - update(); -} - -void TextEdit::get_breakpoints(List<int> *p_breakpoints) const { - for (int i = 0; i < text.size(); i++) { - if (text.is_breakpoint(i)) { - p_breakpoints->push_back(i); - } - } -} - -Array TextEdit::get_breakpoints_array() const { - Array arr; - for (int i = 0; i < text.size(); i++) { - if (text.is_breakpoint(i)) { - arr.append(i); - } - } - return arr; -} - -void TextEdit::remove_breakpoints() { - for (int i = 0; i < text.size(); i++) { - if (text.is_breakpoint(i)) { - /* Should "breakpoint_toggled" be fired when breakpoints are removed this way? */ - text.set_breakpoint(i, false); - } - } -} - -void TextEdit::set_line_info_icon(int p_line, Ref<Texture2D> p_icon, String p_info) { - ERR_FAIL_INDEX(p_line, text.size()); - text.set_info_icon(p_line, p_icon, p_info); - update(); -} - -void TextEdit::clear_info_icons() { - text.clear_info_icons(); - update(); -} - void TextEdit::set_line_as_hidden(int p_line, bool p_hidden) { ERR_FAIL_INDEX(p_line, text.size()); if (is_hiding_enabled() || !p_hidden) { @@ -6502,20 +6437,6 @@ void TextEdit::insert_at(const String &p_text, int at) { } } -void TextEdit::set_show_line_numbers(bool p_show) { - line_numbers = p_show; - update(); -} - -void TextEdit::set_line_numbers_zero_padded(bool p_zero_padded) { - line_numbers_zero_padded = p_zero_padded; - update(); -} - -bool TextEdit::is_show_line_numbers_enabled() const { - return line_numbers; -} - void TextEdit::set_show_line_length_guidelines(bool p_show) { line_length_guidelines = p_show; update(); @@ -6531,69 +6452,6 @@ void TextEdit::set_line_length_guideline_hard_column(int p_column) { update(); } -void TextEdit::set_bookmark_gutter_enabled(bool p_draw) { - draw_bookmark_gutter = p_draw; - update(); -} - -bool TextEdit::is_bookmark_gutter_enabled() const { - return draw_bookmark_gutter; -} - -void TextEdit::set_breakpoint_gutter_enabled(bool p_draw) { - draw_breakpoint_gutter = p_draw; - update(); -} - -bool TextEdit::is_breakpoint_gutter_enabled() const { - return draw_breakpoint_gutter; -} - -void TextEdit::set_breakpoint_gutter_width(int p_gutter_width) { - breakpoint_gutter_width = p_gutter_width; - update(); -} - -int TextEdit::get_breakpoint_gutter_width() const { - return cache.breakpoint_gutter_width; -} - -void TextEdit::set_draw_fold_gutter(bool p_draw) { - draw_fold_gutter = p_draw; - update(); -} - -bool TextEdit::is_drawing_fold_gutter() const { - return draw_fold_gutter; -} - -void TextEdit::set_fold_gutter_width(int p_gutter_width) { - fold_gutter_width = p_gutter_width; - update(); -} - -int TextEdit::get_fold_gutter_width() const { - return cache.fold_gutter_width; -} - -void TextEdit::set_draw_info_gutter(bool p_draw) { - draw_info_gutter = p_draw; - update(); -} - -bool TextEdit::is_drawing_info_gutter() const { - return draw_info_gutter; -} - -void TextEdit::set_info_gutter_width(int p_gutter_width) { - info_gutter_width = p_gutter_width; - update(); -} - -int TextEdit::get_info_gutter_width() const { - return info_gutter_width; -} - void TextEdit::set_draw_minimap(bool p_draw) { draw_minimap = p_draw; update(); @@ -6800,16 +6658,10 @@ void TextEdit::_bind_methods() { ClassDB::bind_method(D_METHOD("redo"), &TextEdit::redo); ClassDB::bind_method(D_METHOD("clear_undo_history"), &TextEdit::clear_undo_history); - ClassDB::bind_method(D_METHOD("set_show_line_numbers", "enable"), &TextEdit::set_show_line_numbers); - ClassDB::bind_method(D_METHOD("is_show_line_numbers_enabled"), &TextEdit::is_show_line_numbers_enabled); ClassDB::bind_method(D_METHOD("set_draw_tabs"), &TextEdit::set_draw_tabs); ClassDB::bind_method(D_METHOD("is_drawing_tabs"), &TextEdit::is_drawing_tabs); ClassDB::bind_method(D_METHOD("set_draw_spaces"), &TextEdit::set_draw_spaces); ClassDB::bind_method(D_METHOD("is_drawing_spaces"), &TextEdit::is_drawing_spaces); - ClassDB::bind_method(D_METHOD("set_breakpoint_gutter_enabled", "enable"), &TextEdit::set_breakpoint_gutter_enabled); - ClassDB::bind_method(D_METHOD("is_breakpoint_gutter_enabled"), &TextEdit::is_breakpoint_gutter_enabled); - ClassDB::bind_method(D_METHOD("set_draw_fold_gutter"), &TextEdit::set_draw_fold_gutter); - ClassDB::bind_method(D_METHOD("is_drawing_fold_gutter"), &TextEdit::is_drawing_fold_gutter); ClassDB::bind_method(D_METHOD("set_hiding_enabled", "enable"), &TextEdit::set_hiding_enabled); ClassDB::bind_method(D_METHOD("is_hiding_enabled"), &TextEdit::is_hiding_enabled); @@ -6832,6 +6684,40 @@ void TextEdit::_bind_methods() { 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); + /* Gutters. */ + BIND_ENUM_CONSTANT(GUTTER_TYPE_STRING); + BIND_ENUM_CONSTANT(GUTTER_TPYE_ICON); + BIND_ENUM_CONSTANT(GUTTER_TPYE_CUSTOM); + + ClassDB::bind_method(D_METHOD("add_gutter", "at"), &TextEdit::add_gutter, DEFVAL(-1)); + ClassDB::bind_method(D_METHOD("remove_gutter", "gutter"), &TextEdit::remove_gutter); + ClassDB::bind_method(D_METHOD("get_gutter_count"), &TextEdit::get_gutter_count); + ClassDB::bind_method(D_METHOD("set_gutter_name", "gutter", "name"), &TextEdit::set_gutter_name); + ClassDB::bind_method(D_METHOD("get_gutter_name", "gutter"), &TextEdit::get_gutter_name); + ClassDB::bind_method(D_METHOD("set_gutter_type", "gutter", "type"), &TextEdit::set_gutter_type); + ClassDB::bind_method(D_METHOD("get_gutter_type", "gutter"), &TextEdit::get_gutter_type); + ClassDB::bind_method(D_METHOD("set_gutter_width", "gutter", "width"), &TextEdit::set_gutter_width); + ClassDB::bind_method(D_METHOD("get_gutter_width", "gutter"), &TextEdit::get_gutter_width); + ClassDB::bind_method(D_METHOD("set_gutter_draw", "gutter", "draw"), &TextEdit::set_gutter_draw); + ClassDB::bind_method(D_METHOD("is_gutter_drawn", "gutter"), &TextEdit::is_gutter_drawn); + ClassDB::bind_method(D_METHOD("set_gutter_clickable", "gutter", "clickable"), &TextEdit::set_gutter_clickable); + ClassDB::bind_method(D_METHOD("is_gutter_clickable", "gutter"), &TextEdit::is_gutter_clickable); + ClassDB::bind_method(D_METHOD("set_gutter_overwritable", "gutter", "overwritable"), &TextEdit::set_gutter_overwritable); + ClassDB::bind_method(D_METHOD("is_gutter_overwritable", "gutter"), &TextEdit::is_gutter_overwritable); + ClassDB::bind_method(D_METHOD("set_gutter_custom_draw", "column", "object", "callback"), &TextEdit::set_gutter_custom_draw); + + // Line gutters. + ClassDB::bind_method(D_METHOD("set_line_gutter_metadata", "line", "gutter", "metadata"), &TextEdit::set_line_gutter_metadata); + ClassDB::bind_method(D_METHOD("get_line_gutter_metadata", "line", "gutter"), &TextEdit::get_line_gutter_metadata); + ClassDB::bind_method(D_METHOD("set_line_gutter_text", "line", "gutter", "text"), &TextEdit::set_line_gutter_text); + ClassDB::bind_method(D_METHOD("get_line_gutter_text", "line", "gutter"), &TextEdit::get_line_gutter_text); + ClassDB::bind_method(D_METHOD("set_line_gutter_icon", "line", "gutter", "icon"), &TextEdit::set_line_gutter_icon); + ClassDB::bind_method(D_METHOD("get_line_gutter_icon", "line", "gutter"), &TextEdit::get_line_gutter_icon); + ClassDB::bind_method(D_METHOD("set_line_gutter_item_color", "line", "gutter", "color"), &TextEdit::set_line_gutter_item_color); + ClassDB::bind_method(D_METHOD("get_line_gutter_item_color", "line", "gutter"), &TextEdit::get_line_gutter_item_color); + ClassDB::bind_method(D_METHOD("set_line_gutter_clickable", "line", "gutter", "clickable"), &TextEdit::set_line_gutter_clickable); + ClassDB::bind_method(D_METHOD("is_line_gutter_clickable", "line", "gutter"), &TextEdit::is_line_gutter_clickable); + 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); @@ -6847,9 +6733,6 @@ void TextEdit::_bind_methods() { ClassDB::bind_method(D_METHOD("menu_option", "option"), &TextEdit::menu_option); ClassDB::bind_method(D_METHOD("get_menu"), &TextEdit::get_menu); - ClassDB::bind_method(D_METHOD("get_breakpoints"), &TextEdit::get_breakpoints_array); - ClassDB::bind_method(D_METHOD("remove_breakpoints"), &TextEdit::remove_breakpoints); - ClassDB::bind_method(D_METHOD("draw_minimap", "draw"), &TextEdit::set_draw_minimap); ClassDB::bind_method(D_METHOD("is_drawing_minimap"), &TextEdit::is_drawing_minimap); ClassDB::bind_method(D_METHOD("set_minimap_width", "width"), &TextEdit::set_minimap_width); @@ -6858,11 +6741,8 @@ 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, "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"); - ADD_PROPERTY(PropertyInfo(Variant::BOOL, "breakpoint_gutter"), "set_breakpoint_gutter_enabled", "is_breakpoint_gutter_enabled"); - ADD_PROPERTY(PropertyInfo(Variant::BOOL, "fold_gutter"), "set_draw_fold_gutter", "is_drawing_fold_gutter"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "highlight_all_occurrences"), "set_highlight_all_occurrences", "is_highlight_all_occurrences_enabled"); 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"); @@ -6890,11 +6770,12 @@ 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("lines_edited_from", PropertyInfo(Variant::INT, "from_line"), PropertyInfo(Variant::INT, "to_line"))); ADD_SIGNAL(MethodInfo("request_completion")); - ADD_SIGNAL(MethodInfo("breakpoint_toggled", PropertyInfo(Variant::INT, "row"))); + ADD_SIGNAL(MethodInfo("gutter_clicked", PropertyInfo(Variant::INT, "line"), PropertyInfo(Variant::INT, "gutter"))); + ADD_SIGNAL(MethodInfo("gutter_added")); + ADD_SIGNAL(MethodInfo("gutter_removed")); ADD_SIGNAL(MethodInfo("symbol_lookup", PropertyInfo(Variant::STRING, "symbol"), PropertyInfo(Variant::INT, "row"), PropertyInfo(Variant::INT, "column"))); - ADD_SIGNAL(MethodInfo("info_clicked", PropertyInfo(Variant::INT, "row"), PropertyInfo(Variant::STRING, "info"))); ADD_SIGNAL(MethodInfo("symbol_validate", PropertyInfo(Variant::STRING, "symbol"))); BIND_ENUM_CONSTANT(MENU_CUT); @@ -6927,13 +6808,6 @@ TextEdit::TextEdit() { _update_caches(); cache.row_height = 1; cache.line_spacing = 1; - cache.line_number_w = 1; - cache.breakpoint_gutter_width = 0; - breakpoint_gutter_width = 0; - cache.fold_gutter_width = 0; - fold_gutter_width = 0; - info_gutter_width = 0; - cache.info_gutter_width = 0; set_default_cursor_shape(CURSOR_IBEAM); indent_size = 4; @@ -6997,15 +6871,9 @@ TextEdit::TextEdit() { completion_active = false; completion_line_ofs = 0; tooltip_obj = nullptr; - line_numbers = false; - line_numbers_zero_padded = false; line_length_guidelines = false; line_length_guideline_soft_col = 80; line_length_guideline_hard_col = 100; - draw_bookmark_gutter = false; - draw_breakpoint_gutter = false; - draw_fold_gutter = false; - draw_info_gutter = false; hiding_enabled = false; next_operation_is_complex = false; scroll_past_end_of_file_enabled = false; @@ -7043,8 +6911,6 @@ TextEdit::TextEdit() { set_readonly(false); menu->connect("id_pressed", callable_mp(this, &TextEdit::menu_option)); first_draw = true; - - executing_line = -1; } TextEdit::~TextEdit() { diff --git a/scene/gui/text_edit.h b/scene/gui/text_edit.h index 70d7365d71..562f4768ae 100644 --- a/scene/gui/text_edit.h +++ b/scene/gui/text_edit.h @@ -41,28 +41,53 @@ class TextEdit : public Control { GDCLASS(TextEdit, Control); public: + enum GutterType { + GUTTER_TYPE_STRING, + GUTTER_TPYE_ICON, + GUTTER_TPYE_CUSTOM + }; + +private: + struct GutterInfo { + GutterType type = GutterType::GUTTER_TYPE_STRING; + String name = ""; + int width = 24; + bool draw = true; + bool clickable = false; + bool overwritable = false; + + ObjectID custom_draw_obj = ObjectID(); + StringName custom_draw_callback; + }; + Vector<GutterInfo> gutters; + int gutters_width = 0; + int gutter_padding = 0; + + void _update_gutter_width(); + class Text { public: + struct Gutter { + Variant metadata; + bool clickable = false; + + Ref<Texture2D> icon = Ref<Texture2D>(); + String text = ""; + Color color = Color(1, 1, 1); + }; + struct Line { - int width_cache : 24; - bool marked : 1; - bool breakpoint : 1; - bool bookmark : 1; - bool hidden : 1; - bool safe : 1; - bool has_info : 1; - int wrap_amount_cache : 24; - Ref<Texture2D> info_icon; - String info; + Vector<Gutter> gutters; + + int32_t width_cache; + bool marked; + bool hidden; + int32_t wrap_amount_cache; String data; Line() { width_cache = 0; marked = false; - breakpoint = false; - bookmark = false; hidden = false; - safe = false; - has_info = false; wrap_amount_cache = 0; } }; @@ -70,7 +95,8 @@ public: private: mutable Vector<Line> text; Ref<Font> font; - int indent_size; + int indent_size = 4; + int gutter_count = 0; void _update_line_cache(int p_line) const; @@ -85,38 +111,37 @@ public: 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; } - void set_bookmark(int p_line, bool p_bookmark) { text.write[p_line].bookmark = p_bookmark; } - bool is_bookmark(int p_line) const { return text[p_line].bookmark; } - void set_breakpoint(int p_line, bool p_breakpoint) { text.write[p_line].breakpoint = p_breakpoint; } - bool is_breakpoint(int p_line) const { return text[p_line].breakpoint; } void set_hidden(int p_line, bool p_hidden) { text.write[p_line].hidden = p_hidden; } bool is_hidden(int p_line) const { return text[p_line].hidden; } - void set_safe(int p_line, bool p_safe) { text.write[p_line].safe = p_safe; } - bool is_safe(int p_line) const { return text[p_line].safe; } - void set_info_icon(int p_line, Ref<Texture2D> p_icon, String p_info) { - if (p_icon.is_null()) { - text.write[p_line].has_info = false; - return; - } - text.write[p_line].info_icon = p_icon; - text.write[p_line].info = p_info; - text.write[p_line].has_info = true; - } - bool has_info_icon(int p_line) const { return text[p_line].has_info; } - const Ref<Texture2D> &get_info_icon(int p_line) const { return text[p_line].info_icon; } - const String &get_info(int p_line) const { return text[p_line].info; } void insert(int p_at, const String &p_text); void remove(int p_at); int size() const { return text.size(); } void clear(); void clear_width_cache(); void clear_wrap_cache(); - void clear_info_icons(); _FORCE_INLINE_ const String &operator[](int p_line) const { return text[p_line].data; } - Text() { indent_size = 4; } + + /* Gutters. */ + void add_gutter(int p_at); + void remove_gutter(int p_gutter); + void move_gutters(int p_from_line, int p_to_line); + + void set_line_gutter_metadata(int p_line, int p_gutter, const Variant &p_metadata) { text.write[p_line].gutters.write[p_gutter].metadata = p_metadata; } + const Variant &get_line_gutter_metadata(int p_line, int p_gutter) const { return text[p_line].gutters[p_gutter].metadata; } + + void set_line_gutter_text(int p_line, int p_gutter, const String &p_text) { text.write[p_line].gutters.write[p_gutter].text = p_text; } + const String &get_line_gutter_text(int p_line, int p_gutter) const { return text[p_line].gutters[p_gutter].text; } + + void set_line_gutter_icon(int p_line, int p_gutter, Ref<Texture2D> p_icon) { text.write[p_line].gutters.write[p_gutter].icon = p_icon; } + const Ref<Texture2D> &get_line_gutter_icon(int p_line, int p_gutter) const { return text[p_line].gutters[p_gutter].icon; } + + void set_line_gutter_item_color(int p_line, int p_gutter, const Color &p_color) { text.write[p_line].gutters.write[p_gutter].color = p_color; } + const Color &get_line_gutter_item_color(int p_line, int p_gutter) const { return text[p_line].gutters[p_gutter].color; } + + void set_line_gutter_clickable(int p_line, int p_gutter, bool p_clickable) { text.write[p_line].gutters.write[p_gutter].clickable = p_clickable; } + bool is_line_gutter_clickable(int p_line, int p_gutter) const { return text[p_line].gutters[p_gutter].clickable; } }; -private: struct Cursor { int last_fit_x; int line, column; ///< cursor @@ -169,60 +194,6 @@ private: } } selection; - struct Cache { - Ref<Texture2D> tab_icon; - Ref<Texture2D> space_icon; - Ref<Texture2D> can_fold_icon; - Ref<Texture2D> folded_icon; - Ref<Texture2D> folded_eol_icon; - Ref<Texture2D> executing_icon; - Ref<StyleBox> style_normal; - Ref<StyleBox> style_focus; - Ref<StyleBox> style_readonly; - Ref<Font> font; - Color completion_background_color; - Color completion_selected_color; - Color completion_existing_color; - Color completion_font_color; - Color caret_color; - Color caret_background_color; - Color line_number_color; - Color safe_line_number_color; - Color font_color; - Color font_color_selected; - Color font_color_readonly; - Color selection_color; - Color mark_color; - Color bookmark_color; - Color breakpoint_color; - Color executing_line_color; - Color code_folding_color; - Color current_line_color; - Color line_length_guideline_color; - Color brace_mismatch_color; - Color word_highlighted_color; - Color search_result_color; - Color search_result_border_color; - Color background_color; - - int row_height; - int line_spacing; - int line_number_w; - int breakpoint_gutter_width; - int fold_gutter_width; - int info_gutter_width; - int minimap_width; - Cache() { - row_height = 0; - line_spacing = 0; - line_number_w = 0; - breakpoint_gutter_width = 0; - fold_gutter_width = 0; - info_gutter_width = 0; - minimap_width = 0; - } - } cache; - Map<int, Dictionary> syntax_highlighting_cache; struct TextOperation { @@ -318,19 +289,10 @@ private: bool cursor_changed_dirty; bool text_changed_dirty; bool undo_enabled; - bool line_numbers; - bool line_numbers_zero_padded; bool line_length_guidelines; int line_length_guideline_soft_col; int line_length_guideline_hard_col; - bool draw_bookmark_gutter; - bool draw_breakpoint_gutter; - int breakpoint_gutter_width; - bool draw_fold_gutter; - int fold_gutter_width; bool hiding_enabled; - bool draw_info_gutter; - int info_gutter_width; bool draw_minimap; int minimap_width; Point2 minimap_char_size; @@ -385,11 +347,8 @@ private: bool context_menu_enabled; bool shortcut_keys_enabled; - bool virtual_keyboard_enabled = true; - int executing_line; - void _generate_context_menu(); int get_visible_rows() const; @@ -447,8 +406,6 @@ private: Size2 get_minimum_size() const override; int _get_control_height() const; - int get_row_height() const; - void _reset_caret_blink_timer(); void _toggle_draw_caret(); @@ -480,6 +437,44 @@ private: int _calculate_spaces_till_next_right_indent(int column); protected: + struct Cache { + Ref<Texture2D> tab_icon; + Ref<Texture2D> space_icon; + Ref<Texture2D> folded_eol_icon; + Ref<StyleBox> style_normal; + Ref<StyleBox> style_focus; + Ref<StyleBox> style_readonly; + Ref<Font> font; + Color completion_background_color; + Color completion_selected_color; + Color completion_existing_color; + Color completion_font_color; + Color caret_color; + Color caret_background_color; + Color font_color; + Color font_color_selected; + Color font_color_readonly; + Color selection_color; + Color mark_color; + Color code_folding_color; + Color current_line_color; + Color line_length_guideline_color; + Color brace_mismatch_color; + Color word_highlighted_color; + Color search_result_color; + Color search_result_border_color; + Color background_color; + + int row_height; + int line_spacing; + int minimap_width; + Cache() { + row_height = 0; + line_spacing = 0; + minimap_width = 0; + } + } cache; + virtual String get_tooltip(const Point2 &p_pos) const override; void _insert_text(int p_line, int p_char, const String &p_text, int *r_end_line = nullptr, int *r_end_char = nullptr); @@ -494,9 +489,51 @@ protected: static void _bind_methods(); public: + /* Syntax Highlighting. */ Ref<SyntaxHighlighter> get_syntax_highlighter(); void set_syntax_highlighter(Ref<SyntaxHighlighter> p_syntax_highlighter); + /* Gutters. */ + void add_gutter(int p_at = -1); + void remove_gutter(int p_gutter); + int get_gutter_count() const; + + void set_gutter_name(int p_gutter, const String &p_name); + String get_gutter_name(int p_gutter) const; + + void set_gutter_type(int p_gutter, GutterType p_type); + GutterType get_gutter_type(int p_gutter) const; + + void set_gutter_width(int p_gutter, int p_width); + int get_gutter_width(int p_gutter) const; + + void set_gutter_draw(int p_gutter, bool p_draw); + bool is_gutter_drawn(int p_gutter) const; + + void set_gutter_clickable(int p_gutter, bool p_clickable); + bool is_gutter_clickable(int p_gutter) const; + + void set_gutter_overwritable(int p_gutter, bool p_overwritable); + bool is_gutter_overwritable(int p_gutter) const; + + void set_gutter_custom_draw(int p_gutter, Object *p_object, const StringName &p_callback); + + // Line gutters. + void set_line_gutter_metadata(int p_line, int p_gutter, const Variant &p_metadata); + Variant get_line_gutter_metadata(int p_line, int p_gutter) const; + + void set_line_gutter_text(int p_line, int p_gutter, const String &p_text); + String get_line_gutter_text(int p_line, int p_gutter) const; + + void set_line_gutter_icon(int p_line, int p_gutter, Ref<Texture2D> p_icon); + Ref<Texture2D> get_line_gutter_icon(int p_line, int p_gutter) const; + + void set_line_gutter_item_color(int p_line, int p_gutter, const Color &p_color); + Color get_line_gutter_item_color(int p_line, int p_gutter); + + void set_line_gutter_clickable(int p_line, int p_gutter, bool p_clickable); + bool is_line_gutter_clickable(int p_line, int p_gutter) const; + enum MenuItems { MENU_CUT, MENU_COPY, @@ -534,22 +571,6 @@ public: void insert_at(const String &p_text, int at); int get_line_count() const; void set_line_as_marked(int p_line, bool p_marked); - void set_line_as_bookmark(int p_line, bool p_bookmark); - bool is_line_set_as_bookmark(int p_line) const; - void get_bookmarks(List<int> *p_bookmarks) const; - Array get_bookmarks_array() const; - void set_line_as_breakpoint(int p_line, bool p_breakpoint); - bool is_line_set_as_breakpoint(int p_line) const; - void set_executing_line(int p_line); - void clear_executing_line(); - void set_line_as_safe(int p_line, bool p_safe); - bool is_line_set_as_safe(int p_line) const; - void get_breakpoints(List<int> *p_breakpoints) const; - Array get_breakpoints_array() const; - void remove_breakpoints(); - - void set_line_info_icon(int p_line, Ref<Texture2D> p_icon, String p_info = ""); - void clear_info_icons(); void set_line_as_hidden(int p_line, bool p_hidden); bool is_line_hidden(int p_line) const; @@ -569,6 +590,7 @@ public: String get_text(); String get_line(int line) const; void set_line(int line, String new_text); + int get_row_height() const; void backspace_at_cursor(); void indent_left(); @@ -690,39 +712,13 @@ public: void menu_option(int p_option); - void set_show_line_numbers(bool p_show); - bool is_show_line_numbers_enabled() const; - void set_highlight_current_line(bool p_enabled); bool is_highlight_current_line_enabled() const; - void set_line_numbers_zero_padded(bool p_zero_padded); - void set_show_line_length_guidelines(bool p_show); void set_line_length_guideline_soft_column(int p_column); void set_line_length_guideline_hard_column(int p_column); - void set_bookmark_gutter_enabled(bool p_draw); - bool is_bookmark_gutter_enabled() const; - - void set_breakpoint_gutter_enabled(bool p_draw); - bool is_breakpoint_gutter_enabled() const; - - void set_breakpoint_gutter_width(int p_gutter_width); - int get_breakpoint_gutter_width() const; - - void set_draw_fold_gutter(bool p_draw); - bool is_drawing_fold_gutter() const; - - void set_fold_gutter_width(int p_gutter_width); - int get_fold_gutter_width() const; - - void set_draw_info_gutter(bool p_draw); - bool is_drawing_info_gutter() const; - - void set_info_gutter_width(int p_gutter_width); - int get_info_gutter_width() const; - void set_draw_minimap(bool p_draw); bool is_drawing_minimap() const; @@ -764,6 +760,7 @@ public: ~TextEdit(); }; +VARIANT_ENUM_CAST(TextEdit::GutterType); VARIANT_ENUM_CAST(TextEdit::MenuItems); VARIANT_ENUM_CAST(TextEdit::SearchFlags); |