diff options
Diffstat (limited to 'scene/gui')
50 files changed, 3965 insertions, 2192 deletions
diff --git a/scene/gui/aspect_ratio_container.cpp b/scene/gui/aspect_ratio_container.cpp index c7f6c0e2da..fb6fa9dec9 100644 --- a/scene/gui/aspect_ratio_container.cpp +++ b/scene/gui/aspect_ratio_container.cpp @@ -155,7 +155,7 @@ void AspectRatioContainer::_bind_methods() { ClassDB::bind_method(D_METHOD("get_alignment_vertical"), &AspectRatioContainer::get_alignment_vertical); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "ratio"), "set_ratio", "get_ratio"); - ADD_PROPERTY(PropertyInfo(Variant::INT, "stretch_mode", PROPERTY_HINT_ENUM, "Width controls height,Height controls width,Fit,Cover"), "set_stretch_mode", "get_stretch_mode"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "stretch_mode", PROPERTY_HINT_ENUM, "Width Controls Height,Height Controls Width,Fit,Cover"), "set_stretch_mode", "get_stretch_mode"); ADD_GROUP("Alignment", "alignment_"); ADD_PROPERTY(PropertyInfo(Variant::INT, "alignment_horizontal", PROPERTY_HINT_ENUM, "Begin,Center,End"), "set_alignment_horizontal", "get_alignment_horizontal"); diff --git a/scene/gui/base_button.cpp b/scene/gui/base_button.cpp index db13b9b11f..66155958cf 100644 --- a/scene/gui/base_button.cpp +++ b/scene/gui/base_button.cpp @@ -53,6 +53,8 @@ void BaseButton::_unpress_group() { } void BaseButton::_gui_input(Ref<InputEvent> p_event) { + ERR_FAIL_COND(p_event.is_null()); + if (status.disabled) { // no interaction with disabled button return; } @@ -96,17 +98,14 @@ void BaseButton::_notification(int p_what) { } if (p_what == NOTIFICATION_FOCUS_ENTER) { - status.hovering = true; update(); } if (p_what == NOTIFICATION_FOCUS_EXIT) { if (status.press_attempt) { status.press_attempt = false; - status.hovering = false; update(); } else if (status.hovering) { - status.hovering = false; update(); } } @@ -153,6 +152,9 @@ void BaseButton::on_action_event(Ref<InputEvent> p_event) { } status.pressed = !status.pressed; _unpress_group(); + if (button_group.is_valid()) { + button_group->emit_signal("pressed", this); + } _toggled(status.pressed); _pressed(); } @@ -170,10 +172,9 @@ void BaseButton::on_action_event(Ref<InputEvent> p_event) { status.hovering = false; } } - // pressed state should be correct with button_up signal - emit_signal("button_up"); status.press_attempt = false; status.pressing_inside = false; + emit_signal("button_up"); } update(); @@ -216,6 +217,9 @@ void BaseButton::set_pressed(bool p_pressed) { if (p_pressed) { _unpress_group(); + if (button_group.is_valid()) { + button_group->emit_signal("pressed", this); + } } _toggled(status.pressed); @@ -323,6 +327,8 @@ Ref<Shortcut> BaseButton::get_shortcut() const { } void BaseButton::_unhandled_key_input(Ref<InputEvent> p_event) { + ERR_FAIL_COND(p_event.is_null()); + if (!_is_focus_owner_in_shorcut_context()) { return; } @@ -483,6 +489,7 @@ BaseButton *ButtonGroup::get_pressed_button() { void ButtonGroup::_bind_methods() { ClassDB::bind_method(D_METHOD("get_pressed_button"), &ButtonGroup::get_pressed_button); ClassDB::bind_method(D_METHOD("get_buttons"), &ButtonGroup::_get_buttons); + ADD_SIGNAL(MethodInfo("pressed", PropertyInfo(Variant::OBJECT, "button"))); } ButtonGroup::ButtonGroup() { diff --git a/scene/gui/base_button.h b/scene/gui/base_button.h index d54d63cc39..6c7a8f3433 100644 --- a/scene/gui/base_button.h +++ b/scene/gui/base_button.h @@ -45,7 +45,7 @@ public: }; private: - int button_mask = BUTTON_MASK_LEFT; + int button_mask = MOUSE_BUTTON_MASK_LEFT; bool toggle_mode = false; bool shortcut_in_tooltip = true; bool keep_pressed_outside = false; diff --git a/scene/gui/box_container.cpp b/scene/gui/box_container.cpp index c570438b6a..7407ad5b8f 100644 --- a/scene/gui/box_container.cpp +++ b/scene/gui/box_container.cpp @@ -313,7 +313,7 @@ BoxContainer::AlignMode BoxContainer::get_alignment() const { return align; } -void BoxContainer::add_spacer(bool p_begin) { +Control *BoxContainer::add_spacer(bool p_begin) { Control *c = memnew(Control); c->set_mouse_filter(MOUSE_FILTER_PASS); //allow spacer to pass mouse events @@ -327,6 +327,8 @@ void BoxContainer::add_spacer(bool p_begin) { if (p_begin) { move_child(c, 0); } + + return c; } BoxContainer::BoxContainer(bool p_vertical) { diff --git a/scene/gui/box_container.h b/scene/gui/box_container.h index 31050d1feb..23feea565c 100644 --- a/scene/gui/box_container.h +++ b/scene/gui/box_container.h @@ -55,7 +55,7 @@ protected: static void _bind_methods(); public: - void add_spacer(bool p_begin = false); + Control *add_spacer(bool p_begin = false); void set_alignment(AlignMode p_align); AlignMode get_alignment() const; diff --git a/scene/gui/button.cpp b/scene/gui/button.cpp index b0bcde8865..c0df5271b4 100644 --- a/scene/gui/button.cpp +++ b/scene/gui/button.cpp @@ -533,7 +533,7 @@ void Button::_bind_methods() { BIND_ENUM_CONSTANT(ALIGN_RIGHT); ADD_PROPERTY(PropertyInfo(Variant::STRING, "text", PROPERTY_HINT_MULTILINE_TEXT, "", PROPERTY_USAGE_DEFAULT_INTL), "set_text", "get_text"); - ADD_PROPERTY(PropertyInfo(Variant::INT, "text_direction", PROPERTY_HINT_ENUM, "Auto,LTR,RTL,Inherited"), "set_text_direction", "get_text_direction"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "text_direction", PROPERTY_HINT_ENUM, "Auto,Left-to-Right,Right-to-Left,Inherited"), "set_text_direction", "get_text_direction"); ADD_PROPERTY(PropertyInfo(Variant::STRING, "language"), "set_language", "get_language"); ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "icon", PROPERTY_HINT_RESOURCE_TYPE, "Texture2D"), "set_button_icon", "get_button_icon"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "flat"), "set_flat", "is_flat"); diff --git a/scene/gui/check_box.cpp b/scene/gui/check_box.cpp index 9df328dd11..c0650a8f3f 100644 --- a/scene/gui/check_box.cpp +++ b/scene/gui/check_box.cpp @@ -34,7 +34,9 @@ Size2 CheckBox::get_icon_size() const { Ref<Texture2D> checked = Control::get_theme_icon("checked"); + Ref<Texture2D> checked_disabled = Control::get_theme_icon("checked_disabled"); Ref<Texture2D> unchecked = Control::get_theme_icon("unchecked"); + Ref<Texture2D> unchecked_disabled = Control::get_theme_icon("unchecked_disabled"); Ref<Texture2D> radio_checked = Control::get_theme_icon("radio_checked"); Ref<Texture2D> radio_unchecked = Control::get_theme_icon("radio_unchecked"); @@ -79,8 +81,8 @@ void CheckBox::_notification(int p_what) { } else if (p_what == NOTIFICATION_DRAW) { RID ci = get_canvas_item(); - Ref<Texture2D> on = Control::get_theme_icon(is_radio() ? "radio_checked" : "checked"); - Ref<Texture2D> off = Control::get_theme_icon(is_radio() ? "radio_unchecked" : "unchecked"); + Ref<Texture2D> on = Control::get_theme_icon(vformat("%s%s", is_radio() ? "radio_checked" : "checked", is_disabled() ? "_disabled" : "")); + Ref<Texture2D> off = Control::get_theme_icon(vformat("%s%s", is_radio() ? "radio_unchecked" : "unchecked", is_disabled() ? "_disabled" : "")); Ref<StyleBox> sb = get_theme_stylebox("normal"); Vector2 ofs; diff --git a/scene/gui/code_edit.cpp b/scene/gui/code_edit.cpp index 28a0ea0100..d5000e88d7 100644 --- a/scene/gui/code_edit.cpp +++ b/scene/gui/code_edit.cpp @@ -30,6 +30,18 @@ #include "code_edit.h" +#include "core/os/keyboard.h" +#include "core/string/string_builder.h" +#include "core/string/ustring.h" + +static bool _is_whitespace(char32_t c) { + return c == '\t' || c == ' '; +} + +static bool _is_char(char32_t c) { + return !is_symbol(c); +} + void CodeEdit::_notification(int p_what) { switch (p_what) { case NOTIFICATION_THEME_CHANGED: @@ -52,12 +64,325 @@ void CodeEdit::_notification(int p_what) { folding_color = get_theme_color("code_folding_color"); can_fold_icon = get_theme_icon("can_fold"); folded_icon = get_theme_icon("folded"); + + code_completion_max_width = get_theme_constant("completion_max_width") * cache.font->get_char_size('x').x; + code_completion_max_lines = get_theme_constant("completion_lines"); + code_completion_scroll_width = get_theme_constant("completion_scroll_width"); + code_completion_scroll_color = get_theme_color("completion_scroll_color"); + code_completion_background_color = get_theme_color("completion_background_color"); + code_completion_selected_color = get_theme_color("completion_selected_color"); + code_completion_existing_color = get_theme_color("completion_existing_color"); } break; case NOTIFICATION_DRAW: { + RID ci = get_canvas_item(); + const bool caret_visible = is_caret_visible(); + const bool rtl = is_layout_rtl(); + const int row_height = get_row_height(); + + bool code_completion_below = false; + if (caret_visible && code_completion_active && code_completion_options.size() > 0) { + Ref<StyleBox> csb = get_theme_stylebox("completion"); + + const int code_completion_options_count = code_completion_options.size(); + const int lines = MIN(code_completion_options_count, code_completion_max_lines); + const int icon_hsep = get_theme_constant("hseparation", "ItemList"); + const Size2 icon_area_size(row_height, row_height); + + code_completion_rect.size.width = code_completion_longest_line + icon_hsep + icon_area_size.width + 2; + code_completion_rect.size.height = lines * row_height; + + const Point2 caret_pos = get_caret_draw_pos(); + const int total_height = csb->get_minimum_size().y + code_completion_rect.size.height; + if (caret_pos.y + row_height + total_height > get_size().height) { + code_completion_rect.position.y = (caret_pos.y - total_height - row_height) + cache.line_spacing; + } else { + code_completion_rect.position.y = caret_pos.y + (cache.line_spacing / 2.0f); + code_completion_below = true; + } + + const int scroll_width = code_completion_options_count > code_completion_max_lines ? code_completion_scroll_width : 0; + const int code_completion_base_width = cache.font->get_string_size(code_completion_base).width; + if (caret_pos.x - code_completion_base_width + code_completion_rect.size.width + scroll_width > get_size().width) { + code_completion_rect.position.x = get_size().width - code_completion_rect.size.width - scroll_width; + } else { + code_completion_rect.position.x = caret_pos.x - code_completion_base_width; + } + + draw_style_box(csb, Rect2(code_completion_rect.position - csb->get_offset(), code_completion_rect.size + csb->get_minimum_size() + Size2(scroll_width, 0))); + if (code_completion_background_color.a > 0.01) { + RenderingServer::get_singleton()->canvas_item_add_rect(ci, Rect2(code_completion_rect.position, code_completion_rect.size + Size2(scroll_width, 0)), code_completion_background_color); + } + + code_completion_line_ofs = CLAMP(code_completion_current_selected - lines / 2, 0, code_completion_options_count - lines); + RenderingServer::get_singleton()->canvas_item_add_rect(ci, Rect2(Point2(code_completion_rect.position.x, code_completion_rect.position.y + (code_completion_current_selected - code_completion_line_ofs) * row_height), Size2(code_completion_rect.size.width, row_height)), code_completion_selected_color); + draw_rect(Rect2(code_completion_rect.position + Vector2(icon_area_size.x + icon_hsep, 0), Size2(MIN(code_completion_base_width, code_completion_rect.size.width - (icon_area_size.x + icon_hsep)), code_completion_rect.size.height)), code_completion_existing_color); + + for (int i = 0; i < lines; i++) { + int l = code_completion_line_ofs + i; + ERR_CONTINUE(l < 0 || l >= code_completion_options_count); + + Ref<TextLine> tl; + tl.instance(); + tl->add_string(code_completion_options[l].display, cache.font, cache.font_size); + + int yofs = (row_height - tl->get_size().y) / 2; + Point2 title_pos(code_completion_rect.position.x, code_completion_rect.position.y + i * row_height + yofs); + + /* Draw completion icon if it is valid. */ + const Ref<Texture2D> &icon = code_completion_options[l].icon; + Rect2 icon_area(code_completion_rect.position.x, code_completion_rect.position.y + i * row_height, icon_area_size.width, icon_area_size.height); + if (icon.is_valid()) { + Size2 icon_size = icon_area.size * 0.7; + icon->draw_rect(ci, Rect2(icon_area.position + (icon_area.size - icon_size) / 2, icon_size)); + } + title_pos.x = icon_area.position.x + icon_area.size.width + icon_hsep; + + tl->set_width(code_completion_rect.size.width - (icon_area_size.x + icon_hsep)); + if (rtl) { + if (code_completion_options[l].default_value.get_type() == Variant::COLOR) { + draw_rect(Rect2(Point2(code_completion_rect.position.x, icon_area.position.y), icon_area_size), (Color)code_completion_options[l].default_value); + } + tl->set_align(HALIGN_RIGHT); + } else { + if (code_completion_options[l].default_value.get_type() == Variant::COLOR) { + draw_rect(Rect2(Point2(code_completion_rect.position.x + code_completion_rect.size.width - icon_area_size.x, icon_area.position.y), icon_area_size), (Color)code_completion_options[l].default_value); + } + tl->set_align(HALIGN_LEFT); + } + tl->draw(ci, title_pos, code_completion_options[l].font_color); + } + + /* Draw a small scroll rectangle to show a position in the options. */ + if (scroll_width) { + float r = (float)code_completion_max_lines / code_completion_options_count; + float o = (float)code_completion_line_ofs / code_completion_options_count; + draw_rect(Rect2(code_completion_rect.position.x + code_completion_rect.size.width, code_completion_rect.position.y + o * code_completion_rect.size.y, scroll_width, code_completion_rect.size.y * r), code_completion_scroll_color); + } + } + + /* Code hint */ + if (caret_visible && code_hint != "" && (!code_completion_active || (code_completion_below != code_hint_draw_below))) { + const Ref<Font> font = cache.font; + const int font_height = font->get_height(cache.font_size); + Ref<StyleBox> sb = get_theme_stylebox("panel", "TooltipPanel"); + Color font_color = get_theme_color("font_color", "TooltipLabel"); + + Vector<String> code_hint_lines = code_hint.split("\n"); + int line_count = code_hint_lines.size(); + + int max_width = 0; + for (int i = 0; i < line_count; i++) { + max_width = MAX(max_width, font->get_string_size(code_hint_lines[i], cache.font_size).x); + } + Size2 minsize = sb->get_minimum_size() + Size2(max_width, line_count * font_height + (cache.line_spacing * line_count - 1)); + + int offset = font->get_string_size(code_hint_lines[0].substr(0, code_hint_lines[0].find(String::chr(0xFFFF))), cache.font_size).x; + if (code_hint_xpos == -0xFFFF) { + code_hint_xpos = get_caret_draw_pos().x - offset; + } + Point2 hint_ofs = Vector2(code_hint_xpos, get_caret_draw_pos().y); + if (code_hint_draw_below) { + hint_ofs.y += cache.line_spacing / 2.0f; + } else { + hint_ofs.y -= (minsize.y + row_height) - cache.line_spacing; + } + + draw_style_box(sb, Rect2(hint_ofs, minsize)); + + int line_spacing = 0; + for (int i = 0; i < line_count; i++) { + const String &line = code_hint_lines[i]; + + int begin = 0; + int end = 0; + if (line.find(String::chr(0xFFFF)) != -1) { + begin = font->get_string_size(line.substr(0, line.find(String::chr(0xFFFF))), cache.font_size).x; + end = font->get_string_size(line.substr(0, line.rfind(String::chr(0xFFFF))), cache.font_size).x; + } + + Point2 round_ofs = hint_ofs + sb->get_offset() + Vector2(0, font->get_ascent() + font_height * i + line_spacing); + round_ofs = round_ofs.round(); + draw_string(font, round_ofs, line.replace(String::chr(0xFFFF), ""), HALIGN_LEFT, -1, cache.font_size, font_color); + if (end > 0) { + Vector2 b = hint_ofs + sb->get_offset() + Vector2(begin, font_height + font_height * i + line_spacing - 1); + draw_line(b, b + Vector2(end - begin, 0), font_color); + } + line_spacing += cache.line_spacing; + } + } } break; } } +void CodeEdit::_gui_input(const Ref<InputEvent> &p_gui_input) { + Ref<InputEventMouseButton> mb = p_gui_input; + + if (mb.is_valid()) { + if (code_completion_active && code_completion_rect.has_point(mb->get_position())) { + if (!mb->is_pressed()) { + return; + } + + switch (mb->get_button_index()) { + case MOUSE_BUTTON_WHEEL_UP: { + if (code_completion_current_selected > 0) { + code_completion_current_selected--; + update(); + } + } break; + case MOUSE_BUTTON_WHEEL_DOWN: { + if (code_completion_current_selected < code_completion_options.size() - 1) { + code_completion_current_selected++; + update(); + } + } break; + case MOUSE_BUTTON_LEFT: { + code_completion_current_selected = CLAMP(code_completion_line_ofs + (mb->get_position().y - code_completion_rect.position.y) / get_row_height(), 0, code_completion_options.size() - 1); + if (mb->is_double_click()) { + confirm_code_completion(); + } + update(); + } break; + } + return; + } + cancel_code_completion(); + set_code_hint(""); + } + + Ref<InputEventKey> k = p_gui_input; + bool update_code_completion = false; + if (!k.is_valid()) { + TextEdit::_gui_input(p_gui_input); + return; + } + + /* If a modifier has been pressed, and nothing else, return. */ + if (!k->is_pressed() || k->get_keycode() == KEY_CTRL || k->get_keycode() == KEY_ALT || k->get_keycode() == KEY_SHIFT || k->get_keycode() == KEY_META) { + return; + } + + /* Allow unicode handling if: */ + /* No Modifiers are pressed (except shift) */ + bool allow_unicode_handling = !(k->is_command_pressed() || k->is_ctrl_pressed() || k->is_alt_pressed() || k->is_meta_pressed()); + + /* AUTO-COMPLETE */ + if (code_completion_enabled && k->is_action("ui_text_completion_query", true)) { + request_code_completion(true); + accept_event(); + return; + } + + if (code_completion_active) { + if (k->is_action("ui_up", true)) { + if (code_completion_current_selected > 0) { + code_completion_current_selected--; + } else { + code_completion_current_selected = code_completion_options.size() - 1; + } + update(); + accept_event(); + return; + } + if (k->is_action("ui_down", true)) { + if (code_completion_current_selected < code_completion_options.size() - 1) { + code_completion_current_selected++; + } else { + code_completion_current_selected = 0; + } + update(); + accept_event(); + return; + } + if (k->is_action("ui_page_up", true)) { + code_completion_current_selected = MAX(0, code_completion_current_selected - code_completion_max_lines); + update(); + accept_event(); + return; + } + if (k->is_action("ui_page_down", true)) { + code_completion_current_selected = MIN(code_completion_options.size() - 1, code_completion_current_selected + code_completion_max_lines); + update(); + accept_event(); + return; + } + if (k->is_action("ui_home", true)) { + code_completion_current_selected = 0; + update(); + accept_event(); + return; + } + if (k->is_action("ui_end", true)) { + code_completion_current_selected = MIN(code_completion_options.size() - 1, code_completion_current_selected + code_completion_max_lines); + update(); + accept_event(); + return; + } + if (k->is_action("ui_text_completion_replace", true) || k->is_action("ui_text_completion_accept", true)) { + confirm_code_completion(k->is_action("ui_text_completion_replace", true)); + accept_event(); + return; + } + if (k->is_action("ui_cancel", true)) { + cancel_code_completion(); + accept_event(); + return; + } + if (k->is_action("ui_text_backspace", true)) { + backspace_at_cursor(); + _filter_code_completion_candidates(); + accept_event(); + return; + } + + if (k->is_action("ui_left", true) || k->is_action("ui_right", true)) { + update_code_completion = true; + } else { + update_code_completion = (allow_unicode_handling && k->get_unicode() >= 32); + } + + if (!update_code_completion) { + cancel_code_completion(); + } + } + + /* MISC */ + if (k->is_action("ui_cancel", true)) { + set_code_hint(""); + accept_event(); + return; + } + if (allow_unicode_handling && k->get_unicode() == ')') { + set_code_hint(""); + } + + /* Remove shift otherwise actions will not match. */ + k = k->duplicate(); + k->set_shift_pressed(false); + + if (k->is_action("ui_text_caret_up", true) || + k->is_action("ui_text_caret_down", true) || + k->is_action("ui_text_caret_line_start", true) || + k->is_action("ui_text_caret_line_end", true) || + k->is_action("ui_text_caret_page_up", true) || + k->is_action("ui_text_caret_page_down", true)) { + set_code_hint(""); + } + + TextEdit::_gui_input(p_gui_input); + + if (update_code_completion) { + _filter_code_completion_candidates(); + } +} + +Control::CursorShape CodeEdit::get_cursor_shape(const Point2 &p_pos) const { + if ((code_completion_active && code_completion_rect.has_point(p_pos)) || (is_readonly() && (!is_selecting_enabled() || get_line_count() == 0))) { + return CURSOR_ARROW; + } + return TextEdit::get_cursor_shape(p_pos); +} + /* Main Gutter */ void CodeEdit::_update_draw_main_gutter() { set_gutter_draw(main_gutter, draw_breakpoints || draw_bookmarks || draw_executing_lines); @@ -275,6 +600,455 @@ void CodeEdit::_fold_gutter_draw_callback(int p_line, int p_gutter, Rect2 p_regi folded_icon->draw_rect(get_canvas_item(), p_region, false, folding_color); } +/* Delimiters */ +// Strings +void CodeEdit::add_string_delimiter(const String &p_start_key, const String &p_end_key, bool p_line_only) { + _add_delimiter(p_start_key, p_end_key, p_line_only, TYPE_STRING); +} + +void CodeEdit::remove_string_delimiter(const String &p_start_key) { + _remove_delimiter(p_start_key, TYPE_STRING); +} + +bool CodeEdit::has_string_delimiter(const String &p_start_key) const { + return _has_delimiter(p_start_key, TYPE_STRING); +} + +void CodeEdit::set_string_delimiters(const TypedArray<String> &p_string_delimiters) { + _set_delimiters(p_string_delimiters, TYPE_STRING); +} + +void CodeEdit::clear_string_delimiters() { + _clear_delimiters(TYPE_STRING); +} + +TypedArray<String> CodeEdit::get_string_delimiters() const { + return _get_delimiters(TYPE_STRING); +} + +int CodeEdit::is_in_string(int p_line, int p_column) const { + return _is_in_delimiter(p_line, p_column, TYPE_STRING); +} + +// Comments +void CodeEdit::add_comment_delimiter(const String &p_start_key, const String &p_end_key, bool p_line_only) { + _add_delimiter(p_start_key, p_end_key, p_line_only, TYPE_COMMENT); +} + +void CodeEdit::remove_comment_delimiter(const String &p_start_key) { + _remove_delimiter(p_start_key, TYPE_COMMENT); +} + +bool CodeEdit::has_comment_delimiter(const String &p_start_key) const { + return _has_delimiter(p_start_key, TYPE_COMMENT); +} + +void CodeEdit::set_comment_delimiters(const TypedArray<String> &p_comment_delimiters) { + _set_delimiters(p_comment_delimiters, TYPE_COMMENT); +} + +void CodeEdit::clear_comment_delimiters() { + _clear_delimiters(TYPE_COMMENT); +} + +TypedArray<String> CodeEdit::get_comment_delimiters() const { + return _get_delimiters(TYPE_COMMENT); +} + +int CodeEdit::is_in_comment(int p_line, int p_column) const { + return _is_in_delimiter(p_line, p_column, TYPE_COMMENT); +} + +String CodeEdit::get_delimiter_start_key(int p_delimiter_idx) const { + ERR_FAIL_INDEX_V(p_delimiter_idx, delimiters.size(), ""); + return delimiters[p_delimiter_idx].start_key; +} + +String CodeEdit::get_delimiter_end_key(int p_delimiter_idx) const { + ERR_FAIL_INDEX_V(p_delimiter_idx, delimiters.size(), ""); + return delimiters[p_delimiter_idx].end_key; +} + +Point2 CodeEdit::get_delimiter_start_position(int p_line, int p_column) const { + if (delimiters.size() == 0) { + return Point2(-1, -1); + } + ERR_FAIL_INDEX_V(p_line, get_line_count(), Point2(-1, -1)); + ERR_FAIL_COND_V(p_column - 1 > get_line(p_line).size(), Point2(-1, -1)); + + Point2 start_position; + start_position.y = -1; + start_position.x = -1; + + bool in_region = ((p_line <= 0 || delimiter_cache[p_line - 1].size() < 1) ? -1 : delimiter_cache[p_line - 1].back()->value()) != -1; + + /* Check the keys for this line. */ + for (Map<int, int>::Element *E = delimiter_cache[p_line].front(); E; E = E->next()) { + if (E->key() > p_column) { + break; + } + in_region = E->value() != -1; + start_position.x = in_region ? E->key() : -1; + } + + /* Region was found on this line and is not a multiline continuation. */ + if (start_position.x != -1 && start_position.x != get_line(p_line).length() + 1) { + start_position.y = p_line; + return start_position; + } + + /* Not in a region */ + if (!in_region) { + return start_position; + } + + /* Region starts on a previous line */ + for (int i = p_line - 1; i >= 0; i--) { + if (delimiter_cache[i].size() < 1) { + continue; + } + start_position.y = i; + start_position.x = delimiter_cache[i].back()->key(); + + /* Make sure it's not a multiline continuation. */ + if (start_position.x != get_line(i).length() + 1) { + break; + } + } + return start_position; +} + +Point2 CodeEdit::get_delimiter_end_position(int p_line, int p_column) const { + if (delimiters.size() == 0) { + return Point2(-1, -1); + } + ERR_FAIL_INDEX_V(p_line, get_line_count(), Point2(-1, -1)); + ERR_FAIL_COND_V(p_column - 1 > get_line(p_line).size(), Point2(-1, -1)); + + Point2 end_position; + end_position.y = -1; + end_position.x = -1; + + int region = (p_line <= 0 || delimiter_cache[p_line - 1].size() < 1) ? -1 : delimiter_cache[p_line - 1].back()->value(); + + /* Check the keys for this line. */ + for (Map<int, int>::Element *E = delimiter_cache[p_line].front(); E; E = E->next()) { + end_position.x = (E->value() == -1) ? E->key() : -1; + if (E->key() > p_column) { + break; + } + region = E->value(); + } + + /* Region was found on this line and is not a multiline continuation. */ + if (region != -1 && end_position.x != -1 && (delimiters[region].line_only || end_position.x != get_line(p_line).length() + 1)) { + end_position.y = p_line; + return end_position; + } + + /* Not in a region */ + if (region == -1) { + end_position.x = -1; + return end_position; + } + + /* Region ends on a later line */ + for (int i = p_line + 1; i < get_line_count(); i++) { + if (delimiter_cache[i].size() < 1 || delimiter_cache[i].front()->value() != -1) { + continue; + } + end_position.x = delimiter_cache[i].front()->key(); + + /* Make sure it's not a multiline continuation. */ + if (get_line(i).length() > 0 && end_position.x != get_line(i).length() + 1) { + end_position.y = i; + break; + } + end_position.x = -1; + } + return end_position; +} + +/* Code hint */ +void CodeEdit::set_code_hint(const String &p_hint) { + code_hint = p_hint; + code_hint_xpos = -0xFFFF; + update(); +} + +void CodeEdit::set_code_hint_draw_below(bool p_below) { + code_hint_draw_below = p_below; + update(); +} + +/* Code Completion */ +void CodeEdit::set_code_completion_enabled(bool p_enable) { + code_completion_enabled = p_enable; +} + +bool CodeEdit::is_code_completion_enabled() const { + return code_completion_enabled; +} + +void CodeEdit::set_code_completion_prefixes(const TypedArray<String> &p_prefixes) { + code_completion_prefixes.clear(); + for (int i = 0; i < p_prefixes.size(); i++) { + code_completion_prefixes.insert(p_prefixes[i]); + } +} + +TypedArray<String> CodeEdit::get_code_completion_prefixes() const { + TypedArray<String> prefixes; + for (Set<String>::Element *E = code_completion_prefixes.front(); E; E = E->next()) { + prefixes.push_back(E->get()); + } + return prefixes; +} + +String CodeEdit::get_text_for_code_completion() const { + StringBuilder completion_text; + const int text_size = get_line_count(); + for (int i = 0; i < text_size; i++) { + String line = get_line(i); + + if (i == cursor_get_line()) { + completion_text += line.substr(0, cursor_get_column()); + /* Not unicode, represents the caret. */ + completion_text += String::chr(0xFFFF); + completion_text += line.substr(cursor_get_column(), line.size()); + } else { + completion_text += line; + } + + if (i != text_size - 1) { + completion_text += "\n"; + } + } + return completion_text.as_string(); +} + +void CodeEdit::request_code_completion(bool p_force) { + ScriptInstance *si = get_script_instance(); + if (si && si->has_method("_request_code_completion")) { + si->call("_request_code_completion", p_force); + return; + } + + /* Don't re-query if all existing options are quoted types, eg path, signal. */ + bool ignored = code_completion_active && !code_completion_options.is_empty(); + if (ignored) { + ScriptCodeCompletionOption::Kind kind = ScriptCodeCompletionOption::KIND_PLAIN_TEXT; + const ScriptCodeCompletionOption *previous_option = nullptr; + for (int i = 0; i < code_completion_options.size(); i++) { + const ScriptCodeCompletionOption ¤t_option = code_completion_options[i]; + if (!previous_option) { + previous_option = ¤t_option; + kind = current_option.kind; + } + if (previous_option->kind != current_option.kind) { + ignored = false; + break; + } + } + ignored = ignored && (kind == ScriptCodeCompletionOption::KIND_FILE_PATH || kind == ScriptCodeCompletionOption::KIND_NODE_PATH || kind == ScriptCodeCompletionOption::KIND_SIGNAL); + } + + if (ignored) { + return; + } + + if (p_force) { + emit_signal("request_code_completion"); + return; + } + + String line = get_line(cursor_get_line()); + int ofs = CLAMP(cursor_get_column(), 0, line.length()); + + if (ofs > 0 && (is_in_string(cursor_get_line(), ofs) != -1 || _is_char(line[ofs - 1]) || code_completion_prefixes.has(String::chr(line[ofs - 1])))) { + emit_signal("request_code_completion"); + } else if (ofs > 1 && line[ofs - 1] == ' ' && code_completion_prefixes.has(String::chr(line[ofs - 2]))) { + emit_signal("request_code_completion"); + } +} + +void CodeEdit::add_code_completion_option(CodeCompletionKind p_type, const String &p_display_text, const String &p_insert_text, const Color &p_text_color, const RES &p_icon, const Variant &p_value) { + ScriptCodeCompletionOption completion_option; + completion_option.kind = (ScriptCodeCompletionOption::Kind)p_type; + completion_option.display = p_display_text; + completion_option.insert_text = p_insert_text; + completion_option.font_color = p_text_color; + completion_option.icon = p_icon; + completion_option.default_value = p_value; + code_completion_option_submitted.push_back(completion_option); +} + +void CodeEdit::update_code_completion_options(bool p_forced) { + code_completion_forced = p_forced; + code_completion_option_sources = code_completion_option_submitted; + code_completion_option_submitted.clear(); + _filter_code_completion_candidates(); +} + +TypedArray<Dictionary> CodeEdit::get_code_completion_options() const { + if (!code_completion_active) { + return TypedArray<Dictionary>(); + } + + TypedArray<Dictionary> completion_options; + completion_options.resize(code_completion_options.size()); + for (int i = 0; i < code_completion_options.size(); i++) { + Dictionary option; + option["kind"] = code_completion_options[i].kind; + option["display_text"] = code_completion_options[i].display; + option["insert_text"] = code_completion_options[i].insert_text; + option["font_color"] = code_completion_options[i].font_color; + option["icon"] = code_completion_options[i].icon; + option["default_value"] = code_completion_options[i].default_value; + completion_options[i] = option; + } + return completion_options; +} + +Dictionary CodeEdit::get_code_completion_option(int p_index) const { + if (!code_completion_active) { + return Dictionary(); + } + ERR_FAIL_INDEX_V(p_index, code_completion_options.size(), Dictionary()); + + Dictionary option; + option["kind"] = code_completion_options[p_index].kind; + option["display_text"] = code_completion_options[p_index].display; + option["insert_text"] = code_completion_options[p_index].insert_text; + option["font_color"] = code_completion_options[p_index].font_color; + option["icon"] = code_completion_options[p_index].icon; + option["default_value"] = code_completion_options[p_index].default_value; + return option; +} + +int CodeEdit::get_code_completion_selected_index() const { + return (code_completion_active) ? code_completion_current_selected : -1; +} + +void CodeEdit::set_code_completion_selected_index(int p_index) { + if (!code_completion_active) { + return; + } + ERR_FAIL_INDEX(p_index, code_completion_options.size()); + code_completion_current_selected = p_index; + update(); +} + +void CodeEdit::confirm_code_completion(bool p_replace) { + if (is_readonly() || !code_completion_active) { + return; + } + + ScriptInstance *si = get_script_instance(); + if (si && si->has_method("_confirm_code_completion")) { + si->call("_confirm_code_completion", p_replace); + return; + } + begin_complex_operation(); + + int caret_line = cursor_get_line(); + + const String &insert_text = code_completion_options[code_completion_current_selected].insert_text; + const String &display_text = code_completion_options[code_completion_current_selected].display; + + if (p_replace) { + /* Find end of current section */ + const String line = get_line(caret_line); + int caret_col = cursor_get_column(); + int caret_remove_line = caret_line; + + bool merge_text = true; + int in_string = is_in_string(caret_line, caret_col); + if (in_string != -1) { + Point2 string_end = get_delimiter_end_position(caret_line, caret_col); + if (string_end.x != -1) { + merge_text = false; + caret_remove_line = string_end.y; + caret_col = string_end.x - 1; + } + } + + if (merge_text) { + for (; caret_col < line.length(); caret_col++) { + if (!_is_char(line[caret_col])) { + break; + } + } + } + + /* Replace. */ + _remove_text(caret_line, cursor_get_column() - code_completion_base.length(), caret_remove_line, caret_col); + cursor_set_column(cursor_get_column() - code_completion_base.length(), false); + insert_text_at_cursor(insert_text); + } else { + /* Get first non-matching char. */ + const String line = get_line(caret_line); + int caret_col = cursor_get_column(); + int matching_chars = code_completion_base.length(); + for (; matching_chars <= insert_text.length(); matching_chars++) { + if (caret_col >= line.length() || line[caret_col] != insert_text[matching_chars]) { + break; + } + caret_col++; + } + + /* Remove base completion text. */ + _remove_text(caret_line, cursor_get_column() - code_completion_base.length(), caret_line, cursor_get_column()); + cursor_set_column(cursor_get_column() - code_completion_base.length(), false); + + /* Merge with text. */ + insert_text_at_cursor(insert_text.substr(0, code_completion_base.length())); + cursor_set_column(caret_col, false); + insert_text_at_cursor(insert_text.substr(matching_chars)); + } + + /* TODO: merge with autobrace completion, when in CodeEdit. */ + /* Handle merging of symbols eg strings, brackets. */ + const String line = get_line(caret_line); + char32_t next_char = line[cursor_get_column()]; + char32_t last_completion_char = insert_text[insert_text.length() - 1]; + char32_t last_completion_char_display = display_text[display_text.length() - 1]; + + if ((last_completion_char == '"' || last_completion_char == '\'') && (last_completion_char == next_char || last_completion_char_display == next_char)) { + _remove_text(caret_line, cursor_get_column(), caret_line, cursor_get_column() + 1); + } + + if (last_completion_char == '(') { + if (next_char == last_completion_char) { + _remove_text(caret_line, cursor_get_column() - 1, caret_line, cursor_get_column()); + } else if (auto_brace_completion_enabled) { + insert_text_at_cursor(")"); + cursor_set_column(cursor_get_column() - 1); + } + } else if (last_completion_char == ')' && next_char == '(') { + _remove_text(caret_line, cursor_get_column() - 2, caret_line, cursor_get_column()); + if (line[cursor_get_column() + 1] != ')') { + cursor_set_column(cursor_get_column() - 1); + } + } + + end_complex_operation(); + + cancel_code_completion(); + if (last_completion_char == '(') { + request_code_completion(); + } +} + +void CodeEdit::cancel_code_completion() { + if (!code_completion_active) { + return; + } + code_completion_forced = false; + code_completion_active = false; + update(); +} + void CodeEdit::_bind_methods() { /* Main Gutter */ ClassDB::bind_method(D_METHOD("_main_gutter_draw_callback"), &CodeEdit::_main_gutter_draw_callback); @@ -320,6 +1094,76 @@ void CodeEdit::_bind_methods() { ClassDB::bind_method(D_METHOD("set_draw_fold_gutter", "enable"), &CodeEdit::set_draw_fold_gutter); ClassDB::bind_method(D_METHOD("is_drawing_fold_gutter"), &CodeEdit::is_drawing_fold_gutter); + /* Delimiters */ + // Strings + ClassDB::bind_method(D_METHOD("add_string_delimiter", "start_key", "end_key", "line_only"), &CodeEdit::add_string_delimiter, DEFVAL(false)); + ClassDB::bind_method(D_METHOD("remove_string_delimiter", "start_key"), &CodeEdit::remove_string_delimiter); + ClassDB::bind_method(D_METHOD("has_string_delimiter", "start_key"), &CodeEdit::has_string_delimiter); + + ClassDB::bind_method(D_METHOD("set_string_delimiters", "string_delimiters"), &CodeEdit::set_string_delimiters); + ClassDB::bind_method(D_METHOD("clear_string_delimiters"), &CodeEdit::clear_string_delimiters); + ClassDB::bind_method(D_METHOD("get_string_delimiters"), &CodeEdit::get_string_delimiters); + + ClassDB::bind_method(D_METHOD("is_in_string", "line", "column"), &CodeEdit::is_in_string, DEFVAL(-1)); + + // Comments + ClassDB::bind_method(D_METHOD("add_comment_delimiter", "start_key", "end_key", "line_only"), &CodeEdit::add_comment_delimiter, DEFVAL(false)); + ClassDB::bind_method(D_METHOD("remove_comment_delimiter", "start_key"), &CodeEdit::remove_comment_delimiter); + ClassDB::bind_method(D_METHOD("has_comment_delimiter", "start_key"), &CodeEdit::has_comment_delimiter); + + ClassDB::bind_method(D_METHOD("set_comment_delimiters", "comment_delimiters"), &CodeEdit::set_comment_delimiters); + ClassDB::bind_method(D_METHOD("clear_comment_delimiters"), &CodeEdit::clear_comment_delimiters); + ClassDB::bind_method(D_METHOD("get_comment_delimiters"), &CodeEdit::get_comment_delimiters); + + ClassDB::bind_method(D_METHOD("is_in_comment", "line", "column"), &CodeEdit::is_in_comment, DEFVAL(-1)); + + // Util + ClassDB::bind_method(D_METHOD("get_delimiter_start_key", "delimiter_index"), &CodeEdit::get_delimiter_start_key); + ClassDB::bind_method(D_METHOD("get_delimiter_end_key", "delimiter_index"), &CodeEdit::get_delimiter_end_key); + + ClassDB::bind_method(D_METHOD("get_delimiter_start_postion", "line", "column"), &CodeEdit::get_delimiter_start_position); + ClassDB::bind_method(D_METHOD("get_delimiter_end_postion", "line", "column"), &CodeEdit::get_delimiter_end_position); + + /* Code hint */ + ClassDB::bind_method(D_METHOD("set_code_hint", "code_hint"), &CodeEdit::set_code_hint); + ClassDB::bind_method(D_METHOD("set_code_hint_draw_below", "draw_below"), &CodeEdit::set_code_hint_draw_below); + + /* Code Completion */ + BIND_ENUM_CONSTANT(KIND_CLASS); + BIND_ENUM_CONSTANT(KIND_FUNCTION); + BIND_ENUM_CONSTANT(KIND_SIGNAL); + BIND_ENUM_CONSTANT(KIND_VARIABLE); + BIND_ENUM_CONSTANT(KIND_MEMBER); + BIND_ENUM_CONSTANT(KIND_ENUM); + BIND_ENUM_CONSTANT(KIND_CONSTANT); + BIND_ENUM_CONSTANT(KIND_NODE_PATH); + BIND_ENUM_CONSTANT(KIND_FILE_PATH); + BIND_ENUM_CONSTANT(KIND_PLAIN_TEXT); + + ClassDB::bind_method(D_METHOD("get_text_for_code_completion"), &CodeEdit::get_text_for_code_completion); + ClassDB::bind_method(D_METHOD("request_code_completion", "force"), &CodeEdit::request_code_completion, DEFVAL(false)); + ClassDB::bind_method(D_METHOD("add_code_completion_option", "type", "display_text", "insert_text", "text_color", "icon", "value"), &CodeEdit::add_code_completion_option, DEFVAL(Color(1, 1, 1)), DEFVAL(RES()), DEFVAL(Variant::NIL)); + ClassDB::bind_method(D_METHOD("update_code_completion_options", "force"), &CodeEdit::update_code_completion_options); + ClassDB::bind_method(D_METHOD("get_code_completion_options"), &CodeEdit::get_code_completion_options); + ClassDB::bind_method(D_METHOD("get_code_completion_option", "index"), &CodeEdit::get_code_completion_option); + ClassDB::bind_method(D_METHOD("get_code_completion_selected_index"), &CodeEdit::get_code_completion_selected_index); + ClassDB::bind_method(D_METHOD("set_code_completion_selected_index", "index"), &CodeEdit::set_code_completion_selected_index); + + ClassDB::bind_method(D_METHOD("confirm_code_completion", "replace"), &CodeEdit::confirm_code_completion, DEFVAL(false)); + ClassDB::bind_method(D_METHOD("cancel_code_completion"), &CodeEdit::cancel_code_completion); + + ClassDB::bind_method(D_METHOD("set_code_completion_enabled", "enable"), &CodeEdit::set_code_completion_enabled); + ClassDB::bind_method(D_METHOD("is_code_completion_enabled"), &CodeEdit::is_code_completion_enabled); + + ClassDB::bind_method(D_METHOD("set_code_completion_prefixes", "prefixes"), &CodeEdit::set_code_completion_prefixes); + ClassDB::bind_method(D_METHOD("get_code_comletion_prefixes"), &CodeEdit::get_code_completion_prefixes); + + // Overridable + BIND_VMETHOD(MethodInfo("_confirm_code_completion", PropertyInfo(Variant::BOOL, "replace"))); + BIND_VMETHOD(MethodInfo("_request_code_completion", PropertyInfo(Variant::BOOL, "force"))); + BIND_VMETHOD(MethodInfo(Variant::ARRAY, "_filter_code_completion_candidates", PropertyInfo(Variant::ARRAY, "candidates"))); + + /* Inspector */ ADD_PROPERTY(PropertyInfo(Variant::BOOL, "draw_breakpoints_gutter"), "set_draw_breakpoints_gutter", "is_drawing_breakpoints_gutter"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "draw_bookmarks"), "set_draw_bookmarks_gutter", "is_drawing_bookmarks_gutter"); @@ -331,7 +1175,17 @@ void CodeEdit::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::BOOL, "draw_fold_gutter"), "set_draw_fold_gutter", "is_drawing_fold_gutter"); + ADD_GROUP("Delimiters", "delimiter_"); + ADD_PROPERTY(PropertyInfo(Variant::PACKED_STRING_ARRAY, "delimiter_strings"), "set_string_delimiters", "get_string_delimiters"); + ADD_PROPERTY(PropertyInfo(Variant::PACKED_STRING_ARRAY, "delimiter_comments"), "set_comment_delimiters", "get_comment_delimiters"); + + ADD_GROUP("Code Completion", "code_completion_"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "code_completion_enabled"), "set_code_completion_enabled", "is_code_completion_enabled"); + ADD_PROPERTY(PropertyInfo(Variant::PACKED_STRING_ARRAY, "code_completion_prefixes"), "set_code_completion_prefixes", "get_code_comletion_prefixes"); + + /* Signals */ ADD_SIGNAL(MethodInfo("breakpoint_toggled", PropertyInfo(Variant::INT, "line"))); + ADD_SIGNAL(MethodInfo("request_code_completion")); } void CodeEdit::_gutter_clicked(int p_line, int p_gutter) { @@ -360,7 +1214,566 @@ void CodeEdit::_gutter_clicked(int p_line, int p_gutter) { } } +void CodeEdit::_update_gutter_indexes() { + for (int i = 0; i < get_gutter_count(); i++) { + if (get_gutter_name(i) == "main_gutter") { + main_gutter = i; + continue; + } + + if (get_gutter_name(i) == "line_numbers") { + line_number_gutter = i; + continue; + } + + if (get_gutter_name(i) == "fold_gutter") { + fold_gutter = i; + continue; + } + } +} + +/* Delimiters */ +void CodeEdit::_update_delimiter_cache(int p_from_line, int p_to_line) { + if (delimiters.size() == 0) { + return; + } + + int line_count = get_line_count(); + if (p_to_line == -1) { + p_to_line = line_count; + } + + int start_line = MIN(p_from_line, p_to_line); + int end_line = MAX(p_from_line, p_to_line); + + /* Make sure delimiter_cache has all the lines. */ + if (start_line != end_line) { + if (p_to_line < p_from_line) { + for (int i = end_line; i > start_line; i--) { + delimiter_cache.remove(i); + } + } else { + for (int i = start_line; i < end_line; i++) { + delimiter_cache.insert(i, Map<int, int>()); + } + } + } + + int in_region = -1; + for (int i = start_line; i < MIN(end_line + 1, line_count); i++) { + int current_end_region = (i <= 0 || delimiter_cache[i].size() < 1) ? -1 : delimiter_cache[i].back()->value(); + in_region = (i <= 0 || delimiter_cache[i - 1].size() < 1) ? -1 : delimiter_cache[i - 1].back()->value(); + + const String &str = get_line(i); + const int line_length = str.length(); + delimiter_cache.write[i].clear(); + + if (str.length() == 0) { + if (in_region != -1) { + delimiter_cache.write[i][0] = in_region; + } + if (i == end_line && current_end_region != in_region) { + end_line++; + end_line = MIN(end_line, line_count); + } + continue; + } + + int end_region = -1; + for (int j = 0; j < line_length; j++) { + int from = j; + for (; from < line_length; from++) { + if (str[from] == '\\') { + from++; + continue; + } + break; + } + + /* check if we are in entering a region */ + bool same_line = false; + if (in_region == -1) { + for (int d = 0; d < delimiters.size(); d++) { + /* check there is enough room */ + int chars_left = line_length - from; + int start_key_length = delimiters[d].start_key.length(); + int end_key_length = delimiters[d].end_key.length(); + if (chars_left < start_key_length) { + continue; + } + + /* search the line */ + bool match = true; + const char32_t *start_key = delimiters[d].start_key.get_data(); + for (int k = 0; k < start_key_length; k++) { + if (start_key[k] != str[from + k]) { + match = false; + break; + } + } + if (!match) { + continue; + } + same_line = true; + in_region = d; + delimiter_cache.write[i][from + 1] = d; + from += start_key_length; + + /* check if it's the whole line */ + if (end_key_length == 0 || delimiters[d].line_only || from + end_key_length > line_length) { + j = line_length; + if (delimiters[d].line_only) { + delimiter_cache.write[i][line_length + 1] = -1; + } else { + end_region = in_region; + } + } + break; + } + + if (j == line_length || in_region == -1) { + continue; + } + } + + /* if we are in one find the end key */ + /* search the line */ + int region_end_index = -1; + int end_key_length = delimiters[in_region].end_key.length(); + const char32_t *end_key = delimiters[in_region].end_key.get_data(); + for (; from < line_length; from++) { + if (line_length - from < end_key_length) { + break; + } + + if (!is_symbol(str[from])) { + continue; + } + + if (str[from] == '\\') { + from++; + continue; + } + + region_end_index = from; + for (int k = 0; k < end_key_length; k++) { + if (end_key[k] != str[from + k]) { + region_end_index = -1; + break; + } + } + + if (region_end_index != -1) { + break; + } + } + + j = from + (end_key_length - 1); + end_region = (region_end_index == -1) ? in_region : -1; + if (!same_line || region_end_index != -1) { + delimiter_cache.write[i][j + 1] = end_region; + } + in_region = -1; + } + + if (i == end_line && current_end_region != end_region) { + end_line++; + end_line = MIN(end_line, line_count); + } + } +} + +int CodeEdit::_is_in_delimiter(int p_line, int p_column, DelimiterType p_type) const { + if (delimiters.size() == 0) { + return -1; + } + ERR_FAIL_INDEX_V(p_line, get_line_count(), 0); + + int region = (p_line <= 0 || delimiter_cache[p_line - 1].size() < 1) ? -1 : delimiter_cache[p_line - 1].back()->value(); + bool in_region = region != -1 && delimiters[region].type == p_type; + for (Map<int, int>::Element *E = delimiter_cache[p_line].front(); E; E = E->next()) { + /* If column is specified, loop untill the key is larger then the column. */ + if (p_column != -1) { + if (E->key() > p_column) { + break; + } + in_region = E->value() != -1 && delimiters[E->value()].type == p_type; + region = in_region ? E->value() : -1; + continue; + } + + /* If no column, calulate if the entire line is a region */ + /* excluding whitespace. */ + const String line = get_line(p_line); + if (!in_region) { + if (E->value() == -1 || delimiters[E->value()].type != p_type) { + break; + } + + region = E->value(); + in_region = true; + for (int i = E->key() - 2; i >= 0; i--) { + if (!_is_whitespace(line[i])) { + return -1; + } + } + } + + if (delimiters[region].line_only) { + return region; + } + + int end_col = E->key(); + if (E->value() != -1) { + if (!E->next()) { + return region; + } + end_col = E->next()->key(); + } + + for (int i = end_col; i < line.length(); i++) { + if (!_is_whitespace(line[i])) { + return -1; + } + } + return region; + } + return in_region ? region : -1; +} + +void CodeEdit::_add_delimiter(const String &p_start_key, const String &p_end_key, bool p_line_only, DelimiterType p_type) { + if (p_start_key.length() > 0) { + for (int i = 0; i < p_start_key.length(); i++) { + ERR_FAIL_COND_MSG(!is_symbol(p_start_key[i]), "delimiter must start with a symbol"); + } + } + + if (p_end_key.length() > 0) { + for (int i = 0; i < p_end_key.length(); i++) { + ERR_FAIL_COND_MSG(!is_symbol(p_end_key[i]), "delimiter must end with a symbol"); + } + } + + int at = 0; + for (int i = 0; i < delimiters.size(); i++) { + ERR_FAIL_COND_MSG(delimiters[i].start_key == p_start_key, "delimiter with start key '" + p_start_key + "' already exists."); + if (p_start_key.length() < delimiters[i].start_key.length()) { + at++; + } + } + + Delimiter delimiter; + delimiter.type = p_type; + delimiter.start_key = p_start_key; + delimiter.end_key = p_end_key; + delimiter.line_only = p_line_only || p_end_key == ""; + delimiters.insert(at, delimiter); + if (!setting_delimiters) { + delimiter_cache.clear(); + _update_delimiter_cache(); + } +} + +void CodeEdit::_remove_delimiter(const String &p_start_key, DelimiterType p_type) { + for (int i = 0; i < delimiters.size(); i++) { + if (delimiters[i].start_key != p_start_key) { + continue; + } + + if (delimiters[i].type != p_type) { + break; + } + + delimiters.remove(i); + if (!setting_delimiters) { + delimiter_cache.clear(); + _update_delimiter_cache(); + } + break; + } +} + +bool CodeEdit::_has_delimiter(const String &p_start_key, DelimiterType p_type) const { + for (int i = 0; i < delimiters.size(); i++) { + if (delimiters[i].start_key == p_start_key) { + return delimiters[i].type == p_type; + } + } + return false; +} + +void CodeEdit::_set_delimiters(const TypedArray<String> &p_delimiters, DelimiterType p_type) { + setting_delimiters = true; + _clear_delimiters(p_type); + + for (int i = 0; i < p_delimiters.size(); i++) { + String key = p_delimiters[i].is_null() ? "" : p_delimiters[i]; + + const String start_key = key.get_slice(" ", 0); + const String end_key = key.get_slice_count(" ") > 1 ? key.get_slice(" ", 1) : String(); + + _add_delimiter(start_key, end_key, end_key == "", p_type); + } + setting_delimiters = false; + _update_delimiter_cache(); +} + +void CodeEdit::_clear_delimiters(DelimiterType p_type) { + for (int i = delimiters.size() - 1; i >= 0; i--) { + if (delimiters[i].type == p_type) { + delimiters.remove(i); + } + } + delimiter_cache.clear(); +} + +TypedArray<String> CodeEdit::_get_delimiters(DelimiterType p_type) const { + TypedArray<String> r_delimiters; + for (int i = 0; i < delimiters.size(); i++) { + if (delimiters[i].type != p_type) { + continue; + } + r_delimiters.push_back(delimiters[i].start_key + (delimiters[i].end_key.is_empty() ? "" : " " + delimiters[i].end_key)); + } + return r_delimiters; +} + +/* Code Completion */ +void CodeEdit::_filter_code_completion_candidates() { + ScriptInstance *si = get_script_instance(); + if (si && si->has_method("_filter_code_completion_candidates")) { + code_completion_options.clear(); + code_completion_base = ""; + + /* Build options argument. */ + TypedArray<Dictionary> completion_options_sources; + completion_options_sources.resize(code_completion_option_sources.size()); + int i = 0; + for (List<ScriptCodeCompletionOption>::Element *E = code_completion_option_sources.front(); E; E = E->next()) { + Dictionary option; + option["kind"] = E->get().kind; + option["display_text"] = E->get().display; + option["insert_text"] = E->get().insert_text; + option["font_color"] = E->get().font_color; + option["icon"] = E->get().icon; + option["default_value"] = E->get().default_value; + completion_options_sources[i] = option; + i++; + } + + TypedArray<Dictionary> completion_options = si->call("_filter_code_completion_candidates", completion_options_sources); + + /* No options to complete, cancel. */ + if (completion_options.size() == 0) { + cancel_code_completion(); + return; + } + + /* Convert back into options. */ + int max_width = 0; + for (i = 0; i < completion_options.size(); i++) { + ScriptCodeCompletionOption option; + option.kind = (ScriptCodeCompletionOption::Kind)(int)completion_options[i].get("kind"); + option.display = completion_options[i].get("display_text"); + option.insert_text = completion_options[i].get("insert_text"); + option.font_color = completion_options[i].get("font_color"); + option.icon = completion_options[i].get("icon"); + option.default_value = completion_options[i].get("default_value"); + + max_width = MAX(max_width, cache.font->get_string_size(option.display).width); + code_completion_options.push_back(option); + } + + code_completion_longest_line = MIN(max_width, code_completion_max_width); + code_completion_current_selected = 0; + code_completion_active = true; + update(); + return; + } + + const int caret_line = cursor_get_line(); + const int caret_column = cursor_get_column(); + const String line = get_line(caret_line); + + if (caret_column > 0 && line[caret_column - 1] == '(' && !code_completion_forced) { + cancel_code_completion(); + return; + } + + /* Get string status, are we in one or at the close. */ + int in_string = is_in_string(caret_line, caret_column); + int first_quote_col = -1; + if (in_string != -1) { + Point2 string_start_pos = get_delimiter_start_position(caret_line, caret_column); + first_quote_col = (string_start_pos.y == caret_line) ? string_start_pos.x : -1; + } else if (caret_column > 0) { + if (is_in_string(caret_line, caret_column - 1) != -1) { + first_quote_col = caret_column - 1; + } + } + + int cofs = caret_column; + String string_to_complete; + bool prev_is_word = false; + + /* Cancel if we are at the close of a string. */ + if (in_string == -1 && first_quote_col == cofs - 1) { + cancel_code_completion(); + return; + /* In a string, therefore we are trying to complete the string text. */ + } else if (in_string != -1 && first_quote_col != -1) { + int key_length = delimiters[in_string].start_key.length(); + string_to_complete = line.substr(first_quote_col - key_length, (cofs - first_quote_col) + key_length); + /* If we have a space, previous word might be a keyword. eg "func |". */ + } else if (cofs > 0 && line[cofs - 1] == ' ') { + int ofs = cofs - 1; + while (ofs >= 0 && line[ofs] == ' ') { + ofs--; + } + prev_is_word = _is_char(line[ofs]); + /* Otherwise get current word and set cofs to the start. */ + } else { + int start_cofs = cofs; + while (cofs > 0 && line[cofs - 1] > 32 && (line[cofs - 1] == '/' || _is_char(line[cofs - 1]))) { + cofs--; + } + string_to_complete = line.substr(cofs, start_cofs - cofs); + } + + /* If all else fails, check for a prefix. */ + /* Single space between caret and prefix is okay. */ + bool prev_is_prefix = false; + if (cofs > 0 && code_completion_prefixes.has(String::chr(line[cofs - 1]))) { + prev_is_prefix = true; + } else if (cofs > 1 && line[cofs - 1] == ' ' && code_completion_prefixes.has(String::chr(line[cofs - 2]))) { + prev_is_prefix = true; + } + + if (!prev_is_word && string_to_complete.is_empty() && (cofs == 0 || !prev_is_prefix)) { + cancel_code_completion(); + return; + } + + /* Filter Options. */ + /* For now handle only tradional quoted strings. */ + bool single_quote = in_string != -1 && first_quote_col > 0 && delimiters[in_string].start_key == "'"; + + code_completion_options.clear(); + code_completion_base = string_to_complete; + + Vector<ScriptCodeCompletionOption> completion_options_casei; + Vector<ScriptCodeCompletionOption> completion_options_subseq; + Vector<ScriptCodeCompletionOption> completion_options_subseq_casei; + + int max_width = 0; + String string_to_complete_lower = string_to_complete.to_lower(); + for (List<ScriptCodeCompletionOption>::Element *E = code_completion_option_sources.front(); E; E = E->next()) { + ScriptCodeCompletionOption &option = E->get(); + + if (single_quote && option.display.is_quoted()) { + option.display = option.display.unquote().quote("'"); + } + + if (in_string != -1) { + String quote = single_quote ? "'" : "\""; + option.display = option.display.unquote().quote(quote); + option.insert_text = option.insert_text.unquote().quote(quote); + } + + if (option.display.length() == 0) { + continue; + } + + if (string_to_complete.length() == 0) { + code_completion_options.push_back(option); + max_width = MAX(max_width, cache.font->get_string_size(option.display).width); + continue; + } + + /* This code works the same as: + + if (option.display.begins_with(s)) { + completion_options.push_back(option); + } else if (option.display.to_lower().begins_with(s.to_lower())) { + completion_options_casei.push_back(option); + } else if (s.is_subsequence_of(option.display)) { + completion_options_subseq.push_back(option); + } else if (s.is_subsequence_ofi(option.display)) { + completion_options_subseq_casei.push_back(option); + } + + But is more performant due to being inlined and looping over the characters only once + */ + + String display_lower = option.display.to_lower(); + + const char32_t *ssq = &string_to_complete[0]; + const char32_t *ssq_lower = &string_to_complete_lower[0]; + + const char32_t *tgt = &option.display[0]; + const char32_t *tgt_lower = &display_lower[0]; + + const char32_t *ssq_last_tgt = nullptr; + const char32_t *ssq_lower_last_tgt = nullptr; + + for (; *tgt; tgt++, tgt_lower++) { + if (*ssq == *tgt) { + ssq++; + ssq_last_tgt = tgt; + } + if (*ssq_lower == *tgt_lower) { + ssq_lower++; + ssq_lower_last_tgt = tgt; + } + } + + /* Matched the whole subsequence in s. */ + if (!*ssq) { + /* Finished matching in the first s.length() characters. */ + if (ssq_last_tgt == &option.display[string_to_complete.length() - 1]) { + code_completion_options.push_back(option); + } else { + completion_options_subseq.push_back(option); + } + max_width = MAX(max_width, cache.font->get_string_size(option.display).width); + /* Matched the whole subsequence in s_lower. */ + } else if (!*ssq_lower) { + /* Finished matching in the first s.length() characters. */ + if (ssq_lower_last_tgt == &option.display[string_to_complete.length() - 1]) { + completion_options_casei.push_back(option); + } else { + completion_options_subseq_casei.push_back(option); + } + max_width = MAX(max_width, cache.font->get_string_size(option.display).width); + } + } + + code_completion_options.append_array(completion_options_casei); + code_completion_options.append_array(completion_options_subseq); + code_completion_options.append_array(completion_options_subseq_casei); + + /* No options to complete, cancel. */ + if (code_completion_options.size() == 0) { + cancel_code_completion(); + return; + } + + /* A perfect match, stop completion. */ + if (code_completion_options.size() == 1 && string_to_complete == code_completion_options[0].display) { + cancel_code_completion(); + return; + } + + code_completion_longest_line = MIN(max_width, code_completion_max_width); + code_completion_current_selected = 0; + code_completion_active = true; + update(); +} + void CodeEdit::_lines_edited_from(int p_from_line, int p_to_line) { + _update_delimiter_cache(p_from_line, p_to_line); + if (p_from_line == p_to_line) { return; } @@ -392,25 +1805,6 @@ void CodeEdit::_lines_edited_from(int p_from_line, int p_to_line) { } } -void CodeEdit::_update_gutter_indexes() { - for (int i = 0; i < get_gutter_count(); i++) { - if (get_gutter_name(i) == "main_gutter") { - main_gutter = i; - continue; - } - - if (get_gutter_name(i) == "line_numbers") { - line_number_gutter = i; - continue; - } - - if (get_gutter_name(i) == "fold_gutter") { - fold_gutter = i; - continue; - } - } -} - CodeEdit::CodeEdit() { /* Text Direction */ set_layout_direction(LAYOUT_DIRECTION_LTR); diff --git a/scene/gui/code_edit.h b/scene/gui/code_edit.h index d0c39ec0f1..6305eacf83 100644 --- a/scene/gui/code_edit.h +++ b/scene/gui/code_edit.h @@ -36,6 +36,22 @@ class CodeEdit : public TextEdit { GDCLASS(CodeEdit, TextEdit) +public: + /* Keep enum in sync with: */ + /* /core/object/script_language.h - ScriptCodeCompletionOption::Kind */ + enum CodeCompletionKind { + KIND_CLASS, + KIND_FUNCTION, + KIND_SIGNAL, + KIND_VARIABLE, + KIND_MEMBER, + KIND_ENUM, + KIND_CONSTANT, + KIND_NODE_PATH, + KIND_FILE_PATH, + KIND_PLAIN_TEXT, + }; + private: /* Main Gutter */ enum MainGutterType { @@ -80,16 +96,113 @@ private: void _fold_gutter_draw_callback(int p_line, int p_gutter, Rect2 p_region); void _gutter_clicked(int p_line, int p_gutter); - void _lines_edited_from(int p_from_line, int p_to_line); - void _update_gutter_indexes(); + /* Delimiters */ + enum DelimiterType { + TYPE_STRING, + TYPE_COMMENT, + }; + + struct Delimiter { + DelimiterType type; + String start_key = ""; + String end_key = ""; + bool line_only = true; + }; + bool setting_delimiters = false; + Vector<Delimiter> delimiters; + /* + * Vector entry per line, contains a Map of column numbers to delimiter index, -1 marks the end of a region. + * e.g the following text will be stored as so: + * + * 0: nothing here + * 1: + * 2: # test + * 3: "test" text "multiline + * 4: + * 5: test + * 6: string" + * + * Vector [ + * 0 = [] + * 1 = [] + * 2 = [ + * 1 = 1 + * 6 = -1 + * ] + * 3 = [ + * 1 = 0 + * 6 = -1 + * 13 = 0 + * ] + * 4 = [ + * 0 = 0 + * ] + * 5 = [ + * 5 = 0 + * ] + * 6 = [ + * 7 = -1 + * ] + * ] + */ + Vector<Map<int, int>> delimiter_cache; + + void _update_delimiter_cache(int p_from_line = 0, int p_to_line = -1); + int _is_in_delimiter(int p_line, int p_column, DelimiterType p_type) const; + + void _add_delimiter(const String &p_start_key, const String &p_end_key, bool p_line_only, DelimiterType p_type); + void _remove_delimiter(const String &p_start_key, DelimiterType p_type); + bool _has_delimiter(const String &p_start_key, DelimiterType p_type) const; + + void _set_delimiters(const TypedArray<String> &p_delimiters, DelimiterType p_type); + void _clear_delimiters(DelimiterType p_type); + TypedArray<String> _get_delimiters(DelimiterType p_type) const; + + /* Code Hint */ + String code_hint = ""; + + bool code_hint_draw_below = true; + int code_hint_xpos = -0xFFFF; + + /* Code Completion */ + bool code_completion_enabled = false; + bool code_completion_forced = false; + + int code_completion_max_width = 0; + int code_completion_max_lines = 7; + int code_completion_scroll_width = 0; + Color code_completion_scroll_color = Color(0, 0, 0, 0); + Color code_completion_background_color = Color(0, 0, 0, 0); + Color code_completion_selected_color = Color(0, 0, 0, 0); + Color code_completion_existing_color = Color(0, 0, 0, 0); + + bool code_completion_active = false; + Vector<ScriptCodeCompletionOption> code_completion_options; + int code_completion_line_ofs = 0; + int code_completion_current_selected = 0; + int code_completion_longest_line = 0; + Rect2i code_completion_rect; + + Set<String> code_completion_prefixes; + List<ScriptCodeCompletionOption> code_completion_option_submitted; + List<ScriptCodeCompletionOption> code_completion_option_sources; + String code_completion_base; + + void _filter_code_completion_candidates(); + + void _lines_edited_from(int p_from_line, int p_to_line); + protected: + void _gui_input(const Ref<InputEvent> &p_gui_input) override; void _notification(int p_what); static void _bind_methods(); public: + virtual CursorShape get_cursor_shape(const Point2 &p_pos = Point2i()) const override; + /* Main Gutter */ void set_draw_breakpoints_gutter(bool p_draw); bool is_drawing_breakpoints_gutter() const; @@ -128,8 +241,64 @@ public: void set_draw_fold_gutter(bool p_draw); bool is_drawing_fold_gutter() const; + /* Delimiters */ + void add_string_delimiter(const String &p_start_key, const String &p_end_key, bool p_line_only = false); + void remove_string_delimiter(const String &p_start_key); + bool has_string_delimiter(const String &p_start_key) const; + + void set_string_delimiters(const TypedArray<String> &p_string_delimiters); + void clear_string_delimiters(); + TypedArray<String> get_string_delimiters() const; + + int is_in_string(int p_line, int p_column = -1) const; + + void add_comment_delimiter(const String &p_start_key, const String &p_end_key, bool p_line_only = false); + void remove_comment_delimiter(const String &p_start_key); + bool has_comment_delimiter(const String &p_start_key) const; + + void set_comment_delimiters(const TypedArray<String> &p_comment_delimiters); + void clear_comment_delimiters(); + TypedArray<String> get_comment_delimiters() const; + + int is_in_comment(int p_line, int p_column = -1) const; + + String get_delimiter_start_key(int p_delimiter_idx) const; + String get_delimiter_end_key(int p_delimiter_idx) const; + + Point2 get_delimiter_start_position(int p_line, int p_column) const; + Point2 get_delimiter_end_position(int p_line, int p_column) const; + + /* Code hint */ + void set_code_hint(const String &p_hint); + void set_code_hint_draw_below(bool p_below); + + /* Code Completion */ + void set_code_completion_enabled(bool p_enable); + bool is_code_completion_enabled() const; + + void set_code_completion_prefixes(const TypedArray<String> &p_prefixes); + TypedArray<String> get_code_completion_prefixes() const; + + String get_text_for_code_completion() const; + + void request_code_completion(bool p_force = false); + + void add_code_completion_option(CodeCompletionKind p_type, const String &p_display_text, const String &p_insert_text, const Color &p_text_color = Color(1, 1, 1), const RES &p_icon = RES(), const Variant &p_value = Variant::NIL); + void update_code_completion_options(bool p_forced = false); + + TypedArray<Dictionary> get_code_completion_options() const; + Dictionary get_code_completion_option(int p_index) const; + + int get_code_completion_selected_index() const; + void set_code_completion_selected_index(int p_index); + + void confirm_code_completion(bool p_replace = false); + void cancel_code_completion(); + CodeEdit(); ~CodeEdit(); }; +VARIANT_ENUM_CAST(CodeEdit::CodeCompletionKind); + #endif // CODEEDIT_H diff --git a/scene/gui/color_picker.cpp b/scene/gui/color_picker.cpp index bddbe30f53..c0b4563615 100644 --- a/scene/gui/color_picker.cpp +++ b/scene/gui/color_picker.cpp @@ -84,6 +84,55 @@ void ColorPicker::_notification(int p_what) { } } +Ref<Shader> ColorPicker::wheel_shader; +Ref<Shader> ColorPicker::circle_shader; + +void ColorPicker::init_shaders() { + wheel_shader.instance(); + wheel_shader->set_code( + "shader_type canvas_item;" + "void fragment() {" + " float x = UV.x - 0.5;" + " float y = UV.y - 0.5;" + " float a = atan(y, x);" + " x += 0.001;" + " y += 0.001;" + " float b = float(sqrt(x * x + y * y) < 0.5) * float(sqrt(x * x + y * y) > 0.42);" + " x -= 0.002;" + " float b2 = float(sqrt(x * x + y * y) < 0.5) * float(sqrt(x * x + y * y) > 0.42);" + " y -= 0.002;" + " float b3 = float(sqrt(x * x + y * y) < 0.5) * float(sqrt(x * x + y * y) > 0.42);" + " x += 0.002;" + " float b4 = float(sqrt(x * x + y * y) < 0.5) * float(sqrt(x * x + y * y) > 0.42);" + " COLOR = vec4(clamp((abs(fract(((a - TAU) / TAU) + vec3(3.0, 2.0, 1.0) / 3.0) * 6.0 - 3.0) - 1.0), 0.0, 1.0), (b + b2 + b3 + b4) / 4.00);" + "}"); + + circle_shader.instance(); + circle_shader->set_code( + "shader_type canvas_item;" + "uniform float v = 1.0;" + "void fragment() {" + " float x = UV.x - 0.5;" + " float y = UV.y - 0.5;" + " float a = atan(y, x);" + " x += 0.001;" + " y += 0.001;" + " float b = float(sqrt(x * x + y * y) < 0.5);" + " x -= 0.002;" + " float b2 = float(sqrt(x * x + y * y) < 0.5);" + " y -= 0.002;" + " float b3 = float(sqrt(x * x + y * y) < 0.5);" + " x += 0.002;" + " float b4 = float(sqrt(x * x + y * y) < 0.5);" + " COLOR = vec4(mix(vec3(1.0), clamp(abs(fract(vec3((a - TAU) / TAU) + vec3(1.0, 2.0 / 3.0, 1.0 / 3.0)) * 6.0 - vec3(3.0)) - vec3(1.0), 0.0, 1.0), ((float(sqrt(x * x + y * y)) * 2.0)) / 1.0) * vec3(v), (b + b2 + b3 + b4) / 4.00);" + "}"); +} + +void ColorPicker::finish_shaders() { + wheel_shader.unref(); + circle_shader.unref(); +} + void ColorPicker::set_focus_on_line_edit() { c_text->call_deferred("grab_focus"); } @@ -115,19 +164,22 @@ void ColorPicker::_update_controls() { if (raw_mode_enabled) { for (int i = 0; i < 3; i++) { - scroll[i]->add_theme_icon_override("grabber", Ref<Texture2D>()); - scroll[i]->add_theme_icon_override("grabber_highlight", Ref<Texture2D>()); - scroll[i]->add_theme_style_override("slider", Ref<StyleBox>()); - scroll[i]->add_theme_style_override("grabber_area", Ref<StyleBox>()); - scroll[i]->add_theme_style_override("grabber_area_highlight", Ref<StyleBox>()); + scroll[i]->remove_theme_icon_override("grabber"); + scroll[i]->remove_theme_icon_override("grabber_highlight"); + scroll[i]->remove_theme_style_override("slider"); + scroll[i]->remove_theme_style_override("grabber_area"); + scroll[i]->remove_theme_style_override("grabber_area_highlight"); } } else { - for (int i = 0; i < 3; i++) { - scroll[i]->add_theme_icon_override("grabber", get_theme_icon("bar_arrow")); - scroll[i]->add_theme_icon_override("grabber_highlight", get_theme_icon("bar_arrow")); - scroll[i]->add_theme_style_override("slider", Ref<StyleBoxEmpty>(memnew(StyleBoxEmpty))); - scroll[i]->add_theme_style_override("grabber_area", Ref<StyleBoxEmpty>(memnew(StyleBoxEmpty))); - scroll[i]->add_theme_style_override("grabber_area_highlight", Ref<StyleBoxEmpty>(memnew(StyleBoxEmpty))); + Ref<StyleBoxEmpty> style_box_empty(memnew(StyleBoxEmpty)); + Ref<Texture2D> bar_arrow = get_theme_icon("bar_arrow"); + + for (int i = 0; i < 4; i++) { + scroll[i]->add_theme_icon_override("grabber", bar_arrow); + scroll[i]->add_theme_icon_override("grabber_highlight", bar_arrow); + scroll[i]->add_theme_style_override("slider", style_box_empty); + scroll[i]->add_theme_style_override("grabber_area", style_box_empty); + scroll[i]->add_theme_style_override("grabber_area_highlight", style_box_empty); } } @@ -140,6 +192,30 @@ void ColorPicker::_update_controls() { scroll[3]->hide(); labels[3]->hide(); } + + switch (picker_type) { + case SHAPE_HSV_RECTANGLE: + wheel_edit->hide(); + w_edit->show(); + uv_edit->show(); + break; + case SHAPE_HSV_WHEEL: + wheel_edit->show(); + w_edit->hide(); + uv_edit->hide(); + + wheel->set_material(wheel_mat); + break; + case SHAPE_VHS_CIRCLE: + wheel_edit->show(); + w_edit->show(); + uv_edit->hide(); + + wheel->set_material(circle_mat); + break; + default: { + } + } } void ColorPicker::_set_pick_color(const Color &p_color, bool p_update_sliders) { @@ -162,6 +238,18 @@ void ColorPicker::set_pick_color(const Color &p_color) { _set_pick_color(p_color, true); //because setters can't have more arguments } +void ColorPicker::set_old_color(const Color &p_color) { + old_color = p_color; +} + +void ColorPicker::set_display_old_color(bool p_enabled) { + display_old_color = p_enabled; +} + +bool ColorPicker::is_displaying_old_color() const { + return display_old_color; +} + void ColorPicker::set_edit_alpha(bool p_show) { edit_alpha = p_show; _update_controls(); @@ -264,6 +352,8 @@ void ColorPicker::_update_color(bool p_update_sliders) { for (int i = 0; i < 4; i++) { scroll[i]->update(); } + wheel->update(); + wheel_uv->update(); updating = false; } @@ -306,6 +396,18 @@ Color ColorPicker::get_pick_color() const { return color; } +void ColorPicker::set_picker_shape(PickerShapeType p_picker_type) { + ERR_FAIL_INDEX(p_picker_type, SHAPE_MAX); + picker_type = p_picker_type; + + _update_controls(); + _update_color(); +} + +ColorPicker::PickerShapeType ColorPicker::get_picker_shape() const { + return picker_type; +} + void ColorPicker::add_preset(const Color &p_color) { if (presets.find(p_color)) { presets.move_to_back(presets.find(p_color)); @@ -417,18 +519,53 @@ void ColorPicker::_update_text_value() { c_text->set_visible(visible); } +void ColorPicker::_sample_input(const Ref<InputEvent> &p_event) { + const Ref<InputEventMouseButton> mb = p_event; + if (mb.is_valid() && mb->is_pressed() && mb->get_button_index() == MOUSE_BUTTON_LEFT) { + const Rect2 rect_old = Rect2(Point2(), Size2(sample->get_size().width * 0.5, sample->get_size().height * 0.95)); + if (rect_old.has_point(mb->get_position())) { + // Revert to the old color when left-clicking the old color sample. + color = old_color; + _update_color(); + emit_signal("color_changed", color); + } + } +} + void ColorPicker::_sample_draw() { - const Rect2 r = Rect2(Point2(), Size2(uv_edit->get_size().width, sample->get_size().height * 0.95)); + // Covers the right half of the sample if the old color is being displayed, + // or the whole sample if it's not being displayed. + Rect2 rect_new; + + if (display_old_color) { + rect_new = Rect2(Point2(sample->get_size().width * 0.5, 0), Size2(sample->get_size().width * 0.5, sample->get_size().height * 0.95)); + + // Draw both old and new colors for easier comparison (only if spawned from a ColorPickerButton). + const Rect2 rect_old = Rect2(Point2(), Size2(sample->get_size().width * 0.5, sample->get_size().height * 0.95)); + + if (display_old_color && old_color.a < 1.0) { + sample->draw_texture_rect(get_theme_icon("preset_bg", "ColorPicker"), rect_old, true); + } + + sample->draw_rect(rect_old, old_color); + + if (old_color.r > 1 || old_color.g > 1 || old_color.b > 1) { + // Draw an indicator to denote that the old color is "overbright" and can't be displayed accurately in the preview. + sample->draw_texture(get_theme_icon("overbright_indicator", "ColorPicker"), Point2()); + } + } else { + rect_new = Rect2(Point2(), Size2(sample->get_size().width, sample->get_size().height * 0.95)); + } if (color.a < 1.0) { - sample->draw_texture_rect(get_theme_icon("preset_bg", "ColorPicker"), r, true); + sample->draw_texture_rect(get_theme_icon("preset_bg", "ColorPicker"), rect_new, true); } - sample->draw_rect(r, color); + sample->draw_rect(rect_new, color); if (color.r > 1 || color.g > 1 || color.b > 1) { - // Draw an indicator to denote that the color is "overbright" and can't be displayed accurately in the preview - sample->draw_texture(get_theme_icon("overbright_indicator", "ColorPicker"), Point2()); + // Draw an indicator to denote that the new color is "overbright" and can't be displayed accurately in the preview. + sample->draw_texture(get_theme_icon("overbright_indicator", "ColorPicker"), Point2(uv_edit->get_size().width * 0.5, 0)); } } @@ -438,42 +575,131 @@ void ColorPicker::_hsv_draw(int p_which, Control *c) { } if (p_which == 0) { Vector<Point2> points; - points.push_back(Vector2()); - points.push_back(Vector2(c->get_size().x, 0)); - points.push_back(c->get_size()); - points.push_back(Vector2(0, c->get_size().y)); Vector<Color> colors; - colors.push_back(Color(1, 1, 1, 1)); - colors.push_back(Color(1, 1, 1, 1)); - colors.push_back(Color(0, 0, 0, 1)); - colors.push_back(Color(0, 0, 0, 1)); - c->draw_polygon(points, colors); Vector<Color> colors2; Color col = color; + Vector2 center = c->get_size() / 2.0; + + switch (picker_type) { + case SHAPE_HSV_WHEEL: { + points.resize(4); + colors.resize(4); + colors2.resize(4); + real_t ring_radius_x = Math_SQRT12 * c->get_size().width * 0.42; + real_t ring_radius_y = Math_SQRT12 * c->get_size().height * 0.42; + + points.set(0, center - Vector2(ring_radius_x, ring_radius_y)); + points.set(1, center + Vector2(ring_radius_x, -ring_radius_y)); + points.set(2, center + Vector2(ring_radius_x, ring_radius_y)); + points.set(3, center + Vector2(-ring_radius_x, ring_radius_y)); + colors.set(0, Color(1, 1, 1, 1)); + colors.set(1, Color(1, 1, 1, 1)); + colors.set(2, Color(0, 0, 0, 1)); + colors.set(3, Color(0, 0, 0, 1)); + c->draw_polygon(points, colors); + + col.set_hsv(h, 1, 1); + col.a = 0; + colors2.set(0, col); + col.a = 1; + colors2.set(1, col); + col.set_hsv(h, 1, 0); + colors2.set(2, col); + col.a = 0; + colors2.set(3, col); + c->draw_polygon(points, colors2); + break; + } + case SHAPE_HSV_RECTANGLE: { + points.resize(4); + colors.resize(4); + colors2.resize(4); + points.set(0, Vector2()); + points.set(1, Vector2(c->get_size().x, 0)); + points.set(2, c->get_size()); + points.set(3, Vector2(0, c->get_size().y)); + colors.set(0, Color(1, 1, 1, 1)); + colors.set(1, Color(1, 1, 1, 1)); + colors.set(2, Color(0, 0, 0, 1)); + colors.set(3, Color(0, 0, 0, 1)); + c->draw_polygon(points, colors); + col = color; + col.set_hsv(h, 1, 1); + col.a = 0; + colors2.set(0, col); + col.a = 1; + colors2.set(1, col); + col.set_hsv(h, 1, 0); + colors2.set(2, col); + col.a = 0; + colors2.set(3, col); + c->draw_polygon(points, colors2); + break; + } + default: { + } + } + Ref<Texture2D> cursor = get_theme_icon("picker_cursor", "ColorPicker"); + int x; + int y; + if (picker_type == SHAPE_VHS_CIRCLE) { + x = center.x + (center.x * Math::cos(h * Math_TAU) * s) - (cursor->get_width() / 2); + y = center.y + (center.y * Math::sin(h * Math_TAU) * s) - (cursor->get_height() / 2); + } else { + real_t corner_x = (c == wheel_uv) ? center.x - Math_SQRT12 * c->get_size().width * 0.42 : 0; + real_t corner_y = (c == wheel_uv) ? center.y - Math_SQRT12 * c->get_size().height * 0.42 : 0; + + Size2 real_size(c->get_size().x - corner_x * 2, c->get_size().y - corner_y * 2); + x = CLAMP(real_size.x * s, 0, real_size.x) + corner_x - (cursor->get_width() / 2); + y = CLAMP(real_size.y - real_size.y * v, 0, real_size.y) + corner_y - (cursor->get_height() / 2); + } + c->draw_texture(cursor, Point2(x, y)); + col.set_hsv(h, 1, 1); - col.a = 0; - colors2.push_back(col); - col.a = 1; - colors2.push_back(col); - col.set_hsv(h, 1, 0); - colors2.push_back(col); - col.a = 0; - colors2.push_back(col); - c->draw_polygon(points, colors2); - int x = CLAMP(c->get_size().x * s, 0, c->get_size().x); - int y = CLAMP(c->get_size().y - c->get_size().y * v, 0, c->get_size().y); - col = color; - col.a = 1; - c->draw_line(Point2(x, 0), Point2(x, c->get_size().y), col.inverted()); - c->draw_line(Point2(0, y), Point2(c->get_size().x, y), col.inverted()); - c->draw_line(Point2(x, y), Point2(x, y), Color(1, 1, 1), 2); + if (picker_type == SHAPE_HSV_WHEEL) { + points.resize(4); + double h1 = h - (0.5 / 360); + double h2 = h + (0.5 / 360); + points.set(0, Point2(center.x + (center.x * Math::cos(h1 * Math_TAU)), center.y + (center.y * Math::sin(h1 * Math_TAU)))); + points.set(1, Point2(center.x + (center.x * Math::cos(h1 * Math_TAU) * 0.84), center.y + (center.y * Math::sin(h1 * Math_TAU) * 0.84))); + points.set(2, Point2(center.x + (center.x * Math::cos(h2 * Math_TAU)), center.y + (center.y * Math::sin(h2 * Math_TAU)))); + points.set(3, Point2(center.x + (center.x * Math::cos(h2 * Math_TAU) * 0.84), center.y + (center.y * Math::sin(h2 * Math_TAU) * 0.84))); + c->draw_multiline(points, col.inverted()); + } + } else if (p_which == 1) { - Ref<Texture2D> hue = get_theme_icon("color_hue", "ColorPicker"); - c->draw_texture_rect(hue, Rect2(Point2(), c->get_size())); - int y = c->get_size().y - c->get_size().y * (1.0 - h); - Color col = Color(); - col.set_hsv(h, 1, 1); - c->draw_line(Point2(0, y), Point2(c->get_size().x, y), col.inverted()); + if (picker_type == SHAPE_HSV_RECTANGLE) { + Ref<Texture2D> hue = get_theme_icon("color_hue", "ColorPicker"); + c->draw_texture_rect(hue, Rect2(Point2(), c->get_size())); + int y = c->get_size().y - c->get_size().y * (1.0 - h); + Color col; + col.set_hsv(h, 1, 1); + c->draw_line(Point2(0, y), Point2(c->get_size().x, y), col.inverted()); + } else if (picker_type == SHAPE_VHS_CIRCLE) { + Vector<Point2> points; + Vector<Color> colors; + Color col; + col.set_hsv(h, s, 1); + points.resize(4); + colors.resize(4); + points.set(0, Vector2()); + points.set(1, Vector2(c->get_size().x, 0)); + points.set(2, c->get_size()); + points.set(3, Vector2(0, c->get_size().y)); + colors.set(0, col); + colors.set(1, col); + colors.set(2, Color(0, 0, 0)); + colors.set(3, Color(0, 0, 0)); + c->draw_polygon(points, colors); + int y = c->get_size().y - c->get_size().y * CLAMP(v, 0, 1); + col.set_hsv(h, 1, v); + c->draw_line(Point2(0, y), Point2(c->get_size().x, y), col.inverted()); + } + } else if (p_which == 2) { + c->draw_rect(Rect2(Point2(), c->get_size()), Color(1, 1, 1)); + if (picker_type == SHAPE_VHS_CIRCLE) { + circle_mat->set_shader_param("v", v); + } } } @@ -540,16 +766,51 @@ void ColorPicker::_slider_draw(int p_which) { scroll[p_which]->draw_polygon(pos, col); } -void ColorPicker::_uv_input(const Ref<InputEvent> &p_event) { +void ColorPicker::_uv_input(const Ref<InputEvent> &p_event, Control *c) { Ref<InputEventMouseButton> bev = p_event; if (bev.is_valid()) { - if (bev->is_pressed() && bev->get_button_index() == BUTTON_LEFT) { + if (bev->is_pressed() && bev->get_button_index() == MOUSE_BUTTON_LEFT) { + Vector2 center = c->get_size() / 2.0; + if (picker_type == SHAPE_VHS_CIRCLE) { + real_t dist = center.distance_to(bev->get_position()); + + if (dist <= center.x) { + real_t rad = Math::atan2(bev->get_position().y - center.y, bev->get_position().x - center.x); + h = ((rad >= 0) ? rad : (Math_TAU + rad)) / Math_TAU; + s = CLAMP(dist / center.x, 0, 1); + } else { + return; + } + } else { + real_t corner_x = (c == wheel_uv) ? center.x - Math_SQRT12 * c->get_size().width * 0.42 : 0; + real_t corner_y = (c == wheel_uv) ? center.y - Math_SQRT12 * c->get_size().height * 0.42 : 0; + Size2 real_size(c->get_size().x - corner_x * 2, c->get_size().y - corner_y * 2); + + if (bev->get_position().x < corner_x || bev->get_position().x > c->get_size().x - corner_x || + bev->get_position().y < corner_y || bev->get_position().y > c->get_size().y - corner_y) { + { + real_t dist = center.distance_to(bev->get_position()); + + if (dist >= center.x * 0.84 && dist <= center.x) { + real_t rad = Math::atan2(bev->get_position().y - center.y, bev->get_position().x - center.x); + h = ((rad >= 0) ? rad : (Math_TAU + rad)) / Math_TAU; + spinning = true; + } else { + return; + } + } + } + + if (!spinning) { + real_t x = CLAMP(bev->get_position().x, corner_x, c->get_size().x - corner_x); + real_t y = CLAMP(bev->get_position().y, corner_x, c->get_size().y - corner_y); + + s = (x - c->get_position().x - corner_x) / real_size.x; + v = 1.0 - (y - c->get_position().y - corner_y) / real_size.y; + } + } changing_color = true; - float x = CLAMP((float)bev->get_position().x, 0, uv_edit->get_size().width); - float y = CLAMP((float)bev->get_position().y, 0, uv_edit->get_size().height); - s = x / uv_edit->get_size().width; - v = 1.0 - y / uv_edit->get_size().height; color.set_hsv(h, s, v, color.a); last_hsv = color; set_pick_color(color); @@ -557,11 +818,13 @@ void ColorPicker::_uv_input(const Ref<InputEvent> &p_event) { if (!deferred_mode_enabled) { emit_signal("color_changed", color); } - } else if (deferred_mode_enabled && !bev->is_pressed() && bev->get_button_index() == BUTTON_LEFT) { + } else if (deferred_mode_enabled && !bev->is_pressed() && bev->get_button_index() == MOUSE_BUTTON_LEFT) { emit_signal("color_changed", color); changing_color = false; + spinning = false; } else { changing_color = false; + spinning = false; } } @@ -571,10 +834,30 @@ void ColorPicker::_uv_input(const Ref<InputEvent> &p_event) { if (!changing_color) { return; } - float x = CLAMP((float)mev->get_position().x, 0, uv_edit->get_size().width); - float y = CLAMP((float)mev->get_position().y, 0, uv_edit->get_size().height); - s = x / uv_edit->get_size().width; - v = 1.0 - y / uv_edit->get_size().height; + + Vector2 center = c->get_size() / 2.0; + if (picker_type == SHAPE_VHS_CIRCLE) { + real_t dist = center.distance_to(mev->get_position()); + real_t rad = Math::atan2(mev->get_position().y - center.y, mev->get_position().x - center.x); + h = ((rad >= 0) ? rad : (Math_TAU + rad)) / Math_TAU; + s = CLAMP(dist / center.x, 0, 1); + } else { + if (spinning) { + real_t rad = Math::atan2(mev->get_position().y - center.y, mev->get_position().x - center.x); + h = ((rad >= 0) ? rad : (Math_TAU + rad)) / Math_TAU; + } else { + real_t corner_x = (c == wheel_uv) ? center.x - Math_SQRT12 * c->get_size().width * 0.42 : 0; + real_t corner_y = (c == wheel_uv) ? center.y - Math_SQRT12 * c->get_size().height * 0.42 : 0; + Size2 real_size(c->get_size().x - corner_x * 2, c->get_size().y - corner_y * 2); + + real_t x = CLAMP(mev->get_position().x, corner_x, c->get_size().x - corner_x); + real_t y = CLAMP(mev->get_position().y, corner_x, c->get_size().y - corner_y); + + s = (x - corner_x) / real_size.x; + v = 1.0 - (y - corner_y) / real_size.y; + } + } + color.set_hsv(h, s, v, color.a); last_hsv = color; set_pick_color(color); @@ -589,10 +872,14 @@ void ColorPicker::_w_input(const Ref<InputEvent> &p_event) { Ref<InputEventMouseButton> bev = p_event; if (bev.is_valid()) { - if (bev->is_pressed() && bev->get_button_index() == BUTTON_LEFT) { + if (bev->is_pressed() && bev->get_button_index() == MOUSE_BUTTON_LEFT) { changing_color = true; float y = CLAMP((float)bev->get_position().y, 0, w_edit->get_size().height); - h = y / w_edit->get_size().height; + if (picker_type == SHAPE_VHS_CIRCLE) { + v = 1.0 - (y / w_edit->get_size().height); + } else { + h = y / w_edit->get_size().height; + } } else { changing_color = false; } @@ -602,7 +889,7 @@ void ColorPicker::_w_input(const Ref<InputEvent> &p_event) { _update_color(); if (!deferred_mode_enabled) { emit_signal("color_changed", color); - } else if (!bev->is_pressed() && bev->get_button_index() == BUTTON_LEFT) { + } else if (!bev->is_pressed() && bev->get_button_index() == MOUSE_BUTTON_LEFT) { emit_signal("color_changed", color); } } @@ -614,7 +901,11 @@ void ColorPicker::_w_input(const Ref<InputEvent> &p_event) { return; } float y = CLAMP((float)mev->get_position().y, 0, w_edit->get_size().height); - h = y / w_edit->get_size().height; + if (picker_type == SHAPE_VHS_CIRCLE) { + v = 1.0 - (y / w_edit->get_size().height); + } else { + h = y / w_edit->get_size().height; + } color.set_hsv(h, s, v, color.a); last_hsv = color; set_pick_color(color); @@ -630,7 +921,7 @@ void ColorPicker::_preset_input(const Ref<InputEvent> &p_event) { if (bev.is_valid()) { int index = 0; - if (bev->is_pressed() && bev->get_button_index() == BUTTON_LEFT) { + if (bev->is_pressed() && bev->get_button_index() == MOUSE_BUTTON_LEFT) { for (int i = 0; i < presets.size(); i++) { int x = (i % presets_per_row) * bt_add_preset->get_size().x; int y = (Math::floor((float)i / presets_per_row)) * bt_add_preset->get_size().y; @@ -641,7 +932,7 @@ void ColorPicker::_preset_input(const Ref<InputEvent> &p_event) { set_pick_color(presets[index]); _update_color(); emit_signal("color_changed", color); - } else if (bev->is_pressed() && bev->get_button_index() == BUTTON_RIGHT && presets_enabled) { + } else if (bev->is_pressed() && bev->get_button_index() == MOUSE_BUTTON_RIGHT && presets_enabled) { index = bev->get_position().x / (preset->get_size().x / presets.size()); Color clicked_preset = presets[index]; erase_preset(clicked_preset); @@ -670,7 +961,7 @@ void ColorPicker::_screen_input(const Ref<InputEvent> &p_event) { } Ref<InputEventMouseButton> bev = p_event; - if (bev.is_valid() && bev->get_button_index() == BUTTON_LEFT && !bev->is_pressed()) { + if (bev.is_valid() && bev->get_button_index() == MOUSE_BUTTON_LEFT && !bev->is_pressed()) { emit_signal("color_changed", color); screen->hide(); } @@ -682,7 +973,7 @@ void ColorPicker::_screen_input(const Ref<InputEvent> &p_event) { return; } - Ref<Image> img = r->get_texture()->get_data(); + Ref<Image> img = r->get_texture()->get_image(); if (img.is_valid() && !img->is_empty()) { Vector2 ofs = mev->get_global_position() - r->get_visible_rect().get_position(); Color c = img->get_pixel(ofs.x, r->get_visible_rect().size.height - ofs.y); @@ -798,18 +1089,25 @@ void ColorPicker::_bind_methods() { ClassDB::bind_method(D_METHOD("add_preset", "color"), &ColorPicker::add_preset); ClassDB::bind_method(D_METHOD("erase_preset", "color"), &ColorPicker::erase_preset); ClassDB::bind_method(D_METHOD("get_presets"), &ColorPicker::get_presets); + ClassDB::bind_method(D_METHOD("set_picker_shape", "picker"), &ColorPicker::set_picker_shape); + ClassDB::bind_method(D_METHOD("get_picker_shape"), &ColorPicker::get_picker_shape); ADD_PROPERTY(PropertyInfo(Variant::COLOR, "color"), "set_pick_color", "get_pick_color"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "edit_alpha"), "set_edit_alpha", "is_editing_alpha"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "hsv_mode"), "set_hsv_mode", "is_hsv_mode"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "raw_mode"), "set_raw_mode", "is_raw_mode"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "deferred_mode"), "set_deferred_mode", "is_deferred_mode"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "picker_shape", PROPERTY_HINT_ENUM, "HSV Rectangle,HSV Rectangle Wheel,VHS Circle"), "set_picker_shape", "get_picker_shape"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "presets_enabled"), "set_presets_enabled", "are_presets_enabled"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "presets_visible"), "set_presets_visible", "are_presets_visible"); ADD_SIGNAL(MethodInfo("color_changed", PropertyInfo(Variant::COLOR, "color"))); ADD_SIGNAL(MethodInfo("preset_added", PropertyInfo(Variant::COLOR, "color"))); ADD_SIGNAL(MethodInfo("preset_removed", PropertyInfo(Variant::COLOR, "color"))); + + BIND_ENUM_CONSTANT(SHAPE_HSV_RECTANGLE); + BIND_ENUM_CONSTANT(SHAPE_HSV_WHEEL); + BIND_ENUM_CONSTANT(SHAPE_VHS_CIRCLE); } ColorPicker::ColorPicker() : @@ -818,36 +1116,26 @@ ColorPicker::ColorPicker() : add_child(hb_edit); hb_edit->set_v_size_flags(SIZE_EXPAND_FILL); - uv_edit = memnew(Control); hb_edit->add_child(uv_edit); - uv_edit->connect("gui_input", callable_mp(this, &ColorPicker::_uv_input)); + uv_edit->connect("gui_input", callable_mp(this, &ColorPicker::_uv_input), make_binds(uv_edit)); uv_edit->set_mouse_filter(MOUSE_FILTER_PASS); uv_edit->set_h_size_flags(SIZE_EXPAND_FILL); uv_edit->set_v_size_flags(SIZE_EXPAND_FILL); uv_edit->set_custom_minimum_size(Size2(get_theme_constant("sv_width"), get_theme_constant("sv_height"))); uv_edit->connect("draw", callable_mp(this, &ColorPicker::_hsv_draw), make_binds(0, uv_edit)); - w_edit = memnew(Control); - hb_edit->add_child(w_edit); - w_edit->set_custom_minimum_size(Size2(get_theme_constant("h_width"), 0)); - w_edit->set_h_size_flags(SIZE_FILL); - w_edit->set_v_size_flags(SIZE_EXPAND_FILL); - w_edit->connect("gui_input", callable_mp(this, &ColorPicker::_w_input)); - w_edit->connect("draw", callable_mp(this, &ColorPicker::_hsv_draw), make_binds(1, w_edit)); - HBoxContainer *hb_smpl = memnew(HBoxContainer); add_child(hb_smpl); - sample = memnew(TextureRect); hb_smpl->add_child(sample); sample->set_h_size_flags(SIZE_EXPAND_FILL); + sample->connect("gui_input", callable_mp(this, &ColorPicker::_sample_input)); sample->connect("draw", callable_mp(this, &ColorPicker::_sample_draw)); - btn_pick = memnew(Button); btn_pick->set_flat(true); hb_smpl->add_child(btn_pick); btn_pick->set_toggle_mode(true); - btn_pick->set_tooltip(TTR("Pick a color from the editor window.")); + btn_pick->set_tooltip(RTR("Pick a color from the editor window.")); btn_pick->connect("pressed", callable_mp(this, &ColorPicker::_screen_pick_pressed)); VBoxContainer *vbl = memnew(VBoxContainer); @@ -887,27 +1175,20 @@ ColorPicker::ColorPicker() : vbr->add_child(hbc); } + labels[3]->set_text("A"); - scroll[3]->add_theme_icon_override("grabber", get_theme_icon("bar_arrow")); - scroll[3]->add_theme_icon_override("grabber_highlight", get_theme_icon("bar_arrow")); - scroll[3]->add_theme_style_override("slider", Ref<StyleBoxEmpty>(memnew(StyleBoxEmpty))); - scroll[3]->add_theme_style_override("grabber_area", Ref<StyleBoxEmpty>(memnew(StyleBoxEmpty))); - scroll[3]->add_theme_style_override("grabber_area_highlight", Ref<StyleBoxEmpty>(memnew(StyleBoxEmpty))); HBoxContainer *hhb = memnew(HBoxContainer); vbr->add_child(hhb); - btn_hsv = memnew(CheckButton); hhb->add_child(btn_hsv); - btn_hsv->set_text(TTR("HSV")); + btn_hsv->set_text(RTR("HSV")); btn_hsv->connect("toggled", callable_mp(this, &ColorPicker::set_hsv_mode)); - btn_raw = memnew(CheckButton); hhb->add_child(btn_raw); - btn_raw->set_text(TTR("Raw")); + btn_raw->set_text(RTR("Raw")); btn_raw->connect("toggled", callable_mp(this, &ColorPicker::set_raw_mode)); - text_type = memnew(Button); hhb->add_child(text_type); text_type->set_text("#"); text_type->set_tooltip(TTR("Switch between hexadecimal and code values.")); @@ -921,41 +1202,76 @@ ColorPicker::ColorPicker() : text_type->set_mouse_filter(MOUSE_FILTER_IGNORE); } - c_text = memnew(LineEdit); hhb->add_child(c_text); c_text->set_h_size_flags(SIZE_EXPAND_FILL); c_text->connect("text_entered", callable_mp(this, &ColorPicker::_html_entered)); c_text->connect("focus_entered", callable_mp(this, &ColorPicker::_focus_enter)); c_text->connect("focus_exited", callable_mp(this, &ColorPicker::_html_focus_exit)); + wheel_edit->set_h_size_flags(SIZE_EXPAND_FILL); + wheel_edit->set_v_size_flags(SIZE_EXPAND_FILL); + wheel_edit->set_custom_minimum_size(Size2(get_theme_constant("sv_width"), get_theme_constant("sv_height"))); + hb_edit->add_child(wheel_edit); + + wheel_mat.instance(); + wheel_mat->set_shader(wheel_shader); + circle_mat.instance(); + circle_mat->set_shader(circle_shader); + + MarginContainer *wheel_margin(memnew(MarginContainer)); +#ifdef TOOLS_ENABLED + wheel_margin->add_theme_constant_override("margin_bottom", 8 * EDSCALE); +#else + wheel_margin->add_theme_constant_override("margin_bottom", 8); +#endif + wheel_edit->add_child(wheel_margin); + + wheel_margin->add_child(wheel); + wheel->set_mouse_filter(MOUSE_FILTER_PASS); + wheel->connect("draw", callable_mp(this, &ColorPicker::_hsv_draw), make_binds(2, wheel)); + + wheel_margin->add_child(wheel_uv); + wheel_uv->connect("gui_input", callable_mp(this, &ColorPicker::_uv_input), make_binds(wheel_uv)); + wheel_uv->connect("draw", callable_mp(this, &ColorPicker::_hsv_draw), make_binds(0, wheel_uv)); + + hb_edit->add_child(w_edit); + w_edit->set_custom_minimum_size(Size2(get_theme_constant("h_width"), 0)); + w_edit->set_h_size_flags(SIZE_FILL); + w_edit->set_v_size_flags(SIZE_EXPAND_FILL); + w_edit->connect("gui_input", callable_mp(this, &ColorPicker::_w_input)); + w_edit->connect("draw", callable_mp(this, &ColorPicker::_hsv_draw), make_binds(1, w_edit)); + + picker_type = SHAPE_HSV_RECTANGLE; _update_controls(); updating = false; set_pick_color(Color(1, 1, 1)); - preset_separator = memnew(HSeparator); add_child(preset_separator); - preset_container = memnew(HBoxContainer); preset_container->set_h_size_flags(SIZE_EXPAND_FILL); add_child(preset_container); - preset = memnew(TextureRect); preset_container->add_child(preset); preset->connect("gui_input", callable_mp(this, &ColorPicker::_preset_input)); preset->connect("draw", callable_mp(this, &ColorPicker::_update_presets)); - preset_container2 = memnew(HBoxContainer); preset_container2->set_h_size_flags(SIZE_EXPAND_FILL); add_child(preset_container2); - bt_add_preset = memnew(Button); preset_container2->add_child(bt_add_preset); - bt_add_preset->set_tooltip(TTR("Add current color as a preset.")); + bt_add_preset->set_tooltip(RTR("Add current color as a preset.")); bt_add_preset->connect("pressed", callable_mp(this, &ColorPicker::_add_preset_pressed)); } ///////////////// +void ColorPickerButton::_about_to_popup() { + set_pressed(true); + if (picker) { + picker->set_old_color(color); + } +} + void ColorPickerButton::_color_changed(const Color &p_color) { color = p_color; update(); @@ -1068,10 +1384,11 @@ void ColorPickerButton::_update_picker() { popup->add_child(picker); add_child(popup); picker->connect("color_changed", callable_mp(this, &ColorPickerButton::_color_changed)); - popup->connect("about_to_popup", callable_mp((BaseButton *)this, &BaseButton::set_pressed), varray(true)); + popup->connect("about_to_popup", callable_mp(this, &ColorPickerButton::_about_to_popup)); popup->connect("popup_hide", callable_mp(this, &ColorPickerButton::_modal_closed)); picker->set_pick_color(color); picker->set_edit_alpha(edit_alpha); + picker->set_display_old_color(true); emit_signal("picker_created"); } } @@ -1083,6 +1400,7 @@ void ColorPickerButton::_bind_methods() { ClassDB::bind_method(D_METHOD("get_popup"), &ColorPickerButton::get_popup); ClassDB::bind_method(D_METHOD("set_edit_alpha", "show"), &ColorPickerButton::set_edit_alpha); ClassDB::bind_method(D_METHOD("is_editing_alpha"), &ColorPickerButton::is_editing_alpha); + ClassDB::bind_method(D_METHOD("_about_to_popup"), &ColorPickerButton::_about_to_popup); ADD_SIGNAL(MethodInfo("color_changed", PropertyInfo(Variant::COLOR, "color"))); ADD_SIGNAL(MethodInfo("popup_closed")); diff --git a/scene/gui/color_picker.h b/scene/gui/color_picker.h index 24e1746c41..14113467d0 100644 --- a/scene/gui/color_picker.h +++ b/scene/gui/color_picker.h @@ -31,6 +31,7 @@ #ifndef COLOR_PICKER_H #define COLOR_PICKER_H +#include "scene/gui/aspect_ratio_container.h" #include "scene/gui/box_container.h" #include "scene/gui/button.h" #include "scene/gui/check_button.h" @@ -45,36 +46,57 @@ class ColorPicker : public BoxContainer { GDCLASS(ColorPicker, BoxContainer); +public: + enum PickerShapeType { + SHAPE_HSV_RECTANGLE, + SHAPE_HSV_WHEEL, + SHAPE_VHS_CIRCLE, + + SHAPE_MAX + }; + private: + static Ref<Shader> wheel_shader; + static Ref<Shader> circle_shader; + Control *screen = nullptr; - Control *uv_edit; - Control *w_edit; - TextureRect *sample; - TextureRect *preset; - HBoxContainer *preset_container; - HBoxContainer *preset_container2; - HSeparator *preset_separator; - Button *bt_add_preset; + Control *uv_edit = memnew(Control); + Control *w_edit = memnew(Control); + AspectRatioContainer *wheel_edit = memnew(AspectRatioContainer); + Ref<ShaderMaterial> wheel_mat; + Ref<ShaderMaterial> circle_mat; + Control *wheel = memnew(Control); + Control *wheel_uv = memnew(Control); + TextureRect *sample = memnew(TextureRect); + TextureRect *preset = memnew(TextureRect); + HBoxContainer *preset_container = memnew(HBoxContainer); + HBoxContainer *preset_container2 = memnew(HBoxContainer); + HSeparator *preset_separator = memnew(HSeparator); + Button *bt_add_preset = memnew(Button); List<Color> presets; - Button *btn_pick; - CheckButton *btn_hsv; - CheckButton *btn_raw; + Button *btn_pick = memnew(Button); + CheckButton *btn_hsv = memnew(CheckButton); + CheckButton *btn_raw = memnew(CheckButton); HSlider *scroll[4]; SpinBox *values[4]; Label *labels[4]; - Button *text_type; - LineEdit *c_text; + Button *text_type = memnew(Button); + LineEdit *c_text = memnew(LineEdit); bool edit_alpha = true; Size2i ms; bool text_is_constructor = false; int presets_per_row = 0; + PickerShapeType picker_type = SHAPE_HSV_WHEEL; Color color; + Color old_color; + bool display_old_color = false; bool raw_mode_enabled = false; bool hsv_mode_enabled = false; bool deferred_mode_enabled = false; bool updating = true; bool changing_color = false; + bool spinning = false; bool presets_enabled = true; bool presets_visible = true; float h = 0.0; @@ -89,11 +111,12 @@ private: void _update_presets(); void _update_text_value(); void _text_type_toggled(); + void _sample_input(const Ref<InputEvent> &p_event); void _sample_draw(); void _hsv_draw(int p_which, Control *c); void _slider_draw(int p_which); - void _uv_input(const Ref<InputEvent> &p_event); + void _uv_input(const Ref<InputEvent> &p_event, Control *c); void _w_input(const Ref<InputEvent> &p_event); void _preset_input(const Ref<InputEvent> &p_event); void _screen_input(const Ref<InputEvent> &p_event); @@ -108,12 +131,22 @@ protected: static void _bind_methods(); public: + static void init_shaders(); + static void finish_shaders(); + void set_edit_alpha(bool p_show); bool is_editing_alpha() const; void _set_pick_color(const Color &p_color, bool p_update_sliders); void set_pick_color(const Color &p_color); Color get_pick_color() const; + void set_old_color(const Color &p_color); + + void set_display_old_color(bool p_enabled); + bool is_displaying_old_color() const; + + void set_picker_shape(PickerShapeType p_picker_type); + PickerShapeType get_picker_shape() const; void add_preset(const Color &p_color); void erase_preset(const Color &p_color); @@ -151,6 +184,7 @@ class ColorPickerButton : public Button { Color color; bool edit_alpha = true; + void _about_to_popup(); void _color_changed(const Color &p_color); void _modal_closed(); @@ -175,4 +209,5 @@ public: ColorPickerButton(); }; +VARIANT_ENUM_CAST(ColorPicker::PickerShapeType); #endif // COLOR_PICKER_H diff --git a/scene/gui/container.cpp b/scene/gui/container.cpp index 2e6b798eea..dea69aae6b 100644 --- a/scene/gui/container.cpp +++ b/scene/gui/container.cpp @@ -159,16 +159,14 @@ void Container::_notification(int p_what) { } } -String Container::get_configuration_warning() const { - String warning = Control::get_configuration_warning(); +TypedArray<String> Container::get_configuration_warnings() const { + TypedArray<String> warnings = Control::get_configuration_warnings(); if (get_class() == "Container" && get_script().is_null()) { - if (!warning.is_empty()) { - warning += "\n\n"; - } - warning += TTR("Container by itself serves no purpose unless a script configures its children placement behavior.\nIf you don't intend to add a script, use a plain Control node instead."); + warnings.push_back(TTR("Container by itself serves no purpose unless a script configures its children placement behavior.\nIf you don't intend to add a script, use a plain Control node instead.")); } - return warning; + + return warnings; } void Container::_bind_methods() { diff --git a/scene/gui/container.h b/scene/gui/container.h index a4f392a3ae..bce3085f0c 100644 --- a/scene/gui/container.h +++ b/scene/gui/container.h @@ -56,7 +56,7 @@ public: void fit_child_in_rect(Control *p_child, const Rect2 &p_rect); - virtual String get_configuration_warning() const override; + TypedArray<String> get_configuration_warnings() const override; Container(); }; diff --git a/scene/gui/control.cpp b/scene/gui/control.cpp index c13bce5e0c..5afe813ee0 100644 --- a/scene/gui/control.cpp +++ b/scene/gui/control.cpp @@ -136,11 +136,11 @@ bool Control::_edit_use_rect() const { return true; } -void Control::_edit_set_rotation(float p_rotation) { +void Control::_edit_set_rotation(real_t p_rotation) { set_rotation(p_rotation); } -float Control::_edit_get_rotation() const { +real_t Control::_edit_get_rotation() const { return get_rotation(); } @@ -168,6 +168,12 @@ Size2 Control::_edit_get_minimum_size() const { } #endif +void Control::accept_event() { + if (is_inside_tree()) { + get_viewport()->_gui_accept_event(); + } +} + void Control::set_custom_minimum_size(const Size2 &p_custom) { if (p_custom == data.custom_minimum_size) { return; @@ -290,15 +296,11 @@ void Control::_update_minimum_size() { } Size2 minsize = get_combined_minimum_size(); - if (minsize.x > data.size_cache.x || - minsize.y > data.size_cache.y) { - _size_changed(); - } - data.updating_last_minimum_size = false; if (minsize != data.last_minimum_size) { data.last_minimum_size = minsize; + _size_changed(); emit_signal(SceneStringNames::get_singleton()->minimum_size_changed); } } @@ -327,7 +329,6 @@ bool Control::_get(const StringName &p_name, Variant &r_ret) const { r_ret = data.color_override.has(name) ? Variant(data.color_override[name]) : Variant(); } else if (sname.begins_with("custom_constants/")) { String name = sname.get_slicec('/', 1); - r_ret = data.constant_override.has(name) ? Variant(data.constant_override[name]) : Variant(); } else { return false; @@ -350,72 +351,72 @@ void Control::_get_property_list(List<PropertyInfo> *p_list) const { List<StringName> names; theme->get_icon_list(get_class_name(), &names); for (List<StringName>::Element *E = names.front(); E; E = E->next()) { - uint32_t hint = PROPERTY_USAGE_EDITOR | PROPERTY_USAGE_CHECKABLE; + uint32_t usage = PROPERTY_USAGE_EDITOR | PROPERTY_USAGE_CHECKABLE; if (data.icon_override.has(E->get())) { - hint |= PROPERTY_USAGE_STORAGE | PROPERTY_USAGE_CHECKED; + usage |= PROPERTY_USAGE_STORAGE | PROPERTY_USAGE_CHECKED; } - p_list->push_back(PropertyInfo(Variant::OBJECT, "custom_icons/" + E->get(), PROPERTY_HINT_RESOURCE_TYPE, "Texture2D", hint)); + p_list->push_back(PropertyInfo(Variant::OBJECT, "custom_icons/" + E->get(), PROPERTY_HINT_RESOURCE_TYPE, "Texture2D", usage)); } } { List<StringName> names; theme->get_stylebox_list(get_class_name(), &names); for (List<StringName>::Element *E = names.front(); E; E = E->next()) { - uint32_t hint = PROPERTY_USAGE_EDITOR | PROPERTY_USAGE_CHECKABLE; + uint32_t usage = PROPERTY_USAGE_EDITOR | PROPERTY_USAGE_CHECKABLE; if (data.style_override.has(E->get())) { - hint |= PROPERTY_USAGE_STORAGE | PROPERTY_USAGE_CHECKED; + usage |= PROPERTY_USAGE_STORAGE | PROPERTY_USAGE_CHECKED; } - p_list->push_back(PropertyInfo(Variant::OBJECT, "custom_styles/" + E->get(), PROPERTY_HINT_RESOURCE_TYPE, "StyleBox", hint)); + p_list->push_back(PropertyInfo(Variant::OBJECT, "custom_styles/" + E->get(), PROPERTY_HINT_RESOURCE_TYPE, "StyleBox", usage)); } } { List<StringName> names; theme->get_font_list(get_class_name(), &names); for (List<StringName>::Element *E = names.front(); E; E = E->next()) { - uint32_t hint = PROPERTY_USAGE_EDITOR | PROPERTY_USAGE_CHECKABLE; + uint32_t usage = PROPERTY_USAGE_EDITOR | PROPERTY_USAGE_CHECKABLE; if (data.font_override.has(E->get())) { - hint |= PROPERTY_USAGE_STORAGE | PROPERTY_USAGE_CHECKED; + usage |= PROPERTY_USAGE_STORAGE | PROPERTY_USAGE_CHECKED; } - p_list->push_back(PropertyInfo(Variant::OBJECT, "custom_fonts/" + E->get(), PROPERTY_HINT_RESOURCE_TYPE, "Font", hint)); + p_list->push_back(PropertyInfo(Variant::OBJECT, "custom_fonts/" + E->get(), PROPERTY_HINT_RESOURCE_TYPE, "Font", usage)); } } { List<StringName> names; theme->get_font_size_list(get_class_name(), &names); for (List<StringName>::Element *E = names.front(); E; E = E->next()) { - uint32_t hint = PROPERTY_USAGE_EDITOR | PROPERTY_USAGE_CHECKABLE; + uint32_t usage = PROPERTY_USAGE_EDITOR | PROPERTY_USAGE_CHECKABLE; if (data.font_size_override.has(E->get())) { - hint |= PROPERTY_USAGE_STORAGE | PROPERTY_USAGE_CHECKED; + usage |= PROPERTY_USAGE_STORAGE | PROPERTY_USAGE_CHECKED; } - p_list->push_back(PropertyInfo(Variant::INT, "custom_font_sizes/" + E->get(), PROPERTY_HINT_NONE, "", hint)); + p_list->push_back(PropertyInfo(Variant::INT, "custom_font_sizes/" + E->get(), PROPERTY_HINT_NONE, "", usage)); } } { List<StringName> names; theme->get_color_list(get_class_name(), &names); for (List<StringName>::Element *E = names.front(); E; E = E->next()) { - uint32_t hint = PROPERTY_USAGE_EDITOR | PROPERTY_USAGE_CHECKABLE; + uint32_t usage = PROPERTY_USAGE_EDITOR | PROPERTY_USAGE_CHECKABLE; if (data.color_override.has(E->get())) { - hint |= PROPERTY_USAGE_STORAGE | PROPERTY_USAGE_CHECKED; + usage |= PROPERTY_USAGE_STORAGE | PROPERTY_USAGE_CHECKED; } - p_list->push_back(PropertyInfo(Variant::COLOR, "custom_colors/" + E->get(), PROPERTY_HINT_NONE, "", hint)); + p_list->push_back(PropertyInfo(Variant::COLOR, "custom_colors/" + E->get(), PROPERTY_HINT_NONE, "", usage)); } } { List<StringName> names; theme->get_constant_list(get_class_name(), &names); for (List<StringName>::Element *E = names.front(); E; E = E->next()) { - uint32_t hint = PROPERTY_USAGE_EDITOR | PROPERTY_USAGE_CHECKABLE; + uint32_t usage = PROPERTY_USAGE_EDITOR | PROPERTY_USAGE_CHECKABLE; if (data.constant_override.has(E->get())) { - hint |= PROPERTY_USAGE_STORAGE | PROPERTY_USAGE_CHECKED; + usage |= PROPERTY_USAGE_STORAGE | PROPERTY_USAGE_CHECKED; } - p_list->push_back(PropertyInfo(Variant::INT, "custom_constants/" + E->get(), PROPERTY_HINT_RANGE, "-16384,16384", hint)); + p_list->push_back(PropertyInfo(Variant::INT, "custom_constants/" + E->get(), PROPERTY_HINT_RANGE, "-16384,16384", usage)); } } } @@ -514,7 +515,9 @@ void Control::_notification(int p_notification) { get_viewport()->_gui_remove_control(this); } break; case NOTIFICATION_READY: { +#ifdef DEBUG_ENABLED connect("ready", callable_mp(this, &Control::_clear_size_warning), varray(), CONNECT_DEFERRED | CONNECT_ONESHOT); +#endif } break; case NOTIFICATION_ENTER_CANVAS: { @@ -770,32 +773,27 @@ Size2 Control::get_minimum_size() const { } template <class T> -bool Control::_find_theme_item(Control *p_theme_owner, Window *p_theme_owner_window, T &r_ret, T (Theme::*get_func)(const StringName &, const StringName &) const, bool (Theme::*has_func)(const StringName &, const StringName &) const, const StringName &p_name, const StringName &p_node_type) { - // try with custom themes +T Control::get_theme_item_in_types(Control *p_theme_owner, Window *p_theme_owner_window, Theme::DataType p_data_type, const StringName &p_name, List<StringName> p_theme_types) { + ERR_FAIL_COND_V_MSG(p_theme_types.size() == 0, T(), "At least one theme type must be specified."); + + // First, look through each control or window node in the branch, until no valid parent can be found. + // For each control iterate through its inheritance chain and see if p_name exists in any of them. Control *theme_owner = p_theme_owner; Window *theme_owner_window = p_theme_owner_window; while (theme_owner || theme_owner_window) { - StringName class_name = p_node_type; - - while (class_name != StringName()) { - if (theme_owner && (theme_owner->data.theme.operator->()->*has_func)(p_name, class_name)) { - r_ret = (theme_owner->data.theme.operator->()->*get_func)(p_name, class_name); - return true; + for (List<StringName>::Element *E = p_theme_types.front(); E; E = E->next()) { + if (theme_owner && theme_owner->data.theme->has_theme_item(p_data_type, p_name, E->get())) { + return theme_owner->data.theme->get_theme_item(p_data_type, p_name, E->get()); } - if (theme_owner_window && (theme_owner_window->theme.operator->()->*has_func)(p_name, class_name)) { - r_ret = (theme_owner_window->theme.operator->()->*get_func)(p_name, class_name); - return true; + if (theme_owner_window && theme_owner_window->theme->has_theme_item(p_data_type, p_name, E->get())) { + return theme_owner_window->theme->get_theme_item(p_data_type, p_name, E->get()); } - - class_name = ClassDB::get_parent_class_nocheck(class_name); } Node *parent = theme_owner ? theme_owner->get_parent() : theme_owner_window->get_parent(); - Control *parent_c = Object::cast_to<Control>(parent); - if (parent_c) { theme_owner = parent_c->data.theme_owner; theme_owner_window = parent_c->data.theme_owner_window; @@ -810,33 +808,47 @@ bool Control::_find_theme_item(Control *p_theme_owner, Window *p_theme_owner_win } } } - return false; + + // Secondly, check the project-defined Theme resource. + if (Theme::get_project_default().is_valid()) { + for (List<StringName>::Element *E = p_theme_types.front(); E; E = E->next()) { + if (Theme::get_project_default()->has_theme_item(p_data_type, p_name, E->get())) { + return Theme::get_project_default()->get_theme_item(p_data_type, p_name, E->get()); + } + } + } + + // Lastly, fall back on the items defined in the default Theme, if they exist. + for (List<StringName>::Element *E = p_theme_types.front(); E; E = E->next()) { + if (Theme::get_default()->has_theme_item(p_data_type, p_name, E->get())) { + return Theme::get_default()->get_theme_item(p_data_type, p_name, E->get()); + } + } + // If they don't exist, use any type to return the default/empty value. + return Theme::get_default()->get_theme_item(p_data_type, p_name, p_theme_types[0]); } -bool Control::_has_theme_item(Control *p_theme_owner, Window *p_theme_owner_window, bool (Theme::*has_func)(const StringName &, const StringName &) const, const StringName &p_name, const StringName &p_node_type) { - // try with custom themes +bool Control::has_theme_item_in_types(Control *p_theme_owner, Window *p_theme_owner_window, Theme::DataType p_data_type, const StringName &p_name, List<StringName> p_theme_types) { + ERR_FAIL_COND_V_MSG(p_theme_types.size() == 0, false, "At least one theme type must be specified."); + + // First, look through each control or window node in the branch, until no valid parent can be found. + // For each control iterate through its inheritance chain and see if p_name exists in any of them. Control *theme_owner = p_theme_owner; Window *theme_owner_window = p_theme_owner_window; while (theme_owner || theme_owner_window) { - StringName class_name = p_node_type; - - while (class_name != StringName()) { - if (theme_owner && (theme_owner->data.theme.operator->()->*has_func)(p_name, class_name)) { + for (List<StringName>::Element *E = p_theme_types.front(); E; E = E->next()) { + if (theme_owner && theme_owner->data.theme->has_theme_item(p_data_type, p_name, E->get())) { return true; } - if (theme_owner_window && (theme_owner_window->theme.operator->()->*has_func)(p_name, class_name)) { + if (theme_owner_window && theme_owner_window->theme->has_theme_item(p_data_type, p_name, E->get())) { return true; } - - class_name = ClassDB::get_parent_class_nocheck(class_name); } Node *parent = theme_owner ? theme_owner->get_parent() : theme_owner_window->get_parent(); - Control *parent_c = Object::cast_to<Control>(parent); - if (parent_c) { theme_owner = parent_c->data.theme_owner; theme_owner_window = parent_c->data.theme_owner_window; @@ -851,179 +863,112 @@ bool Control::_has_theme_item(Control *p_theme_owner, Window *p_theme_owner_wind } } } - return false; -} -Ref<Texture2D> Control::get_theme_icon(const StringName &p_name, const StringName &p_node_type) const { - if (p_node_type == StringName() || p_node_type == get_class_name()) { - const Ref<Texture2D> *tex = data.icon_override.getptr(p_name); - if (tex) { - return *tex; + // Secondly, check the project-defined Theme resource. + if (Theme::get_project_default().is_valid()) { + for (List<StringName>::Element *E = p_theme_types.front(); E; E = E->next()) { + if (Theme::get_project_default()->has_theme_item(p_data_type, p_name, E->get())) { + return true; + } } } - StringName type = p_node_type ? p_node_type : get_class_name(); - - return get_icons(data.theme_owner, data.theme_owner_window, p_name, type); + // Lastly, fall back on the items defined in the default Theme, if they exist. + for (List<StringName>::Element *E = p_theme_types.front(); E; E = E->next()) { + if (Theme::get_default()->has_theme_item(p_data_type, p_name, E->get())) { + return true; + } + } + return false; } -Ref<Texture2D> Control::get_icons(Control *p_theme_owner, Window *p_theme_owner_window, const StringName &p_name, const StringName &p_node_type) { - Ref<Texture2D> icon; - - if (_find_theme_item(p_theme_owner, p_theme_owner_window, icon, &Theme::get_icon, &Theme::has_icon, p_name, p_node_type)) { - return icon; +void Control::_get_theme_type_dependencies(const StringName &p_theme_type, List<StringName> *p_list) const { + if (p_theme_type == StringName() || p_theme_type == get_class_name() || p_theme_type == data.theme_custom_type) { + if (data.theme_custom_type != StringName()) { + p_list->push_back(data.theme_custom_type); + } + Theme::get_type_dependencies(get_class_name(), p_list); + } else { + Theme::get_type_dependencies(p_theme_type, p_list); } +} - if (Theme::get_project_default().is_valid()) { - if (Theme::get_project_default()->has_icon(p_name, p_node_type)) { - return Theme::get_project_default()->get_icon(p_name, p_node_type); +Ref<Texture2D> Control::get_theme_icon(const StringName &p_name, const StringName &p_theme_type) const { + if (p_theme_type == StringName() || p_theme_type == get_class_name() || p_theme_type == data.theme_custom_type) { + const Ref<Texture2D> *tex = data.icon_override.getptr(p_name); + if (tex) { + return *tex; } } - return Theme::get_default()->get_icon(p_name, p_node_type); + List<StringName> theme_types; + _get_theme_type_dependencies(p_theme_type, &theme_types); + return get_theme_item_in_types<Ref<Texture2D>>(data.theme_owner, data.theme_owner_window, Theme::DATA_TYPE_ICON, p_name, theme_types); } -Ref<StyleBox> Control::get_theme_stylebox(const StringName &p_name, const StringName &p_node_type) const { - if (p_node_type == StringName() || p_node_type == get_class_name()) { +Ref<StyleBox> Control::get_theme_stylebox(const StringName &p_name, const StringName &p_theme_type) const { + if (p_theme_type == StringName() || p_theme_type == get_class_name() || p_theme_type == data.theme_custom_type) { const Ref<StyleBox> *style = data.style_override.getptr(p_name); if (style) { return *style; } } - StringName type = p_node_type ? p_node_type : get_class_name(); - - return get_styleboxs(data.theme_owner, data.theme_owner_window, p_name, type); -} - -Ref<StyleBox> Control::get_styleboxs(Control *p_theme_owner, Window *p_theme_owner_window, const StringName &p_name, const StringName &p_node_type) { - Ref<StyleBox> stylebox; - - if (_find_theme_item(p_theme_owner, p_theme_owner_window, stylebox, &Theme::get_stylebox, &Theme::has_stylebox, p_name, p_node_type)) { - return stylebox; - } - - if (Theme::get_project_default().is_valid()) { - if (Theme::get_project_default()->has_stylebox(p_name, p_node_type)) { - return Theme::get_project_default()->get_stylebox(p_name, p_node_type); - } - } - - return Theme::get_default()->get_stylebox(p_name, p_node_type); + List<StringName> theme_types; + _get_theme_type_dependencies(p_theme_type, &theme_types); + return get_theme_item_in_types<Ref<StyleBox>>(data.theme_owner, data.theme_owner_window, Theme::DATA_TYPE_STYLEBOX, p_name, theme_types); } -Ref<Font> Control::get_theme_font(const StringName &p_name, const StringName &p_node_type) const { - if (p_node_type == StringName() || p_node_type == get_class_name()) { +Ref<Font> Control::get_theme_font(const StringName &p_name, const StringName &p_theme_type) const { + if (p_theme_type == StringName() || p_theme_type == get_class_name() || p_theme_type == data.theme_custom_type) { const Ref<Font> *font = data.font_override.getptr(p_name); if (font) { return *font; } } - StringName type = p_node_type ? p_node_type : get_class_name(); - - return get_fonts(data.theme_owner, data.theme_owner_window, p_name, type); + List<StringName> theme_types; + _get_theme_type_dependencies(p_theme_type, &theme_types); + return get_theme_item_in_types<Ref<Font>>(data.theme_owner, data.theme_owner_window, Theme::DATA_TYPE_FONT, p_name, theme_types); } -int Control::get_theme_font_size(const StringName &p_name, const StringName &p_node_type) const { - if (p_node_type == StringName() || p_node_type == get_class_name()) { +int Control::get_theme_font_size(const StringName &p_name, const StringName &p_theme_type) const { + if (p_theme_type == StringName() || p_theme_type == get_class_name() || p_theme_type == data.theme_custom_type) { const int *font_size = data.font_size_override.getptr(p_name); if (font_size) { return *font_size; } } - StringName type = p_node_type ? p_node_type : get_class_name(); - - return get_font_sizes(data.theme_owner, data.theme_owner_window, p_name, type); + List<StringName> theme_types; + _get_theme_type_dependencies(p_theme_type, &theme_types); + return get_theme_item_in_types<int>(data.theme_owner, data.theme_owner_window, Theme::DATA_TYPE_FONT_SIZE, p_name, theme_types); } -Ref<Font> Control::get_fonts(Control *p_theme_owner, Window *p_theme_owner_window, const StringName &p_name, const StringName &p_node_type) { - Ref<Font> font; - - if (_find_theme_item(p_theme_owner, p_theme_owner_window, font, &Theme::get_font, &Theme::has_font, p_name, p_node_type)) { - return font; - } - - if (Theme::get_project_default().is_valid()) { - if (Theme::get_project_default()->has_font(p_name, p_node_type)) { - return Theme::get_project_default()->get_font(p_name, p_node_type); - } - } - - return Theme::get_default()->get_font(p_name, p_node_type); -} - -int Control::get_font_sizes(Control *p_theme_owner, Window *p_theme_owner_window, const StringName &p_name, const StringName &p_node_type) { - int font_size; - - if (_find_theme_item(p_theme_owner, p_theme_owner_window, font_size, &Theme::get_font_size, &Theme::has_font_size, p_name, p_node_type)) { - return font_size; - } - - if (Theme::get_project_default().is_valid()) { - if (Theme::get_project_default()->has_font_size(p_name, p_node_type)) { - return Theme::get_project_default()->get_font_size(p_name, p_node_type); - } - } - - return Theme::get_default()->get_font_size(p_name, p_node_type); -} - -Color Control::get_theme_color(const StringName &p_name, const StringName &p_node_type) const { - if (p_node_type == StringName() || p_node_type == get_class_name()) { +Color Control::get_theme_color(const StringName &p_name, const StringName &p_theme_type) const { + if (p_theme_type == StringName() || p_theme_type == get_class_name() || p_theme_type == data.theme_custom_type) { const Color *color = data.color_override.getptr(p_name); if (color) { return *color; } } - StringName type = p_node_type ? p_node_type : get_class_name(); - - return get_colors(data.theme_owner, data.theme_owner_window, p_name, type); -} - -Color Control::get_colors(Control *p_theme_owner, Window *p_theme_owner_window, const StringName &p_name, const StringName &p_node_type) { - Color color; - - if (_find_theme_item(p_theme_owner, p_theme_owner_window, color, &Theme::get_color, &Theme::has_color, p_name, p_node_type)) { - return color; - } - - if (Theme::get_project_default().is_valid()) { - if (Theme::get_project_default()->has_color(p_name, p_node_type)) { - return Theme::get_project_default()->get_color(p_name, p_node_type); - } - } - return Theme::get_default()->get_color(p_name, p_node_type); + List<StringName> theme_types; + _get_theme_type_dependencies(p_theme_type, &theme_types); + return get_theme_item_in_types<Color>(data.theme_owner, data.theme_owner_window, Theme::DATA_TYPE_COLOR, p_name, theme_types); } -int Control::get_theme_constant(const StringName &p_name, const StringName &p_node_type) const { - if (p_node_type == StringName() || p_node_type == get_class_name()) { +int Control::get_theme_constant(const StringName &p_name, const StringName &p_theme_type) const { + if (p_theme_type == StringName() || p_theme_type == get_class_name() || p_theme_type == data.theme_custom_type) { const int *constant = data.constant_override.getptr(p_name); if (constant) { return *constant; } } - StringName type = p_node_type ? p_node_type : get_class_name(); - - return get_constants(data.theme_owner, data.theme_owner_window, p_name, type); -} - -int Control::get_constants(Control *p_theme_owner, Window *p_theme_owner_window, const StringName &p_name, const StringName &p_node_type) { - int constant; - - if (_find_theme_item(p_theme_owner, p_theme_owner_window, constant, &Theme::get_constant, &Theme::has_constant, p_name, p_node_type)) { - return constant; - } - - if (Theme::get_project_default().is_valid()) { - if (Theme::get_project_default()->has_constant(p_name, p_node_type)) { - return Theme::get_project_default()->get_constant(p_name, p_node_type); - } - } - return Theme::get_default()->get_constant(p_name, p_node_type); + List<StringName> theme_types; + _get_theme_type_dependencies(p_theme_type, &theme_types); + return get_theme_item_in_types<int>(data.theme_owner, data.theme_owner_window, Theme::DATA_TYPE_CONSTANT, p_name, theme_types); } bool Control::has_theme_icon_override(const StringName &p_name) const { @@ -1056,154 +1001,76 @@ bool Control::has_theme_constant_override(const StringName &p_name) const { return constant != nullptr; } -bool Control::has_theme_icon(const StringName &p_name, const StringName &p_node_type) const { - if (p_node_type == StringName() || p_node_type == get_class_name()) { +bool Control::has_theme_icon(const StringName &p_name, const StringName &p_theme_type) const { + if (p_theme_type == StringName() || p_theme_type == get_class_name() || p_theme_type == data.theme_custom_type) { if (has_theme_icon_override(p_name)) { return true; } } - StringName type = p_node_type ? p_node_type : get_class_name(); - - return has_icons(data.theme_owner, data.theme_owner_window, p_name, type); -} - -bool Control::has_icons(Control *p_theme_owner, Window *p_theme_owner_window, const StringName &p_name, const StringName &p_node_type) { - if (_has_theme_item(p_theme_owner, p_theme_owner_window, &Theme::has_icon, p_name, p_node_type)) { - return true; - } - - if (Theme::get_project_default().is_valid()) { - if (Theme::get_project_default()->has_color(p_name, p_node_type)) { - return true; - } - } - return Theme::get_default()->has_icon(p_name, p_node_type); + List<StringName> theme_types; + _get_theme_type_dependencies(p_theme_type, &theme_types); + return has_theme_item_in_types(data.theme_owner, data.theme_owner_window, Theme::DATA_TYPE_ICON, p_name, theme_types); } -bool Control::has_theme_stylebox(const StringName &p_name, const StringName &p_node_type) const { - if (p_node_type == StringName() || p_node_type == get_class_name()) { +bool Control::has_theme_stylebox(const StringName &p_name, const StringName &p_theme_type) const { + if (p_theme_type == StringName() || p_theme_type == get_class_name() || p_theme_type == data.theme_custom_type) { if (has_theme_stylebox_override(p_name)) { return true; } } - StringName type = p_node_type ? p_node_type : get_class_name(); - - return has_styleboxs(data.theme_owner, data.theme_owner_window, p_name, type); + List<StringName> theme_types; + _get_theme_type_dependencies(p_theme_type, &theme_types); + return has_theme_item_in_types(data.theme_owner, data.theme_owner_window, Theme::DATA_TYPE_STYLEBOX, p_name, theme_types); } -bool Control::has_styleboxs(Control *p_theme_owner, Window *p_theme_owner_window, const StringName &p_name, const StringName &p_node_type) { - if (_has_theme_item(p_theme_owner, p_theme_owner_window, &Theme::has_stylebox, p_name, p_node_type)) { - return true; - } - - if (Theme::get_project_default().is_valid()) { - if (Theme::get_project_default()->has_stylebox(p_name, p_node_type)) { - return true; - } - } - return Theme::get_default()->has_stylebox(p_name, p_node_type); -} - -bool Control::has_theme_font(const StringName &p_name, const StringName &p_node_type) const { - if (p_node_type == StringName() || p_node_type == get_class_name()) { +bool Control::has_theme_font(const StringName &p_name, const StringName &p_theme_type) const { + if (p_theme_type == StringName() || p_theme_type == get_class_name() || p_theme_type == data.theme_custom_type) { if (has_theme_font_override(p_name)) { return true; } } - StringName type = p_node_type ? p_node_type : get_class_name(); - - return has_fonts(data.theme_owner, data.theme_owner_window, p_name, type); + List<StringName> theme_types; + _get_theme_type_dependencies(p_theme_type, &theme_types); + return has_theme_item_in_types(data.theme_owner, data.theme_owner_window, Theme::DATA_TYPE_FONT, p_name, theme_types); } -bool Control::has_fonts(Control *p_theme_owner, Window *p_theme_owner_window, const StringName &p_name, const StringName &p_node_type) { - if (_has_theme_item(p_theme_owner, p_theme_owner_window, &Theme::has_font, p_name, p_node_type)) { - return true; - } - - if (Theme::get_project_default().is_valid()) { - if (Theme::get_project_default()->has_font(p_name, p_node_type)) { - return true; - } - } - return Theme::get_default()->has_font(p_name, p_node_type); -} - -bool Control::has_theme_font_size(const StringName &p_name, const StringName &p_node_type) const { - if (p_node_type == StringName() || p_node_type == get_class_name()) { +bool Control::has_theme_font_size(const StringName &p_name, const StringName &p_theme_type) const { + if (p_theme_type == StringName() || p_theme_type == get_class_name() || p_theme_type == data.theme_custom_type) { if (has_theme_font_size_override(p_name)) { return true; } } - StringName type = p_node_type ? p_node_type : get_class_name(); - - return has_font_sizes(data.theme_owner, data.theme_owner_window, p_name, type); + List<StringName> theme_types; + _get_theme_type_dependencies(p_theme_type, &theme_types); + return has_theme_item_in_types(data.theme_owner, data.theme_owner_window, Theme::DATA_TYPE_FONT_SIZE, p_name, theme_types); } -bool Control::has_font_sizes(Control *p_theme_owner, Window *p_theme_owner_window, const StringName &p_name, const StringName &p_node_type) { - if (_has_theme_item(p_theme_owner, p_theme_owner_window, &Theme::has_font_size, p_name, p_node_type)) { - return true; - } - - if (Theme::get_project_default().is_valid()) { - if (Theme::get_project_default()->has_font_size(p_name, p_node_type)) { - return true; - } - } - return Theme::get_default()->has_font_size(p_name, p_node_type); -} - -bool Control::has_theme_color(const StringName &p_name, const StringName &p_node_type) const { - if (p_node_type == StringName() || p_node_type == get_class_name()) { +bool Control::has_theme_color(const StringName &p_name, const StringName &p_theme_type) const { + if (p_theme_type == StringName() || p_theme_type == get_class_name() || p_theme_type == data.theme_custom_type) { if (has_theme_color_override(p_name)) { return true; } } - StringName type = p_node_type ? p_node_type : get_class_name(); - - return has_colors(data.theme_owner, data.theme_owner_window, p_name, type); -} - -bool Control::has_colors(Control *p_theme_owner, Window *p_theme_owner_window, const StringName &p_name, const StringName &p_node_type) { - if (_has_theme_item(p_theme_owner, p_theme_owner_window, &Theme::has_color, p_name, p_node_type)) { - return true; - } - - if (Theme::get_project_default().is_valid()) { - if (Theme::get_project_default()->has_color(p_name, p_node_type)) { - return true; - } - } - return Theme::get_default()->has_color(p_name, p_node_type); + List<StringName> theme_types; + _get_theme_type_dependencies(p_theme_type, &theme_types); + return has_theme_item_in_types(data.theme_owner, data.theme_owner_window, Theme::DATA_TYPE_COLOR, p_name, theme_types); } -bool Control::has_theme_constant(const StringName &p_name, const StringName &p_node_type) const { - if (p_node_type == StringName() || p_node_type == get_class_name()) { +bool Control::has_theme_constant(const StringName &p_name, const StringName &p_theme_type) const { + if (p_theme_type == StringName() || p_theme_type == get_class_name() || p_theme_type == data.theme_custom_type) { if (has_theme_constant_override(p_name)) { return true; } } - StringName type = p_node_type ? p_node_type : get_class_name(); - - return has_constants(data.theme_owner, data.theme_owner_window, p_name, p_node_type); -} - -bool Control::has_constants(Control *p_theme_owner, Window *p_theme_owner_window, const StringName &p_name, const StringName &p_node_type) { - if (_has_theme_item(p_theme_owner, p_theme_owner_window, &Theme::has_constant, p_name, p_node_type)) { - return true; - } - - if (Theme::get_project_default().is_valid()) { - if (Theme::get_project_default()->has_constant(p_name, p_node_type)) { - return true; - } - } - return Theme::get_default()->has_constant(p_name, p_node_type); + List<StringName> theme_types; + _get_theme_type_dependencies(p_theme_type, &theme_types); + return has_theme_item_in_types(data.theme_owner, data.theme_owner_window, Theme::DATA_TYPE_CONSTANT, p_name, theme_types); } Rect2 Control::get_parent_anchorable_rect() const { @@ -1238,10 +1105,10 @@ Size2 Control::get_parent_area_size() const { void Control::_size_changed() { Rect2 parent_rect = get_parent_anchorable_rect(); - float edge_pos[4]; + real_t edge_pos[4]; for (int i = 0; i < 4; i++) { - float area = parent_rect.size[i & 1]; + real_t area = parent_rect.size[i & 1]; edge_pos[i] = data.offset[i] + (data.anchor[i] * area); } @@ -1295,13 +1162,13 @@ void Control::_size_changed() { } } -void Control::set_anchor(Side p_side, float p_anchor, bool p_keep_offset, bool p_push_opposite_anchor) { +void Control::set_anchor(Side p_side, real_t p_anchor, bool p_keep_offset, bool p_push_opposite_anchor) { ERR_FAIL_INDEX((int)p_side, 4); Rect2 parent_rect = get_parent_anchorable_rect(); - float parent_range = (p_side == SIDE_LEFT || p_side == SIDE_RIGHT) ? parent_rect.size.x : parent_rect.size.y; - float previous_pos = data.offset[p_side] + data.anchor[p_side] * parent_range; - float previous_opposite_pos = data.offset[(p_side + 2) % 4] + data.anchor[(p_side + 2) % 4] * parent_range; + real_t parent_range = (p_side == SIDE_LEFT || p_side == SIDE_RIGHT) ? parent_rect.size.x : parent_rect.size.y; + real_t previous_pos = data.offset[p_side] + data.anchor[p_side] * parent_range; + real_t previous_opposite_pos = data.offset[(p_side + 2) % 4] + data.anchor[(p_side + 2) % 4] * parent_range; data.anchor[p_side] = p_anchor; @@ -1327,11 +1194,11 @@ void Control::set_anchor(Side p_side, float p_anchor, bool p_keep_offset, bool p update(); } -void Control::_set_anchor(Side p_side, float p_anchor) { +void Control::_set_anchor(Side p_side, real_t p_anchor) { set_anchor(p_side, p_anchor); } -void Control::set_anchor_and_offset(Side p_side, float p_anchor, float p_pos, bool p_push_opposite_anchor) { +void Control::set_anchor_and_offset(Side p_side, real_t p_anchor, real_t p_pos, bool p_push_opposite_anchor) { set_anchor(p_side, p_anchor, false, p_push_opposite_anchor); set_offset(p_side, p_pos); } @@ -1468,7 +1335,7 @@ void Control::set_offsets_preset(LayoutPreset p_preset, LayoutPresetMode p_resiz Rect2 parent_rect = get_parent_anchorable_rect(); - float x = parent_rect.size.x; + real_t x = parent_rect.size.x; if (is_layout_rtl()) { x = parent_rect.size.x - x - new_size.x; } @@ -1592,13 +1459,13 @@ void Control::set_anchors_and_offsets_preset(LayoutPreset p_preset, LayoutPreset set_offsets_preset(p_preset, p_resize_mode, p_margin); } -float Control::get_anchor(Side p_side) const { +real_t Control::get_anchor(Side p_side) const { ERR_FAIL_INDEX_V(int(p_side), 4, 0.0); return data.anchor[p_side]; } -void Control::set_offset(Side p_side, float p_value) { +void Control::set_offset(Side p_side, real_t p_value) { ERR_FAIL_INDEX((int)p_side, 4); data.offset[p_side] = p_value; @@ -1617,7 +1484,7 @@ void Control::set_end(const Size2 &p_point) { _size_changed(); } -float Control::get_offset(Side p_side) const { +real_t Control::get_offset(Side p_side) const { ERR_FAIL_INDEX_V((int)p_side, 4, 0); return data.offset[p_side]; @@ -1660,12 +1527,12 @@ void Control::set_global_position(const Point2 &p_point, bool p_keep_offsets) { set_position(inv.xform(p_point), p_keep_offsets); } -void Control::_compute_anchors(Rect2 p_rect, const float p_offsets[4], float (&r_anchors)[4]) { +void Control::_compute_anchors(Rect2 p_rect, const real_t p_offsets[4], real_t (&r_anchors)[4]) { Size2 parent_rect_size = get_parent_anchorable_rect().size; ERR_FAIL_COND(parent_rect_size.x == 0.0); ERR_FAIL_COND(parent_rect_size.y == 0.0); - float x = p_rect.position.x; + real_t x = p_rect.position.x; if (is_layout_rtl()) { x = parent_rect_size.x - x - p_rect.size.x; } @@ -1675,10 +1542,10 @@ void Control::_compute_anchors(Rect2 p_rect, const float p_offsets[4], float (&r r_anchors[3] = (p_rect.position.y + p_rect.size.y - p_offsets[3]) / parent_rect_size.y; } -void Control::_compute_offsets(Rect2 p_rect, const float p_anchors[4], float (&r_offsets)[4]) { +void Control::_compute_offsets(Rect2 p_rect, const real_t p_anchors[4], real_t (&r_offsets)[4]) { Size2 parent_rect_size = get_parent_anchorable_rect().size; - float x = p_rect.position.x; + real_t x = p_rect.position.x; if (is_layout_rtl()) { x = parent_rect_size.x - x - p_rect.size.x; } @@ -1714,8 +1581,8 @@ void Control::set_rect(const Rect2 &p_rect) { void Control::_set_size(const Size2 &p_size) { #ifdef DEBUG_ENABLED - if (data.size_warning) { - WARN_PRINT("Adjusting the size of Control nodes before they are fully initialized is unreliable. Consider deferring it with set_deferred()."); + if (data.size_warning && (data.anchor[SIDE_LEFT] != data.anchor[SIDE_RIGHT] || data.anchor[SIDE_TOP] != data.anchor[SIDE_BOTTOM])) { + WARN_PRINT("Nodes with non-equal opposite anchors will have their size overriden after _ready(). \nIf you want to set size, change the anchors or consider using set_deferred()."); } #endif set_size(p_size); @@ -1780,53 +1647,38 @@ Rect2 Control::get_anchorable_rect() const { } void Control::add_theme_icon_override(const StringName &p_name, const Ref<Texture2D> &p_icon) { + ERR_FAIL_COND(!p_icon.is_valid()); + if (data.icon_override.has(p_name)) { data.icon_override[p_name]->disconnect("changed", callable_mp(this, &Control::_override_changed)); } - // clear if "null" is passed instead of an icon - if (p_icon.is_null()) { - data.icon_override.erase(p_name); - } else { - data.icon_override[p_name] = p_icon; - if (data.icon_override[p_name].is_valid()) { - data.icon_override[p_name]->connect("changed", callable_mp(this, &Control::_override_changed), Vector<Variant>(), CONNECT_REFERENCE_COUNTED); - } - } + data.icon_override[p_name] = p_icon; + data.icon_override[p_name]->connect("changed", callable_mp(this, &Control::_override_changed), Vector<Variant>(), CONNECT_REFERENCE_COUNTED); notification(NOTIFICATION_THEME_CHANGED); } void Control::add_theme_style_override(const StringName &p_name, const Ref<StyleBox> &p_style) { + ERR_FAIL_COND(!p_style.is_valid()); + if (data.style_override.has(p_name)) { data.style_override[p_name]->disconnect("changed", callable_mp(this, &Control::_override_changed)); } - // clear if "null" is passed instead of a style - if (p_style.is_null()) { - data.style_override.erase(p_name); - } else { - data.style_override[p_name] = p_style; - if (data.style_override[p_name].is_valid()) { - data.style_override[p_name]->connect("changed", callable_mp(this, &Control::_override_changed), Vector<Variant>(), CONNECT_REFERENCE_COUNTED); - } - } + data.style_override[p_name] = p_style; + data.style_override[p_name]->connect("changed", callable_mp(this, &Control::_override_changed), Vector<Variant>(), CONNECT_REFERENCE_COUNTED); notification(NOTIFICATION_THEME_CHANGED); } void Control::add_theme_font_override(const StringName &p_name, const Ref<Font> &p_font) { + ERR_FAIL_COND(!p_font.is_valid()); + if (data.font_override.has(p_name)) { data.font_override[p_name]->disconnect("changed", callable_mp(this, &Control::_override_changed)); } - // clear if "null" is passed instead of a font - if (p_font.is_null()) { - data.font_override.erase(p_name); - } else { - data.font_override[p_name] = p_font; - if (data.font_override[p_name].is_valid()) { - data.font_override[p_name]->connect("changed", callable_mp(this, &Control::_override_changed), Vector<Variant>(), CONNECT_REFERENCE_COUNTED); - } - } + data.font_override[p_name] = p_font; + data.font_override[p_name]->connect("changed", callable_mp(this, &Control::_override_changed), Vector<Variant>(), CONNECT_REFERENCE_COUNTED); notification(NOTIFICATION_THEME_CHANGED); } @@ -1845,6 +1697,48 @@ void Control::add_theme_constant_override(const StringName &p_name, int p_consta notification(NOTIFICATION_THEME_CHANGED); } +void Control::remove_theme_icon_override(const StringName &p_name) { + if (data.icon_override.has(p_name)) { + data.icon_override[p_name]->disconnect("changed", callable_mp(this, &Control::_override_changed)); + } + + data.icon_override.erase(p_name); + notification(NOTIFICATION_THEME_CHANGED); +} + +void Control::remove_theme_style_override(const StringName &p_name) { + if (data.style_override.has(p_name)) { + data.style_override[p_name]->disconnect("changed", callable_mp(this, &Control::_override_changed)); + } + + data.style_override.erase(p_name); + notification(NOTIFICATION_THEME_CHANGED); +} + +void Control::remove_theme_font_override(const StringName &p_name) { + if (data.font_override.has(p_name)) { + data.font_override[p_name]->disconnect("changed", callable_mp(this, &Control::_override_changed)); + } + + data.font_override.erase(p_name); + notification(NOTIFICATION_THEME_CHANGED); +} + +void Control::remove_theme_font_size_override(const StringName &p_name) { + data.font_size_override.erase(p_name); + notification(NOTIFICATION_THEME_CHANGED); +} + +void Control::remove_theme_color_override(const StringName &p_name) { + data.color_override.erase(p_name); + notification(NOTIFICATION_THEME_CHANGED); +} + +void Control::remove_theme_constant_override(const StringName &p_name) { + data.constant_override.erase(p_name); + notification(NOTIFICATION_THEME_CHANGED); +} + void Control::set_focus_mode(FocusMode p_focus_mode) { ERR_FAIL_INDEX((int)p_focus_mode, 3); @@ -2149,19 +2043,22 @@ void Control::set_theme(const Ref<Theme> &p_theme) { } } -void Control::accept_event() { - if (is_inside_tree()) { - get_viewport()->_gui_accept_event(); - } -} - Ref<Theme> Control::get_theme() const { return data.theme; } +void Control::set_theme_custom_type(const StringName &p_theme_type) { + data.theme_custom_type = p_theme_type; + _propagate_theme_changed(this, data.theme_owner, data.theme_owner_window); +} + +StringName Control::get_theme_custom_type() const { + return data.theme_custom_type; +} + void Control::set_tooltip(const String &p_tooltip) { data.tooltip = p_tooltip; - update_configuration_warning(); + update_configuration_warnings(); } String Control::get_tooltip(const Point2 &p_pos) const { @@ -2257,7 +2154,7 @@ Control *Control::_get_focus_neighbor(Side p_side, int p_count) { return c; } - float dist = 1e7; + real_t dist = 1e7; Control *result = nullptr; Point2 points[4]; @@ -2278,10 +2175,10 @@ Control *Control::_get_focus_neighbor(Side p_side, int p_count) { Vector2 vdir = dir[p_side]; - float maxd = -1e7; + real_t maxd = -1e7; for (int i = 0; i < 4; i++) { - float d = vdir.dot(points[i]); + real_t d = vdir.dot(points[i]); if (d > maxd) { maxd = d; } @@ -2308,7 +2205,7 @@ Control *Control::_get_focus_neighbor(Side p_side, int p_count) { return result; } -void Control::_window_find_focus_neighbor(const Vector2 &p_dir, Node *p_at, const Point2 *p_points, float p_min, float &r_closest_dist, Control **r_closest) { +void Control::_window_find_focus_neighbor(const Vector2 &p_dir, Node *p_at, const Point2 *p_points, real_t p_min, real_t &r_closest_dist, Control **r_closest) { if (Object::cast_to<Viewport>(p_at)) { return; //bye } @@ -2325,10 +2222,10 @@ void Control::_window_find_focus_neighbor(const Vector2 &p_dir, Node *p_at, cons points[2] = xform.xform(c->get_size()); points[3] = xform.xform(Point2(0, c->get_size().y)); - float min = 1e7; + real_t min = 1e7; for (int i = 0; i < 4; i++) { - float d = p_dir.dot(points[i]); + real_t d = p_dir.dot(points[i]); if (d < min) { min = d; } @@ -2344,8 +2241,8 @@ void Control::_window_find_focus_neighbor(const Vector2 &p_dir, Node *p_at, cons Vector2 fb = points[(j + 1) % 4]; Vector2 pa, pb; - float d = Geometry2D::get_closest_points_between_segments(la, lb, fa, fb, pa, pb); - //float d = Geometry2D::get_closest_distance_between_segments(Vector3(la.x,la.y,0),Vector3(lb.x,lb.y,0),Vector3(fa.x,fa.y,0),Vector3(fb.x,fb.y,0)); + real_t d = Geometry2D::get_closest_points_between_segments(la, lb, fa, fb, pa, pb); + //real_t d = Geometry2D::get_closest_distance_between_segments(Vector3(la.x,la.y,0),Vector3(lb.x,lb.y,0),Vector3(fa.x,fa.y,0),Vector3(fb.x,fb.y,0)); if (d < r_closest_dist) { r_closest_dist = d; *r_closest = c; @@ -2385,7 +2282,7 @@ void Control::set_v_size_flags(int p_flags) { emit_signal(SceneStringNames::get_singleton()->size_flags_changed); } -void Control::set_stretch_ratio(float p_ratio) { +void Control::set_stretch_ratio(real_t p_ratio) { if (data.expand == p_ratio) { return; } @@ -2394,7 +2291,7 @@ void Control::set_stretch_ratio(float p_ratio) { emit_signal(SceneStringNames::get_singleton()->size_flags_changed); } -float Control::get_stretch_ratio() const { +real_t Control::get_stretch_ratio() const { return data.expand; } @@ -2446,7 +2343,7 @@ int Control::get_v_size_flags() const { void Control::set_mouse_filter(MouseFilter p_filter) { ERR_FAIL_INDEX(p_filter, 3); data.mouse_filter = p_filter; - update_configuration_warning(); + update_configuration_warnings(); } Control::MouseFilter Control::get_mouse_filter() const { @@ -2477,9 +2374,9 @@ bool Control::is_text_field() const { return false; } -Vector<Vector2i> Control::structured_text_parser(StructuredTextParser p_node_type, const Array &p_args, const String p_text) const { +Vector<Vector2i> Control::structured_text_parser(StructuredTextParser p_theme_type, const Array &p_args, const String p_text) const { Vector<Vector2i> ret; - switch (p_node_type) { + switch (p_theme_type) { case STRUCTURED_TEXT_URI: { int prev = 0; for (int i = 0; i < p_text.length(); i++) { @@ -2566,21 +2463,21 @@ Vector<Vector2i> Control::structured_text_parser(StructuredTextParser p_node_typ return ret; } -void Control::set_rotation(float p_radians) { +void Control::set_rotation(real_t p_radians) { data.rotation = p_radians; update(); _notify_transform(); } -float Control::get_rotation() const { +real_t Control::get_rotation() const { return data.rotation; } -void Control::set_rotation_degrees(float p_degrees) { +void Control::set_rotation_degrees(real_t p_degrees) { set_rotation(Math::deg2rad(p_degrees)); } -float Control::get_rotation_degrees() const { +real_t Control::get_rotation_degrees() const { return Math::rad2deg(get_rotation()); } @@ -2666,15 +2563,15 @@ void Control::get_argument_options(const StringName &p_function, int p_idx, List if (p_idx == 0) { List<StringName> sn; String pf = p_function; - if (pf == "add_color_override" || pf == "has_color" || pf == "has_color_override" || pf == "get_color") { + if (pf == "add_theme_color_override" || pf == "has_theme_color" || pf == "has_theme_color_override" || pf == "get_theme_color") { Theme::get_default()->get_color_list(get_class(), &sn); - } else if (pf == "add_style_override" || pf == "has_style" || pf == "has_style_override" || pf == "get_style") { + } else if (pf == "add_theme_style_override" || pf == "has_theme_style" || pf == "has_theme_style_override" || pf == "get_theme_style") { Theme::get_default()->get_stylebox_list(get_class(), &sn); - } else if (pf == "add_font_override" || pf == "has_font" || pf == "has_font_override" || pf == "get_font") { + } else if (pf == "add_theme_font_override" || pf == "has_theme_font" || pf == "has_theme_font_override" || pf == "get_theme_font") { Theme::get_default()->get_font_list(get_class(), &sn); - } else if (pf == "add_font_size_override" || pf == "has_font_size" || pf == "has_font_size_override" || pf == "get_font_size") { + } else if (pf == "add_theme_font_size_override" || pf == "has_theme_font_size" || pf == "has_theme_font_size_override" || pf == "get_theme_font_size") { Theme::get_default()->get_font_size_list(get_class(), &sn); - } else if (pf == "add_constant_override" || pf == "has_constant" || pf == "has_constant_override" || pf == "get_constant") { + } else if (pf == "add_theme_constant_override" || pf == "has_theme_constant" || pf == "has_theme_constant_override" || pf == "get_theme_constant") { Theme::get_default()->get_constant_list(get_class(), &sn); } @@ -2685,17 +2582,14 @@ void Control::get_argument_options(const StringName &p_function, int p_idx, List } } -String Control::get_configuration_warning() const { - String warning = CanvasItem::get_configuration_warning(); +TypedArray<String> Control::get_configuration_warnings() const { + TypedArray<String> warnings = Node::get_configuration_warnings(); if (data.mouse_filter == MOUSE_FILTER_IGNORE && data.tooltip != "") { - if (!warning.is_empty()) { - warning += "\n\n"; - } - warning += TTR("The Hint Tooltip won't be displayed as the control's Mouse Filter is set to \"Ignore\". To solve this, set the Mouse Filter to \"Stop\" or \"Pass\"."); + warnings.push_back(TTR("The Hint Tooltip won't be displayed as the control's Mouse Filter is set to \"Ignore\". To solve this, set the Mouse Filter to \"Stop\" or \"Pass\".")); } - return warning; + return warnings; } void Control::set_clip_contents(bool p_clip) { @@ -2792,6 +2686,9 @@ void Control::_bind_methods() { ClassDB::bind_method(D_METHOD("set_theme", "theme"), &Control::set_theme); ClassDB::bind_method(D_METHOD("get_theme"), &Control::get_theme); + ClassDB::bind_method(D_METHOD("set_theme_custom_type", "theme_type"), &Control::set_theme_custom_type); + ClassDB::bind_method(D_METHOD("get_theme_custom_type"), &Control::get_theme_custom_type); + ClassDB::bind_method(D_METHOD("add_theme_icon_override", "name", "texture"), &Control::add_theme_icon_override); ClassDB::bind_method(D_METHOD("add_theme_stylebox_override", "name", "stylebox"), &Control::add_theme_style_override); ClassDB::bind_method(D_METHOD("add_theme_font_override", "name", "font"), &Control::add_theme_font_override); @@ -2799,12 +2696,19 @@ void Control::_bind_methods() { ClassDB::bind_method(D_METHOD("add_theme_color_override", "name", "color"), &Control::add_theme_color_override); ClassDB::bind_method(D_METHOD("add_theme_constant_override", "name", "constant"), &Control::add_theme_constant_override); - ClassDB::bind_method(D_METHOD("get_theme_icon", "name", "node_type"), &Control::get_theme_icon, DEFVAL("")); - ClassDB::bind_method(D_METHOD("get_theme_stylebox", "name", "node_type"), &Control::get_theme_stylebox, DEFVAL("")); - ClassDB::bind_method(D_METHOD("get_theme_font", "name", "node_type"), &Control::get_theme_font, DEFVAL("")); - ClassDB::bind_method(D_METHOD("get_theme_font_size", "name", "node_type"), &Control::get_theme_font_size, DEFVAL("")); - ClassDB::bind_method(D_METHOD("get_theme_color", "name", "node_type"), &Control::get_theme_color, DEFVAL("")); - ClassDB::bind_method(D_METHOD("get_theme_constant", "name", "node_type"), &Control::get_theme_constant, DEFVAL("")); + ClassDB::bind_method(D_METHOD("remove_theme_icon_override", "name"), &Control::remove_theme_icon_override); + ClassDB::bind_method(D_METHOD("remove_theme_stylebox_override", "name"), &Control::remove_theme_style_override); + ClassDB::bind_method(D_METHOD("remove_theme_font_override", "name"), &Control::remove_theme_font_override); + ClassDB::bind_method(D_METHOD("remove_theme_font_size_override", "name"), &Control::remove_theme_font_size_override); + ClassDB::bind_method(D_METHOD("remove_theme_color_override", "name"), &Control::remove_theme_color_override); + ClassDB::bind_method(D_METHOD("remove_theme_constant_override", "name"), &Control::remove_theme_constant_override); + + ClassDB::bind_method(D_METHOD("get_theme_icon", "name", "theme_type"), &Control::get_theme_icon, DEFVAL("")); + ClassDB::bind_method(D_METHOD("get_theme_stylebox", "name", "theme_type"), &Control::get_theme_stylebox, DEFVAL("")); + ClassDB::bind_method(D_METHOD("get_theme_font", "name", "theme_type"), &Control::get_theme_font, DEFVAL("")); + ClassDB::bind_method(D_METHOD("get_theme_font_size", "name", "theme_type"), &Control::get_theme_font_size, DEFVAL("")); + ClassDB::bind_method(D_METHOD("get_theme_color", "name", "theme_type"), &Control::get_theme_color, DEFVAL("")); + ClassDB::bind_method(D_METHOD("get_theme_constant", "name", "theme_type"), &Control::get_theme_constant, DEFVAL("")); ClassDB::bind_method(D_METHOD("has_theme_icon_override", "name"), &Control::has_theme_icon_override); ClassDB::bind_method(D_METHOD("has_theme_stylebox_override", "name"), &Control::has_theme_stylebox_override); @@ -2813,12 +2717,12 @@ void Control::_bind_methods() { ClassDB::bind_method(D_METHOD("has_theme_color_override", "name"), &Control::has_theme_color_override); ClassDB::bind_method(D_METHOD("has_theme_constant_override", "name"), &Control::has_theme_constant_override); - ClassDB::bind_method(D_METHOD("has_theme_icon", "name", "node_type"), &Control::has_theme_icon, DEFVAL("")); - ClassDB::bind_method(D_METHOD("has_theme_stylebox", "name", "node_type"), &Control::has_theme_stylebox, DEFVAL("")); - ClassDB::bind_method(D_METHOD("has_theme_font", "name", "node_type"), &Control::has_theme_font, DEFVAL("")); - ClassDB::bind_method(D_METHOD("has_theme_font_size", "name", "node_type"), &Control::has_theme_font_size, DEFVAL("")); - ClassDB::bind_method(D_METHOD("has_theme_color", "name", "node_type"), &Control::has_theme_color, DEFVAL("")); - ClassDB::bind_method(D_METHOD("has_theme_constant", "name", "node_type"), &Control::has_theme_constant, DEFVAL("")); + ClassDB::bind_method(D_METHOD("has_theme_icon", "name", "theme_type"), &Control::has_theme_icon, DEFVAL("")); + ClassDB::bind_method(D_METHOD("has_theme_stylebox", "name", "theme_type"), &Control::has_theme_stylebox, DEFVAL("")); + ClassDB::bind_method(D_METHOD("has_theme_font", "name", "theme_type"), &Control::has_theme_font, DEFVAL("")); + ClassDB::bind_method(D_METHOD("has_theme_font_size", "name", "theme_type"), &Control::has_theme_font_size, DEFVAL("")); + ClassDB::bind_method(D_METHOD("has_theme_color", "name", "theme_type"), &Control::has_theme_color, DEFVAL("")); + ClassDB::bind_method(D_METHOD("has_theme_constant", "name", "theme_type"), &Control::has_theme_constant, DEFVAL("")); ClassDB::bind_method(D_METHOD("get_parent_control"), &Control::get_parent_control); @@ -2899,7 +2803,7 @@ void Control::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::INT, "grow_vertical", PROPERTY_HINT_ENUM, "Begin,End,Both"), "set_v_grow_direction", "get_v_grow_direction"); ADD_GROUP("Layout Direction", "layout_"); - ADD_PROPERTY(PropertyInfo(Variant::INT, "layout_direction", PROPERTY_HINT_ENUM, "Inherited,Locale,Left-to-right,Right-to-left"), "set_layout_direction", "get_layout_direction"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "layout_direction", PROPERTY_HINT_ENUM, "Inherited,Locale,Left-to-Right,Right-to-Left"), "set_layout_direction", "get_layout_direction"); ADD_GROUP("Rect", "rect_"); ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "rect_position", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_EDITOR), "_set_position", "get_position"); @@ -2926,14 +2830,15 @@ void Control::_bind_methods() { ADD_GROUP("Mouse", "mouse_"); ADD_PROPERTY(PropertyInfo(Variant::INT, "mouse_filter", PROPERTY_HINT_ENUM, "Stop,Pass,Ignore"), "set_mouse_filter", "get_mouse_filter"); - ADD_PROPERTY(PropertyInfo(Variant::INT, "mouse_default_cursor_shape", PROPERTY_HINT_ENUM, "Arrow,Ibeam,Pointing hand,Cross,Wait,Busy,Drag,Can drop,Forbidden,Vertical resize,Horizontal resize,Secondary diagonal resize,Main diagonal resize,Move,Vertical split,Horizontal split,Help"), "set_default_cursor_shape", "get_default_cursor_shape"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "mouse_default_cursor_shape", PROPERTY_HINT_ENUM, "Arrow,I-Beam,Pointing Hand,Cross,Wait,Busy,Drag,Can Drop,Forbidden,Vertical Resize,Horizontal Resize,Secondary Diagonal Resize,Main Diagonal Resize,Move,Vertical Split,Horizontal Split,Help"), "set_default_cursor_shape", "get_default_cursor_shape"); ADD_GROUP("Size Flags", "size_flags_"); ADD_PROPERTY(PropertyInfo(Variant::INT, "size_flags_horizontal", PROPERTY_HINT_FLAGS, "Fill,Expand,Shrink Center,Shrink End"), "set_h_size_flags", "get_h_size_flags"); ADD_PROPERTY(PropertyInfo(Variant::INT, "size_flags_vertical", PROPERTY_HINT_FLAGS, "Fill,Expand,Shrink Center,Shrink End"), "set_v_size_flags", "get_v_size_flags"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "size_flags_stretch_ratio", PROPERTY_HINT_RANGE, "0,20,0.01,or_greater"), "set_stretch_ratio", "get_stretch_ratio"); - ADD_GROUP("Theme", ""); + ADD_GROUP("Theme", "theme_"); ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "theme", PROPERTY_HINT_RESOURCE_TYPE, "Theme"), "set_theme", "get_theme"); + ADD_PROPERTY(PropertyInfo(Variant::STRING, "theme_custom_type"), "set_theme_custom_type", "get_theme_custom_type"); ADD_GROUP("", ""); BIND_ENUM_CONSTANT(FOCUS_NONE); diff --git a/scene/gui/control.h b/scene/gui/control.h index 8981e05872..a05025c32d 100644 --- a/scene/gui/control.h +++ b/scene/gui/control.h @@ -171,22 +171,22 @@ private: Size2 last_minimum_size; bool updating_last_minimum_size = false; - float offset[4] = { 0.0, 0.0, 0.0, 0.0 }; - float anchor[4] = { ANCHOR_BEGIN, ANCHOR_BEGIN, ANCHOR_BEGIN, ANCHOR_BEGIN }; + real_t offset[4] = { 0.0, 0.0, 0.0, 0.0 }; + real_t anchor[4] = { ANCHOR_BEGIN, ANCHOR_BEGIN, ANCHOR_BEGIN, ANCHOR_BEGIN }; FocusMode focus_mode = FOCUS_NONE; GrowDirection h_grow = GROW_DIRECTION_END; GrowDirection v_grow = GROW_DIRECTION_END; LayoutDirection layout_dir = LAYOUT_DIRECTION_INHERITED; - float rotation = 0.0; + real_t rotation = 0.0; Vector2 scale = Vector2(1, 1); Vector2 pivot_offset; bool size_warning = true; int h_size_flags = SIZE_FILL; int v_size_flags = SIZE_FILL; - float expand = 1.0; + real_t expand = 1.0; Point2 custom_minimum_size; MouseFilter mouse_filter = MOUSE_FILTER_STOP; @@ -201,6 +201,8 @@ private: Ref<Theme> theme; Control *theme_owner = nullptr; Window *theme_owner_window = nullptr; + StringName theme_custom_type; + String tooltip; CursorShape default_cursor = CURSOR_ARROW; @@ -224,10 +226,10 @@ private: // used internally Control *_find_control_at_pos(CanvasItem *p_node, const Point2 &p_pos, const Transform2D &p_xform, Transform2D &r_inv_xform); - void _window_find_focus_neighbor(const Vector2 &p_dir, Node *p_at, const Point2 *p_points, float p_min, float &r_closest_dist, Control **r_closest); + void _window_find_focus_neighbor(const Vector2 &p_dir, Node *p_at, const Point2 *p_points, real_t p_min, real_t &r_closest_dist, Control **r_closest); Control *_get_focus_neighbor(Side p_side, int p_count = 0); - void _set_anchor(Side p_side, float p_anchor); + void _set_anchor(Side p_side, real_t p_anchor); void _set_position(const Point2 &p_point); void _set_global_position(const Point2 &p_point); void _set_size(const Size2 &p_size); @@ -239,8 +241,8 @@ private: void _clear_size_warning(); void _update_scroll(); - void _compute_offsets(Rect2 p_rect, const float p_anchors[4], float (&r_offsets)[4]); - void _compute_anchors(Rect2 p_rect, const float p_offsets[4], float (&r_anchors)[4]); + void _compute_offsets(Rect2 p_rect, const real_t p_anchors[4], real_t (&r_offsets)[4]); + void _compute_anchors(Rect2 p_rect, const real_t p_offsets[4], real_t (&r_anchors)[4]); void _size_changed(); String _get_tooltip() const; @@ -258,23 +260,9 @@ private: static void _propagate_theme_changed(Node *p_at, Control *p_owner, Window *p_owner_window, bool p_assign = true); template <class T> - _FORCE_INLINE_ static bool _find_theme_item(Control *p_theme_owner, Window *p_theme_owner_window, T &, T (Theme::*get_func)(const StringName &, const StringName &) const, bool (Theme::*has_func)(const StringName &, const StringName &) const, const StringName &p_name, const StringName &p_node_type); - - _FORCE_INLINE_ static bool _has_theme_item(Control *p_theme_owner, Window *p_theme_owner_window, bool (Theme::*has_func)(const StringName &, const StringName &) const, const StringName &p_name, const StringName &p_node_type); - - static Ref<Texture2D> get_icons(Control *p_theme_owner, Window *p_theme_owner_window, const StringName &p_name, const StringName &p_node_type = StringName()); - static Ref<StyleBox> get_styleboxs(Control *p_theme_owner, Window *p_theme_owner_window, const StringName &p_name, const StringName &p_node_type = StringName()); - static Ref<Font> get_fonts(Control *p_theme_owner, Window *p_theme_owner_window, const StringName &p_name, const StringName &p_node_type = StringName()); - static int get_font_sizes(Control *p_theme_owner, Window *p_theme_owner_window, const StringName &p_name, const StringName &p_node_type = StringName()); - static Color get_colors(Control *p_theme_owner, Window *p_theme_owner_window, const StringName &p_name, const StringName &p_node_type = StringName()); - static int get_constants(Control *p_theme_owner, Window *p_theme_owner_window, const StringName &p_name, const StringName &p_node_type = StringName()); - - static bool has_icons(Control *p_theme_owner, Window *p_theme_owner_window, const StringName &p_name, const StringName &p_node_type = StringName()); - static bool has_styleboxs(Control *p_theme_owner, Window *p_theme_owner_window, const StringName &p_name, const StringName &p_node_type = StringName()); - static bool has_fonts(Control *p_theme_owner, Window *p_theme_owner_window, const StringName &p_name, const StringName &p_node_type = StringName()); - static bool has_font_sizes(Control *p_theme_owner, Window *p_theme_owner_window, const StringName &p_name, const StringName &p_node_type = StringName()); - static bool has_colors(Control *p_theme_owner, Window *p_theme_owner_window, const StringName &p_name, const StringName &p_node_type = StringName()); - static bool has_constants(Control *p_theme_owner, Window *p_theme_owner_window, const StringName &p_name, const StringName &p_node_type = StringName()); + static T get_theme_item_in_types(Control *p_theme_owner, Window *p_theme_owner_window, Theme::DataType p_data_type, const StringName &p_name, List<StringName> p_theme_types); + static bool has_theme_item_in_types(Control *p_theme_owner, Window *p_theme_owner_window, Theme::DataType p_data_type, const StringName &p_name, List<StringName> p_theme_types); + _FORCE_INLINE_ void _get_theme_type_dependencies(const StringName &p_theme_type, List<StringName> *p_list) const; protected: virtual void add_child_notify(Node *p_child) override; @@ -282,7 +270,7 @@ protected: //virtual void _window_gui_input(InputEvent p_event); - virtual Vector<Vector2i> structured_text_parser(StructuredTextParser p_node_type, const Array &p_args, const String p_text) const; + virtual Vector<Vector2i> structured_text_parser(StructuredTextParser p_theme_type, const Array &p_args, const String p_text) const; bool _set(const StringName &p_name, const Variant &p_value); bool _get(const StringName &p_name, Variant &r_ret) const; @@ -325,8 +313,8 @@ public: virtual Rect2 _edit_get_rect() const override; virtual bool _edit_use_rect() const override; - virtual void _edit_set_rotation(float p_rotation) override; - virtual float _edit_get_rotation() const override; + virtual void _edit_set_rotation(real_t p_rotation) override; + virtual real_t _edit_get_rotation() const override; virtual bool _edit_use_rotation() const override; virtual void _edit_set_pivot(const Point2 &p_pivot) override; @@ -364,13 +352,13 @@ public: void set_offsets_preset(LayoutPreset p_preset, LayoutPresetMode p_resize_mode = PRESET_MODE_MINSIZE, int p_margin = 0); void set_anchors_and_offsets_preset(LayoutPreset p_preset, LayoutPresetMode p_resize_mode = PRESET_MODE_MINSIZE, int p_margin = 0); - void set_anchor(Side p_side, float p_anchor, bool p_keep_offset = true, bool p_push_opposite_anchor = true); - float get_anchor(Side p_side) const; + void set_anchor(Side p_side, real_t p_anchor, bool p_keep_offset = true, bool p_push_opposite_anchor = true); + real_t get_anchor(Side p_side) const; - void set_offset(Side p_side, float p_value); - float get_offset(Side p_side) const; + void set_offset(Side p_side, real_t p_value); + real_t get_offset(Side p_side) const; - void set_anchor_and_offset(Side p_side, float p_anchor, float p_pos, bool p_push_opposite_anchor = true); + void set_anchor_and_offset(Side p_side, real_t p_anchor, real_t p_pos, bool p_push_opposite_anchor = true); void set_begin(const Point2 &p_point); // helper void set_end(const Point2 &p_point); // helper @@ -395,10 +383,10 @@ public: void set_rect(const Rect2 &p_rect); // Reset anchors to begin and set rect, for faster container children sorting. - void set_rotation(float p_radians); - void set_rotation_degrees(float p_degrees); - float get_rotation() const; - float get_rotation_degrees() const; + void set_rotation(real_t p_radians); + void set_rotation_degrees(real_t p_degrees); + real_t get_rotation() const; + real_t get_rotation_degrees() const; void set_h_grow_direction(GrowDirection p_direction); GrowDirection get_h_grow_direction() const; @@ -415,14 +403,17 @@ public: void set_theme(const Ref<Theme> &p_theme); Ref<Theme> get_theme() const; + void set_theme_custom_type(const StringName &p_theme_type); + StringName get_theme_custom_type() const; + void set_h_size_flags(int p_flags); int get_h_size_flags() const; void set_v_size_flags(int p_flags); int get_v_size_flags() const; - void set_stretch_ratio(float p_ratio); - float get_stretch_ratio() const; + void set_stretch_ratio(real_t p_ratio); + real_t get_stretch_ratio() const; void minimum_size_changed(); @@ -459,12 +450,19 @@ public: void add_theme_color_override(const StringName &p_name, const Color &p_color); void add_theme_constant_override(const StringName &p_name, int p_constant); - Ref<Texture2D> get_theme_icon(const StringName &p_name, const StringName &p_node_type = StringName()) const; - Ref<StyleBox> get_theme_stylebox(const StringName &p_name, const StringName &p_node_type = StringName()) const; - Ref<Font> get_theme_font(const StringName &p_name, const StringName &p_node_type = StringName()) const; - int get_theme_font_size(const StringName &p_name, const StringName &p_node_type = StringName()) const; - Color get_theme_color(const StringName &p_name, const StringName &p_node_type = StringName()) const; - int get_theme_constant(const StringName &p_name, const StringName &p_node_type = StringName()) const; + void remove_theme_icon_override(const StringName &p_name); + void remove_theme_style_override(const StringName &p_name); + void remove_theme_font_override(const StringName &p_name); + void remove_theme_font_size_override(const StringName &p_name); + void remove_theme_color_override(const StringName &p_name); + void remove_theme_constant_override(const StringName &p_name); + + Ref<Texture2D> get_theme_icon(const StringName &p_name, const StringName &p_theme_type = StringName()) const; + Ref<StyleBox> get_theme_stylebox(const StringName &p_name, const StringName &p_theme_type = StringName()) const; + Ref<Font> get_theme_font(const StringName &p_name, const StringName &p_theme_type = StringName()) const; + int get_theme_font_size(const StringName &p_name, const StringName &p_theme_type = StringName()) const; + Color get_theme_color(const StringName &p_name, const StringName &p_theme_type = StringName()) const; + int get_theme_constant(const StringName &p_name, const StringName &p_theme_type = StringName()) const; bool has_theme_icon_override(const StringName &p_name) const; bool has_theme_stylebox_override(const StringName &p_name) const; @@ -473,12 +471,12 @@ public: bool has_theme_color_override(const StringName &p_name) const; bool has_theme_constant_override(const StringName &p_name) const; - bool has_theme_icon(const StringName &p_name, const StringName &p_node_type = StringName()) const; - bool has_theme_stylebox(const StringName &p_name, const StringName &p_node_type = StringName()) const; - bool has_theme_font(const StringName &p_name, const StringName &p_node_type = StringName()) const; - bool has_theme_font_size(const StringName &p_name, const StringName &p_node_type = StringName()) const; - bool has_theme_color(const StringName &p_name, const StringName &p_node_type = StringName()) const; - bool has_theme_constant(const StringName &p_name, const StringName &p_node_type = StringName()) const; + bool has_theme_icon(const StringName &p_name, const StringName &p_theme_type = StringName()) const; + bool has_theme_stylebox(const StringName &p_name, const StringName &p_theme_type = StringName()) const; + bool has_theme_font(const StringName &p_name, const StringName &p_theme_type = StringName()) const; + bool has_theme_font_size(const StringName &p_name, const StringName &p_theme_type = StringName()) const; + bool has_theme_color(const StringName &p_name, const StringName &p_theme_type = StringName()) const; + bool has_theme_constant(const StringName &p_name, const StringName &p_theme_type = StringName()) const; /* TOOLTIP */ @@ -517,7 +515,7 @@ public: bool is_visibility_clip_disabled() const; virtual void get_argument_options(const StringName &p_function, int p_idx, List<String> *r_options) const override; - virtual String get_configuration_warning() const override; + TypedArray<String> get_configuration_warnings() const override; Control() {} }; diff --git a/scene/gui/dialogs.cpp b/scene/gui/dialogs.cpp index fdfbf9eafc..7cae091c57 100644 --- a/scene/gui/dialogs.cpp +++ b/scene/gui/dialogs.cpp @@ -155,7 +155,7 @@ bool AcceptDialog::has_autowrap() { return label->has_autowrap(); } -void AcceptDialog::register_text_enter(Node *p_line_edit) { +void AcceptDialog::register_text_enter(Control *p_line_edit) { ERR_FAIL_NULL(p_line_edit); LineEdit *line_edit = Object::cast_to<LineEdit>(p_line_edit); if (line_edit) { @@ -256,7 +256,7 @@ Button *AcceptDialog::add_button(const String &p_text, bool p_right, const Strin Button *AcceptDialog::add_cancel_button(const String &p_cancel) { String c = p_cancel; if (p_cancel == "") { - c = RTR("Cancel"); + c = TTRC("Cancel"); } Button *b = swap_cancel_ok ? add_button(c, true) : add_button(c); b->connect("pressed", callable_mp(this, &AcceptDialog::_cancel_pressed)); @@ -317,13 +317,13 @@ AcceptDialog::AcceptDialog() { hbc->add_spacer(); ok = memnew(Button); - ok->set_text(RTR("OK")); + ok->set_text(TTRC("OK")); hbc->add_child(ok); hbc->add_spacer(); ok->connect("pressed", callable_mp(this, &AcceptDialog::_ok_pressed)); - set_title(RTR("Alert!")); + set_title(TTRC("Alert!")); connect("window_input", callable_mp(this, &AcceptDialog::_input_from_window)); } @@ -342,7 +342,7 @@ Button *ConfirmationDialog::get_cancel_button() { } ConfirmationDialog::ConfirmationDialog() { - set_title(RTR("Please Confirm...")); + set_title(TTRC("Please Confirm...")); #ifdef TOOLS_ENABLED set_min_size(Size2(200, 70) * EDSCALE); #endif diff --git a/scene/gui/dialogs.h b/scene/gui/dialogs.h index b072055d49..69035001c0 100644 --- a/scene/gui/dialogs.h +++ b/scene/gui/dialogs.h @@ -77,7 +77,7 @@ public: Label *get_label() { return label; } static void set_swap_cancel_ok(bool p_swap); - void register_text_enter(Node *p_line_edit); + void register_text_enter(Control *p_line_edit); Button *get_ok_button() { return ok; } Button *add_button(const String &p_text, bool p_right = false, const String &p_action = ""); diff --git a/scene/gui/file_dialog.cpp b/scene/gui/file_dialog.cpp index 7fb5113130..806039d7ac 100644 --- a/scene/gui/file_dialog.cpp +++ b/scene/gui/file_dialog.cpp @@ -57,6 +57,14 @@ void FileDialog::_theme_changed() { dir_up->add_theme_color_override("icon_hover_color", font_hover_color); dir_up->add_theme_color_override("icon_pressed_color", font_pressed_color); + dir_prev->add_theme_color_override("icon_color_normal", font_color); + dir_prev->add_theme_color_override("icon_color_hover", font_hover_color); + dir_prev->add_theme_color_override("icon_color_pressed", font_pressed_color); + + dir_next->add_theme_color_override("icon_color_normal", font_color); + dir_next->add_theme_color_override("icon_color_hover", font_hover_color); + dir_next->add_theme_color_override("icon_color_pressed", font_pressed_color); + refresh->add_theme_color_override("icon_normal_color", font_color); refresh->add_theme_color_override("icon_hover_color", font_hover_color); refresh->add_theme_color_override("icon_pressed_color", font_pressed_color); @@ -74,6 +82,13 @@ void FileDialog::_notification(int p_what) { } if (p_what == NOTIFICATION_ENTER_TREE) { dir_up->set_icon(vbox->get_theme_icon("parent_folder", "FileDialog")); + if (vbox->is_layout_rtl()) { + dir_prev->set_icon(vbox->get_theme_icon("forward_folder", "FileDialog")); + dir_next->set_icon(vbox->get_theme_icon("back_folder", "FileDialog")); + } else { + dir_prev->set_icon(vbox->get_theme_icon("back_folder", "FileDialog")); + dir_next->set_icon(vbox->get_theme_icon("forward_folder", "FileDialog")); + } refresh->set_icon(vbox->get_theme_icon("reload", "FileDialog")); show_hidden->set_icon(vbox->get_theme_icon("toggle_hidden", "FileDialog")); _theme_changed(); @@ -81,6 +96,8 @@ void FileDialog::_notification(int p_what) { } void FileDialog::_unhandled_input(const Ref<InputEvent> &p_event) { + ERR_FAIL_COND(p_event.is_null()); + Ref<InputEventKey> k = p_event; if (k.is_valid() && has_focus()) { if (k->is_pressed()) { @@ -88,7 +105,7 @@ void FileDialog::_unhandled_input(const Ref<InputEvent> &p_event) { switch (k->get_keycode()) { case KEY_H: { - if (k->get_command()) { + if (k->is_command_pressed()) { set_show_hidden_files(!show_hidden_files); } else { handled = false; @@ -144,6 +161,7 @@ void FileDialog::_dir_entered(String p_dir) { file->set_text(""); invalidate(); update_dir(); + _push_history(); } void FileDialog::_file_entered(const String &p_file) { @@ -177,6 +195,21 @@ void FileDialog::_post_popup() { } else { file_box->set_visible(true); } + + local_history.clear(); + local_history_pos = -1; + _push_history(); +} + +void FileDialog::_push_history() { + local_history.resize(local_history_pos + 1); + String new_path = dir_access->get_current_dir(); + if (local_history.size() == 0 || new_path != local_history[local_history_pos]) { + local_history.push_back(new_path); + local_history_pos++; + dir_prev->set_disabled(local_history_pos == 0); + dir_next->set_disabled(true); + } } void FileDialog::_action_pressed() { @@ -272,7 +305,7 @@ void FileDialog::_action_pressed() { } if (dir_access->file_exists(f)) { - confirm_save->set_text(RTR("File exists, overwrite?")); + confirm_save->set_text(TTRC("File exists, overwrite?")); confirm_save->popup_centered(Size2(200, 80)); } else { emit_signal("file_selected", f); @@ -316,6 +349,35 @@ void FileDialog::_go_up() { dir_access->change_dir(".."); update_file_list(); update_dir(); + _push_history(); +} + +void FileDialog::_go_back() { + if (local_history_pos <= 0) { + return; + } + + local_history_pos--; + dir_access->change_dir(local_history[local_history_pos]); + update_file_list(); + update_dir(); + + dir_prev->set_disabled(local_history_pos == 0); + dir_next->set_disabled(local_history_pos == local_history.size() - 1); +} + +void FileDialog::_go_forward() { + if (local_history_pos == local_history.size() - 1) { + return; + } + + local_history_pos++; + dir_access->change_dir(local_history[local_history_pos]); + update_file_list(); + update_dir(); + + dir_prev->set_disabled(local_history_pos == 0); + dir_next->set_disabled(local_history_pos == local_history.size() - 1); } void FileDialog::deselect_all() { @@ -329,10 +391,10 @@ void FileDialog::deselect_all() { switch (mode) { case FILE_MODE_OPEN_FILE: case FILE_MODE_OPEN_FILES: - get_ok_button()->set_text(RTR("Open")); + get_ok_button()->set_text(TTRC("Open")); break; case FILE_MODE_OPEN_DIR: - get_ok_button()->set_text(RTR("Select Current Folder")); + get_ok_button()->set_text(TTRC("Select Current Folder")); break; case FILE_MODE_OPEN_ANY: case FILE_MODE_SAVE_FILE: @@ -356,7 +418,7 @@ void FileDialog::_tree_selected() { if (!d["dir"]) { file->set_text(d["name"]); } else if (mode == FILE_MODE_OPEN_DIR) { - get_ok_button()->set_text(RTR("Select This Folder")); + get_ok_button()->set_text(TTRC("Select This Folder")); } get_ok_button()->set_disabled(_is_open_should_be_disabled()); @@ -377,6 +439,7 @@ void FileDialog::_tree_item_activated() { } call_deferred("_update_file_list"); call_deferred("_update_dir"); + _push_history(); } else { _action_pressed(); } @@ -415,6 +478,13 @@ void FileDialog::update_file_list() { bool is_hidden; String item; + if (dir_access->is_readable(dir_access->get_current_dir().utf8().get_data())) { + message->hide(); + } else { + message->set_text(TTRC("You don't have permission to access contents of this folder.")); + message->show(); + } + while ((item = dir_access->get_next()) != "") { if (item == "." || item == "..") { continue; @@ -519,8 +589,8 @@ void FileDialog::update_file_list() { files.pop_front(); } - if (tree->get_root() && tree->get_root()->get_children() && tree->get_selected() == nullptr) { - tree->get_root()->get_children()->select(0); + if (tree->get_root() && tree->get_root()->get_first_child() && tree->get_selected() == nullptr) { + tree->get_root()->get_first_child()->select(0); } } @@ -549,7 +619,7 @@ void FileDialog::update_filters() { all_filters += ", ..."; } - filter->add_item(RTR("All Recognized") + " (" + all_filters + ")"); + filter->add_item(String(TTRC("All Recognized")) + " (" + all_filters + ")"); } for (int i = 0; i < filters.size(); i++) { String flt = filters[i].get_slice(";", 0).strip_edges(); @@ -561,7 +631,7 @@ void FileDialog::update_filters() { } } - filter->add_item(RTR("All Files (*)")); + filter->add_item(TTRC("All Files (*)")); } void FileDialog::clear_filters() { @@ -602,6 +672,7 @@ void FileDialog::set_current_dir(const String &p_dir) { dir_access->change_dir(p_dir); update_dir(); invalidate(); + _push_history(); } void FileDialog::set_current_file(const String &p_file) { @@ -646,37 +717,37 @@ void FileDialog::set_file_mode(FileMode p_mode) { mode = p_mode; switch (mode) { case FILE_MODE_OPEN_FILE: - get_ok_button()->set_text(RTR("Open")); + get_ok_button()->set_text(TTRC("Open")); if (mode_overrides_title) { - set_title(RTR("Open a File")); + set_title(TTRC("Open a File")); } makedir->hide(); break; case FILE_MODE_OPEN_FILES: - get_ok_button()->set_text(RTR("Open")); + get_ok_button()->set_text(TTRC("Open")); if (mode_overrides_title) { - set_title(RTR("Open File(s)")); + set_title(TTRC("Open File(s)")); } makedir->hide(); break; case FILE_MODE_OPEN_DIR: - get_ok_button()->set_text(RTR("Select Current Folder")); + get_ok_button()->set_text(TTRC("Select Current Folder")); if (mode_overrides_title) { - set_title(RTR("Open a Directory")); + set_title(TTRC("Open a Directory")); } makedir->show(); break; case FILE_MODE_OPEN_ANY: - get_ok_button()->set_text(RTR("Open")); + get_ok_button()->set_text(TTRC("Open")); if (mode_overrides_title) { - set_title(RTR("Open a File or Directory")); + set_title(TTRC("Open a File or Directory")); } makedir->show(); break; case FILE_MODE_SAVE_FILE: - get_ok_button()->set_text(RTR("Save")); + get_ok_button()->set_text(TTRC("Save")); if (mode_overrides_title) { - set_title(RTR("Save a File")); + set_title(TTRC("Save a File")); } makedir->show(); break; @@ -737,6 +808,7 @@ void FileDialog::_make_dir_confirm() { invalidate(); update_filters(); update_dir(); + _push_history(); } else { mkdirerr->popup_centered(Size2(250, 50)); } @@ -754,6 +826,7 @@ void FileDialog::_select_drive(int p_idx) { file->set_text(""); invalidate(); update_dir(); + _push_history(); } void FileDialog::_update_drives() { @@ -857,17 +930,27 @@ FileDialog::FileDialog() { vbox->connect("theme_changed", callable_mp(this, &FileDialog::_theme_changed)); mode = FILE_MODE_SAVE_FILE; - set_title(RTR("Save a File")); + set_title(TTRC("Save a File")); HBoxContainer *hbc = memnew(HBoxContainer); + dir_prev = memnew(Button); + dir_prev->set_flat(true); + dir_prev->set_tooltip(TTRC("Go to previous folder.")); + dir_next = memnew(Button); + dir_next->set_flat(true); + dir_next->set_tooltip(TTRC("Go to next folder.")); dir_up = memnew(Button); dir_up->set_flat(true); - dir_up->set_tooltip(RTR("Go to parent folder.")); + dir_up->set_tooltip(TTRC("Go to parent folder.")); + hbc->add_child(dir_prev); + hbc->add_child(dir_next); hbc->add_child(dir_up); + dir_prev->connect("pressed", callable_mp(this, &FileDialog::_go_back)); + dir_next->connect("pressed", callable_mp(this, &FileDialog::_go_forward)); dir_up->connect("pressed", callable_mp(this, &FileDialog::_go_up)); - hbc->add_child(memnew(Label(RTR("Path:")))); + hbc->add_child(memnew(Label(TTRC("Path:")))); drives_container = memnew(HBoxContainer); hbc->add_child(drives_container); @@ -883,7 +966,7 @@ FileDialog::FileDialog() { refresh = memnew(Button); refresh->set_flat(true); - refresh->set_tooltip(RTR("Refresh files.")); + refresh->set_tooltip(TTRC("Refresh files.")); refresh->connect("pressed", callable_mp(this, &FileDialog::update_file_list)); hbc->add_child(refresh); @@ -891,7 +974,7 @@ FileDialog::FileDialog() { show_hidden->set_flat(true); show_hidden->set_toggle_mode(true); show_hidden->set_pressed(is_showing_hidden_files()); - show_hidden->set_tooltip(RTR("Toggle the visibility of hidden files.")); + show_hidden->set_tooltip(TTRC("Toggle the visibility of hidden files.")); show_hidden->connect("toggled", callable_mp(this, &FileDialog::set_show_hidden_files)); hbc->add_child(show_hidden); @@ -899,17 +982,24 @@ FileDialog::FileDialog() { hbc->add_child(shortcuts_container); makedir = memnew(Button); - makedir->set_text(RTR("Create Folder")); + makedir->set_text(TTRC("Create Folder")); makedir->connect("pressed", callable_mp(this, &FileDialog::_make_dir)); hbc->add_child(makedir); vbox->add_child(hbc); tree = memnew(Tree); tree->set_hide_root(true); - vbox->add_margin_child(RTR("Directories & Files:"), tree, true); + vbox->add_margin_child(TTRC("Directories & Files:"), tree, true); + + message = memnew(Label); + message->hide(); + message->set_anchors_and_offsets_preset(Control::PRESET_WIDE); + message->set_align(Label::ALIGN_CENTER); + message->set_valign(Label::VALIGN_CENTER); + tree->add_child(message); file_box = memnew(HBoxContainer); - file_box->add_child(memnew(Label(RTR("File:")))); + file_box->add_child(memnew(Label(TTRC("File:")))); file = memnew(LineEdit); file->set_structured_text_bidi_override(Control::STRUCTURED_TEXT_FILE); file->set_stretch_ratio(4); @@ -941,22 +1031,22 @@ FileDialog::FileDialog() { confirm_save->connect("confirmed", callable_mp(this, &FileDialog::_save_confirm_pressed)); makedialog = memnew(ConfirmationDialog); - makedialog->set_title(RTR("Create Folder")); + makedialog->set_title(TTRC("Create Folder")); VBoxContainer *makevb = memnew(VBoxContainer); makedialog->add_child(makevb); makedirname = memnew(LineEdit); makedirname->set_structured_text_bidi_override(Control::STRUCTURED_TEXT_FILE); - makevb->add_margin_child(RTR("Name:"), makedirname); + makevb->add_margin_child(TTRC("Name:"), makedirname); add_child(makedialog); makedialog->register_text_enter(makedirname); makedialog->connect("confirmed", callable_mp(this, &FileDialog::_make_dir_confirm)); mkdirerr = memnew(AcceptDialog); - mkdirerr->set_text(RTR("Could not create folder.")); + mkdirerr->set_text(TTRC("Could not create folder.")); add_child(mkdirerr); exterr = memnew(AcceptDialog); - exterr->set_text(RTR("Must use a valid extension.")); + exterr->set_text(TTRC("Must use a valid extension.")); add_child(exterr); update_filters(); diff --git a/scene/gui/file_dialog.h b/scene/gui/file_dialog.h index 25b742c234..55774b488c 100644 --- a/scene/gui/file_dialog.h +++ b/scene/gui/file_dialog.h @@ -32,7 +32,7 @@ #define FILE_DIALOG_H #include "box_container.h" -#include "core/os/dir_access.h" +#include "core/io/dir_access.h" #include "scene/gui/dialogs.h" #include "scene/gui/line_edit.h" #include "scene/gui/option_button.h" @@ -86,6 +86,10 @@ private: DirAccess *dir_access; ConfirmationDialog *confirm_save; + Label *message; + + Button *dir_prev; + Button *dir_next; Button *dir_up; Button *refresh; @@ -93,6 +97,10 @@ private: Vector<String> filters; + Vector<String> local_history; + int local_history_pos = 0; + void _push_history(); + bool mode_overrides_title = true; static bool default_show_hidden_files; @@ -119,6 +127,8 @@ private: void _make_dir(); void _make_dir_confirm(); void _go_up(); + void _go_back(); + void _go_forward(); void _update_drives(); diff --git a/scene/gui/gradient_edit.cpp b/scene/gui/gradient_edit.cpp index 36b383f16c..7278ca6e94 100644 --- a/scene/gui/gradient_edit.cpp +++ b/scene/gui/gradient_edit.cpp @@ -92,6 +92,8 @@ GradientEdit::~GradientEdit() { } void GradientEdit::_gui_input(const Ref<InputEvent> &p_event) { + ERR_FAIL_COND(p_event.is_null()); + Ref<InputEventKey> k = p_event; if (k.is_valid() && k->is_pressed() && k->get_keycode() == KEY_DELETE && grabbed != -1) { @@ -105,7 +107,7 @@ void GradientEdit::_gui_input(const Ref<InputEvent> &p_event) { Ref<InputEventMouseButton> mb = p_event; //Show color picker on double click. - if (mb.is_valid() && mb->get_button_index() == 1 && mb->is_doubleclick() && mb->is_pressed()) { + if (mb.is_valid() && mb->get_button_index() == 1 && mb->is_double_click() && mb->is_pressed()) { grabbed = _get_point_from_pos(mb->get_position().x); _show_color_picker(); accept_event(); @@ -125,7 +127,7 @@ void GradientEdit::_gui_input(const Ref<InputEvent> &p_event) { } //Hold alt key to duplicate selected color - if (mb.is_valid() && mb->get_button_index() == 1 && mb->is_pressed() && mb->get_alt()) { + if (mb.is_valid() && mb->get_button_index() == 1 && mb->is_pressed() && mb->is_alt_pressed()) { int x = mb->get_position().x; grabbed = _get_point_from_pos(x); @@ -234,9 +236,9 @@ void GradientEdit::_gui_input(const Ref<InputEvent> &p_event) { // Snap to "round" coordinates if holding Ctrl. // Be more precise if holding Shift as well - if (mm->get_control()) { - newofs = Math::snapped(newofs, mm->get_shift() ? 0.025 : 0.1); - } else if (mm->get_shift()) { + if (mm->is_ctrl_pressed()) { + newofs = Math::snapped(newofs, mm->is_shift_pressed() ? 0.025 : 0.1); + } else if (mm->is_shift_pressed()) { // Snap to nearest point if holding just Shift const float snap_threshold = 0.03; float smallest_ofs = snap_threshold; diff --git a/scene/gui/graph_edit.cpp b/scene/gui/graph_edit.cpp index 71d31434d4..78ab30699a 100644 --- a/scene/gui/graph_edit.cpp +++ b/scene/gui/graph_edit.cpp @@ -154,6 +154,8 @@ Vector2 GraphEditMinimap::_convert_to_graph_position(const Vector2 &p_position) } void GraphEditMinimap::_gui_input(const Ref<InputEvent> &p_ev) { + ERR_FAIL_COND(p_ev.is_null()); + if (!ge->is_minimap_enabled()) { return; } @@ -161,7 +163,7 @@ void GraphEditMinimap::_gui_input(const Ref<InputEvent> &p_ev) { Ref<InputEventMouseButton> mb = p_ev; Ref<InputEventMouseMotion> mm = p_ev; - if (mb.is_valid() && mb->get_button_index() == BUTTON_LEFT) { + if (mb.is_valid() && mb->get_button_index() == MOUSE_BUTTON_LEFT) { if (mb->is_pressed()) { is_pressing = true; @@ -553,7 +555,7 @@ bool GraphEdit::_filter_input(const Point2 &p_point) { void GraphEdit::_top_layer_input(const Ref<InputEvent> &p_ev) { Ref<InputEventMouseButton> mb = p_ev; - if (mb.is_valid() && mb->get_button_index() == BUTTON_LEFT && mb->is_pressed()) { + if (mb.is_valid() && mb->get_button_index() == MOUSE_BUTTON_LEFT && mb->is_pressed()) { connecting_valid = false; Ref<Texture2D> port = get_theme_icon("port", "GraphNode"); click_pos = mb->get_position() / zoom; @@ -696,7 +698,7 @@ void GraphEdit::_top_layer_input(const Ref<InputEvent> &p_ev) { } } - if (mb.is_valid() && mb->get_button_index() == BUTTON_LEFT && !mb->is_pressed()) { + if (mb.is_valid() && mb->get_button_index() == MOUSE_BUTTON_LEFT && !mb->is_pressed()) { if (connecting_valid) { if (connecting && connecting_target) { String from = connecting_from; @@ -822,7 +824,7 @@ void GraphEdit::_bake_segment2d(Vector<Vector2> &points, Vector<Color> &colors, } } -void GraphEdit::_draw_cos_line(CanvasItem *p_where, const Vector2 &p_from, const Vector2 &p_to, const Color &p_color, const Color &p_to_color, float p_width, float p_bezier_ratio = 1.0) { +void GraphEdit::_draw_cos_line(CanvasItem *p_where, const Vector2 &p_from, const Vector2 &p_to, const Color &p_color, const Color &p_to_color, float p_width, float p_bezier_ratio) { //cubic bezier code float diff = p_to.x - p_from.x; float cp_offset; @@ -1066,8 +1068,10 @@ void GraphEdit::set_selected(Node *p_child) { } void GraphEdit::_gui_input(const Ref<InputEvent> &p_ev) { + ERR_FAIL_COND(p_ev.is_null()); + Ref<InputEventMouseMotion> mm = p_ev; - if (mm.is_valid() && (mm->get_button_mask() & BUTTON_MASK_MIDDLE || (mm->get_button_mask() & BUTTON_MASK_LEFT && Input::get_singleton()->is_key_pressed(KEY_SPACE)))) { + if (mm.is_valid() && (mm->get_button_mask() & MOUSE_BUTTON_MASK_MIDDLE || (mm->get_button_mask() & MOUSE_BUTTON_MASK_LEFT && Input::get_singleton()->is_key_pressed(KEY_SPACE)))) { h_scroll->set_value(h_scroll->get_value() - mm->get_relative().x); v_scroll->set_value(v_scroll->get_value() - mm->get_relative().y); } @@ -1087,7 +1091,7 @@ void GraphEdit::_gui_input(const Ref<InputEvent> &p_ev) { // Snapping can be toggled temporarily by holding down Ctrl. // This is done here as to not toggle the grid when holding down Ctrl. - if (is_using_snap() ^ Input::get_singleton()->is_key_pressed(KEY_CONTROL)) { + if (is_using_snap() ^ Input::get_singleton()->is_key_pressed(KEY_CTRL)) { const int snap = get_snap(); pos = pos.snapped(Vector2(snap, snap)); } @@ -1123,7 +1127,7 @@ void GraphEdit::_gui_input(const Ref<InputEvent> &p_ev) { } gn->set_selected(box_selection_mode_additive); } else { - bool select = (previus_selected.find(gn) != nullptr); + bool select = (previous_selected.find(gn) != nullptr); if (gn->is_selected() && !select) { emit_signal("node_deselected", gn); } else if (!gn->is_selected() && select) { @@ -1139,7 +1143,7 @@ void GraphEdit::_gui_input(const Ref<InputEvent> &p_ev) { Ref<InputEventMouseButton> b = p_ev; if (b.is_valid()) { - if (b->get_button_index() == BUTTON_RIGHT && b->is_pressed()) { + if (b->get_button_index() == MOUSE_BUTTON_RIGHT && b->is_pressed()) { if (box_selecting) { box_selecting = false; for (int i = get_child_count() - 1; i >= 0; i--) { @@ -1148,7 +1152,7 @@ void GraphEdit::_gui_input(const Ref<InputEvent> &p_ev) { continue; } - bool select = (previus_selected.find(gn) != nullptr); + bool select = (previous_selected.find(gn) != nullptr); if (gn->is_selected() && !select) { emit_signal("node_deselected", gn); } else if (!gn->is_selected() && select) { @@ -1169,8 +1173,8 @@ void GraphEdit::_gui_input(const Ref<InputEvent> &p_ev) { } } - if (b->get_button_index() == BUTTON_LEFT && !b->is_pressed() && dragging) { - if (!just_selected && drag_accum == Vector2() && Input::get_singleton()->is_key_pressed(KEY_CONTROL)) { + if (b->get_button_index() == MOUSE_BUTTON_LEFT && !b->is_pressed() && dragging) { + if (!just_selected && drag_accum == Vector2() && Input::get_singleton()->is_key_pressed(KEY_CTRL)) { //deselect current node for (int i = get_child_count() - 1; i >= 0; i--) { GraphNode *gn = Object::cast_to<GraphNode>(get_child(i)); @@ -1208,7 +1212,7 @@ void GraphEdit::_gui_input(const Ref<InputEvent> &p_ev) { connections_layer->update(); } - if (b->get_button_index() == BUTTON_LEFT && b->is_pressed()) { + if (b->get_button_index() == MOUSE_BUTTON_LEFT && b->is_pressed()) { GraphNode *gn = nullptr; for (int i = get_child_count() - 1; i >= 0; i--) { @@ -1234,7 +1238,7 @@ void GraphEdit::_gui_input(const Ref<InputEvent> &p_ev) { dragging = true; drag_accum = Vector2(); just_selected = !gn->is_selected(); - if (!gn->is_selected() && !Input::get_singleton()->is_key_pressed(KEY_CONTROL)) { + if (!gn->is_selected() && !Input::get_singleton()->is_key_pressed(KEY_CTRL)) { for (int i = 0; i < get_child_count(); i++) { GraphNode *o_gn = Object::cast_to<GraphNode>(get_child(i)); if (o_gn) { @@ -1271,31 +1275,31 @@ void GraphEdit::_gui_input(const Ref<InputEvent> &p_ev) { box_selecting = true; box_selecting_from = b->get_position(); - if (b->get_control()) { + if (b->is_ctrl_pressed()) { box_selection_mode_additive = true; - previus_selected.clear(); + previous_selected.clear(); for (int i = get_child_count() - 1; i >= 0; i--) { GraphNode *gn2 = Object::cast_to<GraphNode>(get_child(i)); if (!gn2 || !gn2->is_selected()) { continue; } - previus_selected.push_back(gn2); + previous_selected.push_back(gn2); } - } else if (b->get_shift()) { + } else if (b->is_shift_pressed()) { box_selection_mode_additive = false; - previus_selected.clear(); + previous_selected.clear(); for (int i = get_child_count() - 1; i >= 0; i--) { GraphNode *gn2 = Object::cast_to<GraphNode>(get_child(i)); if (!gn2 || !gn2->is_selected()) { continue; } - previus_selected.push_back(gn2); + previous_selected.push_back(gn2); } } else { box_selection_mode_additive = true; - previus_selected.clear(); + previous_selected.clear(); for (int i = get_child_count() - 1; i >= 0; i--) { GraphNode *gn2 = Object::cast_to<GraphNode>(get_child(i)); if (!gn2) { @@ -1310,24 +1314,25 @@ void GraphEdit::_gui_input(const Ref<InputEvent> &p_ev) { } } - if (b->get_button_index() == BUTTON_LEFT && !b->is_pressed() && box_selecting) { + if (b->get_button_index() == MOUSE_BUTTON_LEFT && !b->is_pressed() && box_selecting) { box_selecting = false; - previus_selected.clear(); + box_selecting_rect = Rect2(); + previous_selected.clear(); top_layer->update(); minimap->update(); } - if (b->get_button_index() == BUTTON_WHEEL_UP && Input::get_singleton()->is_key_pressed(KEY_CONTROL)) { - set_zoom(zoom * ZOOM_SCALE); - } else if (b->get_button_index() == BUTTON_WHEEL_DOWN && Input::get_singleton()->is_key_pressed(KEY_CONTROL)) { - set_zoom(zoom / ZOOM_SCALE); - } else if (b->get_button_index() == BUTTON_WHEEL_UP && !Input::get_singleton()->is_key_pressed(KEY_SHIFT)) { + if (b->get_button_index() == MOUSE_BUTTON_WHEEL_UP && Input::get_singleton()->is_key_pressed(KEY_CTRL)) { + set_zoom_custom(zoom * ZOOM_SCALE, b->get_position()); + } else if (b->get_button_index() == MOUSE_BUTTON_WHEEL_DOWN && Input::get_singleton()->is_key_pressed(KEY_CTRL)) { + set_zoom_custom(zoom / ZOOM_SCALE, b->get_position()); + } else if (b->get_button_index() == MOUSE_BUTTON_WHEEL_UP && !Input::get_singleton()->is_key_pressed(KEY_SHIFT)) { v_scroll->set_value(v_scroll->get_value() - v_scroll->get_page() * b->get_factor() / 8); - } else if (b->get_button_index() == BUTTON_WHEEL_DOWN && !Input::get_singleton()->is_key_pressed(KEY_SHIFT)) { + } else if (b->get_button_index() == MOUSE_BUTTON_WHEEL_DOWN && !Input::get_singleton()->is_key_pressed(KEY_SHIFT)) { v_scroll->set_value(v_scroll->get_value() + v_scroll->get_page() * b->get_factor() / 8); - } else if (b->get_button_index() == BUTTON_WHEEL_RIGHT || (b->get_button_index() == BUTTON_WHEEL_DOWN && Input::get_singleton()->is_key_pressed(KEY_SHIFT))) { + } else if (b->get_button_index() == MOUSE_BUTTON_WHEEL_RIGHT || (b->get_button_index() == MOUSE_BUTTON_WHEEL_DOWN && Input::get_singleton()->is_key_pressed(KEY_SHIFT))) { h_scroll->set_value(h_scroll->get_value() + h_scroll->get_page() * b->get_factor() / 8); - } else if (b->get_button_index() == BUTTON_WHEEL_LEFT || (b->get_button_index() == BUTTON_WHEEL_UP && Input::get_singleton()->is_key_pressed(KEY_SHIFT))) { + } else if (b->get_button_index() == MOUSE_BUTTON_WHEEL_LEFT || (b->get_button_index() == MOUSE_BUTTON_WHEEL_UP && Input::get_singleton()->is_key_pressed(KEY_SHIFT))) { h_scroll->set_value(h_scroll->get_value() - h_scroll->get_page() * b->get_factor() / 8); } } diff --git a/scene/gui/graph_edit.h b/scene/gui/graph_edit.h index 8fdf975319..8a51bcb11e 100644 --- a/scene/gui/graph_edit.h +++ b/scene/gui/graph_edit.h @@ -150,7 +150,7 @@ private: Point2 box_selecting_from; Point2 box_selecting_to; Rect2 box_selecting_rect; - List<GraphNode *> previus_selected; + List<GraphNode *> previous_selected; bool setting_scroll_ofs = false; bool right_disconnects = false; @@ -163,7 +163,7 @@ private: void _bake_segment2d(Vector<Vector2> &points, Vector<Color> &colors, float p_begin, float p_end, const Vector2 &p_a, const Vector2 &p_out, const Vector2 &p_b, const Vector2 &p_in, int p_depth, int p_min_depth, int p_max_depth, float p_tol, const Color &p_color, const Color &p_to_color, int &lines) const; - void _draw_cos_line(CanvasItem *p_where, const Vector2 &p_from, const Vector2 &p_to, const Color &p_color, const Color &p_to_color, float p_width, float p_bezier_ratio); + void _draw_cos_line(CanvasItem *p_where, const Vector2 &p_from, const Vector2 &p_to, const Color &p_color, const Color &p_to_color, float p_width, float p_bezier_ratio = 1.0); void _graph_node_raised(Node *p_gn); void _graph_node_moved(Node *p_gn); diff --git a/scene/gui/graph_node.cpp b/scene/gui/graph_node.cpp index c80fc516e8..77c502cf8d 100644 --- a/scene/gui/graph_node.cpp +++ b/scene/gui/graph_node.cpp @@ -228,7 +228,7 @@ void GraphNode::_resort() { } stretch_avail += stretch_diff - sb->get_margin(SIDE_BOTTOM) - sb->get_margin(SIDE_TOP); //available stretch space. - /** Second, pass sucessively to discard elements that can't be stretched, this will run while stretchable + /** Second, pass successively to discard elements that can't be stretched, this will run while stretchable elements exist */ while (stretch_ratio_total > 0) { // first of all, don't even be here if no stretchable objects exist @@ -459,7 +459,7 @@ void GraphNode::_shape() { } void GraphNode::set_slot(int p_idx, bool p_enable_left, int p_type_left, const Color &p_color_left, bool p_enable_right, int p_type_right, const Color &p_color_right, const Ref<Texture2D> &p_custom_left, const Ref<Texture2D> &p_custom_right) { - ERR_FAIL_COND(p_idx < 0); + ERR_FAIL_COND_MSG(p_idx < 0, vformat("Cannot set slot with p_idx (%d) lesser than zero.", p_idx)); if (!p_enable_left && p_type_left == 0 && p_color_left == Color(1, 1, 1, 1) && !p_enable_right && p_type_right == 0 && p_color_right == Color(1, 1, 1, 1) && @@ -503,6 +503,26 @@ bool GraphNode::is_slot_enabled_left(int p_idx) const { return slot_info[p_idx].enable_left; } +void GraphNode::set_slot_enabled_left(int p_idx, bool p_enable_left) { + ERR_FAIL_COND_MSG(p_idx < 0, vformat("Cannot set enable_left for the slot with p_idx (%d) lesser than zero.", p_idx)); + + slot_info[p_idx].enable_left = p_enable_left; + update(); + connpos_dirty = true; + + emit_signal("slot_updated", p_idx); +} + +void GraphNode::set_slot_type_left(int p_idx, int p_type_left) { + ERR_FAIL_COND_MSG(!slot_info.has(p_idx), vformat("Cannot set type_left for the slot '%d' because it hasn't been enabled.", p_idx)); + + slot_info[p_idx].type_left = p_type_left; + update(); + connpos_dirty = true; + + emit_signal("slot_updated", p_idx); +} + int GraphNode::get_slot_type_left(int p_idx) const { if (!slot_info.has(p_idx)) { return 0; @@ -510,6 +530,16 @@ int GraphNode::get_slot_type_left(int p_idx) const { return slot_info[p_idx].type_left; } +void GraphNode::set_slot_color_left(int p_idx, const Color &p_color_left) { + ERR_FAIL_COND_MSG(!slot_info.has(p_idx), vformat("Cannot set color_left for the slot '%d' because it hasn't been enabled.", p_idx)); + + slot_info[p_idx].color_left = p_color_left; + update(); + connpos_dirty = true; + + emit_signal("slot_updated", p_idx); +} + Color GraphNode::get_slot_color_left(int p_idx) const { if (!slot_info.has(p_idx)) { return Color(1, 1, 1, 1); @@ -524,6 +554,26 @@ bool GraphNode::is_slot_enabled_right(int p_idx) const { return slot_info[p_idx].enable_right; } +void GraphNode::set_slot_enabled_right(int p_idx, bool p_enable_right) { + ERR_FAIL_COND_MSG(p_idx < 0, vformat("Cannot set enable_right for the slot with p_idx (%d) lesser than zero.", p_idx)); + + slot_info[p_idx].enable_right = p_enable_right; + update(); + connpos_dirty = true; + + emit_signal("slot_updated", p_idx); +} + +void GraphNode::set_slot_type_right(int p_idx, int p_type_right) { + ERR_FAIL_COND_MSG(!slot_info.has(p_idx), vformat("Cannot set type_right for the slot '%d' because it hasn't been enabled.", p_idx)); + + slot_info[p_idx].type_right = p_type_right; + update(); + connpos_dirty = true; + + emit_signal("slot_updated", p_idx); +} + int GraphNode::get_slot_type_right(int p_idx) const { if (!slot_info.has(p_idx)) { return 0; @@ -531,6 +581,16 @@ int GraphNode::get_slot_type_right(int p_idx) const { return slot_info[p_idx].type_right; } +void GraphNode::set_slot_color_right(int p_idx, const Color &p_color_right) { + ERR_FAIL_COND_MSG(!slot_info.has(p_idx), vformat("Cannot set color_right for the slot '%d' because it hasn't been enabled.", p_idx)); + + slot_info[p_idx].color_right = p_color_right; + update(); + connpos_dirty = true; + + emit_signal("slot_updated", p_idx); +} + Color GraphNode::get_slot_color_right(int p_idx) const { if (!slot_info.has(p_idx)) { return Color(1, 1, 1, 1); @@ -804,11 +864,13 @@ Color GraphNode::get_connection_output_color(int p_idx) { } void GraphNode::_gui_input(const Ref<InputEvent> &p_ev) { + ERR_FAIL_COND(p_ev.is_null()); + Ref<InputEventMouseButton> mb = p_ev; if (mb.is_valid()) { ERR_FAIL_COND_MSG(get_parent_control() == nullptr, "GraphNode must be the child of a GraphEdit node."); - if (mb->is_pressed() && mb->get_button_index() == BUTTON_LEFT) { + if (mb->is_pressed() && mb->get_button_index() == MOUSE_BUTTON_LEFT) { Vector2 mpos = Vector2(mb->get_position().x, mb->get_position().y); if (close_rect.size != Size2() && close_rect.has_point(mpos)) { //send focus to parent @@ -831,7 +893,7 @@ void GraphNode::_gui_input(const Ref<InputEvent> &p_ev) { emit_signal("raise_request"); } - if (!mb->is_pressed() && mb->get_button_index() == BUTTON_LEFT) { + if (!mb->is_pressed() && mb->get_button_index() == MOUSE_BUTTON_LEFT) { resizing = false; } } @@ -889,11 +951,23 @@ void GraphNode::_bind_methods() { ClassDB::bind_method(D_METHOD("set_slot", "idx", "enable_left", "type_left", "color_left", "enable_right", "type_right", "color_right", "custom_left", "custom_right"), &GraphNode::set_slot, DEFVAL(Ref<Texture2D>()), DEFVAL(Ref<Texture2D>())); ClassDB::bind_method(D_METHOD("clear_slot", "idx"), &GraphNode::clear_slot); ClassDB::bind_method(D_METHOD("clear_all_slots"), &GraphNode::clear_all_slots); + ClassDB::bind_method(D_METHOD("is_slot_enabled_left", "idx"), &GraphNode::is_slot_enabled_left); + ClassDB::bind_method(D_METHOD("set_slot_enabled_left", "idx", "enable_left"), &GraphNode::set_slot_enabled_left); + + ClassDB::bind_method(D_METHOD("set_slot_type_left", "idx", "type_left"), &GraphNode::set_slot_type_left); ClassDB::bind_method(D_METHOD("get_slot_type_left", "idx"), &GraphNode::get_slot_type_left); + + ClassDB::bind_method(D_METHOD("set_slot_color_left", "idx", "color_left"), &GraphNode::set_slot_color_left); ClassDB::bind_method(D_METHOD("get_slot_color_left", "idx"), &GraphNode::get_slot_color_left); + ClassDB::bind_method(D_METHOD("is_slot_enabled_right", "idx"), &GraphNode::is_slot_enabled_right); + ClassDB::bind_method(D_METHOD("set_slot_enabled_right", "idx", "enable_right"), &GraphNode::set_slot_enabled_right); + + ClassDB::bind_method(D_METHOD("set_slot_type_right", "idx", "type_right"), &GraphNode::set_slot_type_right); ClassDB::bind_method(D_METHOD("get_slot_type_right", "idx"), &GraphNode::get_slot_type_right); + + ClassDB::bind_method(D_METHOD("set_slot_color_right", "idx", "color_right"), &GraphNode::set_slot_color_right); ClassDB::bind_method(D_METHOD("get_slot_color_right", "idx"), &GraphNode::get_slot_color_right); ClassDB::bind_method(D_METHOD("set_position_offset", "offset"), &GraphNode::set_position_offset); @@ -925,7 +999,7 @@ void GraphNode::_bind_methods() { ClassDB::bind_method(D_METHOD("get_overlay"), &GraphNode::get_overlay); ADD_PROPERTY(PropertyInfo(Variant::STRING, "title"), "set_title", "get_title"); - ADD_PROPERTY(PropertyInfo(Variant::INT, "text_direction", PROPERTY_HINT_ENUM, "Auto,LTR,RTL,Inherited"), "set_text_direction", "get_text_direction"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "text_direction", PROPERTY_HINT_ENUM, "Auto,Left-to-Right,Right-to-Left,Inherited"), "set_text_direction", "get_text_direction"); ADD_PROPERTY(PropertyInfo(Variant::STRING, "language"), "set_language", "get_language"); ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "position_offset"), "set_position_offset", "get_position_offset"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "show_close"), "set_show_close_button", "is_close_button_visible"); diff --git a/scene/gui/graph_node.h b/scene/gui/graph_node.h index 1bc54dddb7..c70f616b47 100644 --- a/scene/gui/graph_node.h +++ b/scene/gui/graph_node.h @@ -113,11 +113,23 @@ public: void set_slot(int p_idx, bool p_enable_left, int p_type_left, const Color &p_color_left, bool p_enable_right, int p_type_right, const Color &p_color_right, const Ref<Texture2D> &p_custom_left = Ref<Texture2D>(), const Ref<Texture2D> &p_custom_right = Ref<Texture2D>()); void clear_slot(int p_idx); void clear_all_slots(); + bool is_slot_enabled_left(int p_idx) const; + void set_slot_enabled_left(int p_idx, bool p_enable_left); + + void set_slot_type_left(int p_idx, int p_type_left); int get_slot_type_left(int p_idx) const; + + void set_slot_color_left(int p_idx, const Color &p_color_left); Color get_slot_color_left(int p_idx) const; + bool is_slot_enabled_right(int p_idx) const; + void set_slot_enabled_right(int p_idx, bool p_enable_right); + + void set_slot_type_right(int p_idx, int p_type_right); int get_slot_type_right(int p_idx) const; + + void set_slot_color_right(int p_idx, const Color &p_color_right); Color get_slot_color_right(int p_idx) const; void set_title(const String &p_title); diff --git a/scene/gui/grid_container.cpp b/scene/gui/grid_container.cpp index 541925a802..a54f5eef06 100644 --- a/scene/gui/grid_container.cpp +++ b/scene/gui/grid_container.cpp @@ -33,7 +33,7 @@ void GridContainer::_notification(int p_what) { switch (p_what) { case NOTIFICATION_SORT_CHILDREN: { - Map<int, int> col_minw; // Max of min_width of all controls in each col (indexed by col). + Map<int, int> col_minw; // Max of min_width of all controls in each col (indexed by col). Map<int, int> row_minh; // Max of min_height of all controls in each row (indexed by row). Set<int> col_expanded; // Columns which have the SIZE_EXPAND flag set. Set<int> row_expanded; // Rows which have the SIZE_EXPAND flag set. diff --git a/scene/gui/item_list.cpp b/scene/gui/item_list.cpp index fa72599fb3..150980b2e9 100644 --- a/scene/gui/item_list.cpp +++ b/scene/gui/item_list.cpp @@ -406,6 +406,9 @@ void ItemList::remove_item(int p_idx) { ERR_FAIL_INDEX(p_idx, items.size()); items.remove(p_idx); + if (current == p_idx) { + current = -1; + } update(); shape_changed = true; defer_select_single = -1; @@ -530,6 +533,8 @@ Size2 ItemList::Item::get_icon_size() const { } void ItemList::_gui_input(const Ref<InputEvent> &p_event) { + ERR_FAIL_COND(p_event.is_null()); + double prev_scroll = scroll_bar->get_value(); Ref<InputEventMouseMotion> mm = p_event; @@ -540,7 +545,7 @@ void ItemList::_gui_input(const Ref<InputEvent> &p_event) { Ref<InputEventMouseButton> mb = p_event; - if (defer_select_single >= 0 && mb.is_valid() && mb->get_button_index() == BUTTON_LEFT && !mb->is_pressed()) { + if (defer_select_single >= 0 && mb.is_valid() && mb->get_button_index() == MOUSE_BUTTON_LEFT && !mb->is_pressed()) { select(defer_select_single, true); emit_signal("multi_selected", defer_select_single, true); @@ -548,7 +553,7 @@ void ItemList::_gui_input(const Ref<InputEvent> &p_event) { return; } - if (mb.is_valid() && (mb->get_button_index() == BUTTON_LEFT || (allow_rmb_select && mb->get_button_index() == BUTTON_RIGHT)) && mb->is_pressed()) { + if (mb.is_valid() && (mb->get_button_index() == MOUSE_BUTTON_LEFT || (allow_rmb_select && mb->get_button_index() == MOUSE_BUTTON_RIGHT)) && mb->is_pressed()) { search_string = ""; //any mousepress cancels Vector2 pos = mb->get_position(); Ref<StyleBox> bg = get_theme_stylebox("bg"); @@ -576,11 +581,11 @@ void ItemList::_gui_input(const Ref<InputEvent> &p_event) { if (closest != -1) { int i = closest; - if (select_mode == SELECT_MULTI && items[i].selected && mb->get_command()) { + if (select_mode == SELECT_MULTI && items[i].selected && mb->is_command_pressed()) { deselect(i); emit_signal("multi_selected", i, false); - } else if (select_mode == SELECT_MULTI && mb->get_shift() && current >= 0 && current < items.size() && current != i) { + } else if (select_mode == SELECT_MULTI && mb->is_shift_pressed() && current >= 0 && current < items.size() && current != i) { int from = current; int to = i; if (i < current) { @@ -594,21 +599,21 @@ void ItemList::_gui_input(const Ref<InputEvent> &p_event) { } } - if (mb->get_button_index() == BUTTON_RIGHT) { + if (mb->get_button_index() == MOUSE_BUTTON_RIGHT) { emit_signal("item_rmb_selected", i, get_local_mouse_position()); } } else { - if (!mb->is_doubleclick() && !mb->get_command() && select_mode == SELECT_MULTI && items[i].selectable && !items[i].disabled && items[i].selected && mb->get_button_index() == BUTTON_LEFT) { + if (!mb->is_double_click() && !mb->is_command_pressed() && select_mode == SELECT_MULTI && items[i].selectable && !items[i].disabled && items[i].selected && mb->get_button_index() == MOUSE_BUTTON_LEFT) { defer_select_single = i; return; } - if (items[i].selected && mb->get_button_index() == BUTTON_RIGHT) { + if (items[i].selected && mb->get_button_index() == MOUSE_BUTTON_RIGHT) { emit_signal("item_rmb_selected", i, get_local_mouse_position()); } else { bool selected = items[i].selected; - select(i, select_mode == SELECT_SINGLE || !mb->get_command()); + select(i, select_mode == SELECT_SINGLE || !mb->is_command_pressed()); if (!selected || allow_reselect) { if (select_mode == SELECT_SINGLE) { @@ -618,9 +623,9 @@ void ItemList::_gui_input(const Ref<InputEvent> &p_event) { } } - if (mb->get_button_index() == BUTTON_RIGHT) { + if (mb->get_button_index() == MOUSE_BUTTON_RIGHT) { emit_signal("item_rmb_selected", i, get_local_mouse_position()); - } else if (/*select_mode==SELECT_SINGLE &&*/ mb->is_doubleclick()) { + } else if (/*select_mode==SELECT_SINGLE &&*/ mb->is_double_click()) { emit_signal("item_activated", i); } } @@ -628,7 +633,7 @@ void ItemList::_gui_input(const Ref<InputEvent> &p_event) { return; } - if (mb->get_button_index() == BUTTON_RIGHT) { + if (mb->get_button_index() == MOUSE_BUTTON_RIGHT) { emit_signal("rmb_clicked", mb->get_position()); return; @@ -637,10 +642,10 @@ void ItemList::_gui_input(const Ref<InputEvent> &p_event) { // Since closest is null, more likely we clicked on empty space, so send signal to interested controls. Allows, for example, implement items deselecting. emit_signal("nothing_selected"); } - if (mb.is_valid() && mb->get_button_index() == BUTTON_WHEEL_UP && mb->is_pressed()) { + if (mb.is_valid() && mb->get_button_index() == MOUSE_BUTTON_WHEEL_UP && mb->is_pressed()) { scroll_bar->set_value(scroll_bar->get_value() - scroll_bar->get_page() * mb->get_factor() / 8); } - if (mb.is_valid() && mb->get_button_index() == BUTTON_WHEEL_DOWN && mb->is_pressed()) { + if (mb.is_valid() && mb->get_button_index() == MOUSE_BUTTON_WHEEL_DOWN && mb->is_pressed()) { scroll_bar->set_value(scroll_bar->get_value() + scroll_bar->get_page() * mb->get_factor() / 8); } diff --git a/scene/gui/label.cpp b/scene/gui/label.cpp index be73fd8f51..de0ee626b9 100644 --- a/scene/gui/label.cpp +++ b/scene/gui/label.cpp @@ -695,7 +695,7 @@ void Label::_bind_methods() { BIND_ENUM_CONSTANT(VALIGN_FILL); ADD_PROPERTY(PropertyInfo(Variant::STRING, "text", PROPERTY_HINT_MULTILINE_TEXT, "", PROPERTY_USAGE_DEFAULT_INTL), "set_text", "get_text"); - ADD_PROPERTY(PropertyInfo(Variant::INT, "text_direction", PROPERTY_HINT_ENUM, "Auto,LTR,RTL,Inherited"), "set_text_direction", "get_text_direction"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "text_direction", PROPERTY_HINT_ENUM, "Auto,Left-to-Right,Right-to-Left,Inherited"), "set_text_direction", "get_text_direction"); ADD_PROPERTY(PropertyInfo(Variant::STRING, "language"), "set_language", "get_language"); ADD_PROPERTY(PropertyInfo(Variant::INT, "align", PROPERTY_HINT_ENUM, "Left,Center,Right,Fill"), "set_align", "get_align"); ADD_PROPERTY(PropertyInfo(Variant::INT, "valign", PROPERTY_HINT_ENUM, "Top,Center,Bottom,Fill"), "set_valign", "get_valign"); diff --git a/scene/gui/line_edit.cpp b/scene/gui/line_edit.cpp index ce371e3b56..c2ed9c1a3c 100644 --- a/scene/gui/line_edit.cpp +++ b/scene/gui/line_edit.cpp @@ -51,13 +51,13 @@ void LineEdit::_swap_current_input_direction() { } else { input_direction = TEXT_DIRECTION_LTR; } - set_cursor_position(get_cursor_position()); + set_caret_column(get_caret_column()); update(); } -void LineEdit::_move_cursor_left(bool p_select, bool p_move_by_word) { +void LineEdit::_move_caret_left(bool p_select, bool p_move_by_word) { if (selection.enabled && !p_select) { - set_cursor_position(selection.begin); + set_caret_column(selection.begin); deselect(); return; } @@ -65,7 +65,7 @@ void LineEdit::_move_cursor_left(bool p_select, bool p_move_by_word) { shift_selection_check_pre(p_select); if (p_move_by_word) { - int cc = cursor_pos; + int cc = caret_column; Vector<Vector2i> words = TS->shaped_text_get_word_breaks(text_rid); for (int i = words.size() - 1; i >= 0; i--) { @@ -75,21 +75,21 @@ void LineEdit::_move_cursor_left(bool p_select, bool p_move_by_word) { } } - set_cursor_position(cc); + set_caret_column(cc); } else { - if (mid_grapheme_caret_enabled) { - set_cursor_position(get_cursor_position() - 1); + if (caret_mid_grapheme_enabled) { + set_caret_column(get_caret_column() - 1); } else { - set_cursor_position(TS->shaped_text_prev_grapheme_pos(text_rid, get_cursor_position())); + set_caret_column(TS->shaped_text_prev_grapheme_pos(text_rid, get_caret_column())); } } shift_selection_check_post(p_select); } -void LineEdit::_move_cursor_right(bool p_select, bool p_move_by_word) { +void LineEdit::_move_caret_right(bool p_select, bool p_move_by_word) { if (selection.enabled && !p_select) { - set_cursor_position(selection.end); + set_caret_column(selection.end); deselect(); return; } @@ -97,7 +97,7 @@ void LineEdit::_move_cursor_right(bool p_select, bool p_move_by_word) { shift_selection_check_pre(p_select); if (p_move_by_word) { - int cc = cursor_pos; + int cc = caret_column; Vector<Vector2i> words = TS->shaped_text_get_word_breaks(text_rid); for (int i = 0; i < words.size(); i++) { @@ -107,27 +107,27 @@ void LineEdit::_move_cursor_right(bool p_select, bool p_move_by_word) { } } - set_cursor_position(cc); + set_caret_column(cc); } else { - if (mid_grapheme_caret_enabled) { - set_cursor_position(get_cursor_position() + 1); + if (caret_mid_grapheme_enabled) { + set_caret_column(get_caret_column() + 1); } else { - set_cursor_position(TS->shaped_text_next_grapheme_pos(text_rid, get_cursor_position())); + set_caret_column(TS->shaped_text_next_grapheme_pos(text_rid, get_caret_column())); } } shift_selection_check_post(p_select); } -void LineEdit::_move_cursor_start(bool p_select) { +void LineEdit::_move_caret_start(bool p_select) { shift_selection_check_pre(p_select); - set_cursor_position(0); + set_caret_column(0); shift_selection_check_post(p_select); } -void LineEdit::_move_cursor_end(bool p_select) { +void LineEdit::_move_caret_end(bool p_select) { shift_selection_check_pre(p_select); - set_cursor_position(text.length()); + set_caret_column(text.length()); shift_selection_check_post(p_select); } @@ -138,7 +138,7 @@ void LineEdit::_backspace(bool p_word, bool p_all_to_left) { if (p_all_to_left) { deselect(); - text = text.substr(0, cursor_pos); + text = text.substr(0, caret_column); _text_changed(); return; } @@ -149,18 +149,19 @@ void LineEdit::_backspace(bool p_word, bool p_all_to_left) { } if (p_word) { - int cc = cursor_pos; + int cc = caret_column; Vector<Vector2i> words = TS->shaped_text_get_word_breaks(text_rid); for (int i = words.size() - 1; i >= 0; i--) { if (words[i].x < cc) { cc = words[i].x; + break; } } - delete_text(cc, cursor_pos); + delete_text(cc, caret_column); - set_cursor_position(cc); + set_caret_column(cc); } else { delete_char(); } @@ -173,9 +174,9 @@ void LineEdit::_delete(bool p_word, bool p_all_to_right) { if (p_all_to_right) { deselect(); - text = text.substr(cursor_pos, text.length() - cursor_pos); + text = text.substr(caret_column, text.length() - caret_column); _shape(); - set_cursor_position(0); + set_caret_column(0); _text_changed(); return; } @@ -187,12 +188,12 @@ void LineEdit::_delete(bool p_word, bool p_all_to_right) { int text_len = text.length(); - if (cursor_pos == text_len) { + if (caret_column == text_len) { return; // Nothing to do. } if (p_word) { - int cc = cursor_pos; + int cc = caret_column; Vector<Vector2i> words = TS->shaped_text_get_word_breaks(text_rid); for (int i = 0; i < words.size(); i++) { if (words[i].y > cc) { @@ -201,20 +202,23 @@ void LineEdit::_delete(bool p_word, bool p_all_to_right) { } } - delete_text(cursor_pos, cc); + delete_text(caret_column, cc); + set_caret_column(caret_column); } else { - if (mid_grapheme_caret_enabled) { - set_cursor_position(cursor_pos + 1); + if (caret_mid_grapheme_enabled) { + set_caret_column(caret_column + 1); delete_char(); } else { - int cc = cursor_pos; - set_cursor_position(TS->shaped_text_next_grapheme_pos(text_rid, cursor_pos)); - delete_text(cc, cursor_pos); + int cc = caret_column; + set_caret_column(TS->shaped_text_next_grapheme_pos(text_rid, caret_column)); + delete_text(cc, caret_column); } } } void LineEdit::_gui_input(Ref<InputEvent> p_event) { + ERR_FAIL_COND(p_event.is_null()); + Ref<InputEventMouseButton> b = p_event; if (b.is_valid()) { @@ -222,7 +226,7 @@ void LineEdit::_gui_input(Ref<InputEvent> p_event) { // Ignore mouse clicks in IME input mode. return; } - if (b->is_pressed() && b->get_button_index() == BUTTON_RIGHT && context_menu_enabled) { + if (b->is_pressed() && b->get_button_index() == MOUSE_BUTTON_RIGHT && context_menu_enabled) { menu->set_position(get_screen_transform().xform(get_local_mouse_position())); menu->set_size(Vector2(1, 1)); _generate_context_menu(); @@ -232,7 +236,7 @@ void LineEdit::_gui_input(Ref<InputEvent> p_event) { return; } - if (b->get_button_index() != BUTTON_LEFT) { + if (b->get_button_index() != MOUSE_BUTTON_LEFT) { return; } @@ -246,35 +250,35 @@ void LineEdit::_gui_input(Ref<InputEvent> p_event) { return; } - shift_selection_check_pre(b->get_shift()); + shift_selection_check_pre(b->is_shift_pressed()); - set_cursor_at_pixel_pos(b->get_position().x); + set_caret_at_pixel_pos(b->get_position().x); - if (b->get_shift()) { - selection_fill_at_cursor(); + if (b->is_shift_pressed()) { + selection_fill_at_caret(); selection.creating = true; } else { if (selecting_enabled) { - if (!b->is_doubleclick() && (OS::get_singleton()->get_ticks_msec() - selection.last_dblclk) < 600) { + if (!b->is_double_click() && (OS::get_singleton()->get_ticks_msec() - selection.last_dblclk) < 600) { // Triple-click select all. selection.enabled = true; selection.begin = 0; selection.end = text.length(); - selection.doubleclick = true; + selection.double_click = true; selection.last_dblclk = 0; - cursor_pos = selection.begin; - } else if (b->is_doubleclick()) { + caret_column = selection.begin; + } else if (b->is_double_click()) { // Double-click select word. Vector<Vector2i> words = TS->shaped_text_get_word_breaks(text_rid); for (int i = 0; i < words.size(); i++) { - if (words[i].x < cursor_pos && words[i].y > cursor_pos) { + if (words[i].x < caret_column && words[i].y > caret_column) { selection.enabled = true; selection.begin = words[i].x; selection.end = words[i].y; - selection.doubleclick = true; + selection.double_click = true; selection.last_dblclk = OS::get_singleton()->get_ticks_msec(); - cursor_pos = selection.end; + caret_column = selection.end; break; } } @@ -283,9 +287,9 @@ void LineEdit::_gui_input(Ref<InputEvent> p_event) { selection.drag_attempt = false; - if ((cursor_pos < selection.begin) || (cursor_pos > selection.end) || !selection.enabled) { + if ((caret_column < selection.begin) || (caret_column > selection.end) || !selection.enabled) { deselect(); - selection.cursor_start = cursor_pos; + selection.start_column = caret_column; selection.creating = true; } else if (selection.enabled) { selection.drag_attempt = true; @@ -304,11 +308,11 @@ void LineEdit::_gui_input(Ref<InputEvent> p_event) { } } - if ((!selection.creating) && (!selection.doubleclick)) { + if ((!selection.creating) && (!selection.double_click)) { deselect(); } selection.creating = false; - selection.doubleclick = false; + selection.double_click = false; show_virtual_keyboard(); } @@ -327,10 +331,10 @@ void LineEdit::_gui_input(Ref<InputEvent> p_event) { } } - if (m->get_button_mask() & BUTTON_LEFT) { + if (m->get_button_mask() & MOUSE_BUTTON_LEFT) { if (selection.creating) { - set_cursor_at_pixel_pos(m->get_position().x); - selection_fill_at_cursor(); + set_caret_at_pixel_pos(m->get_position().x); + selection_fill_at_caret(); } } } @@ -344,7 +348,7 @@ void LineEdit::_gui_input(Ref<InputEvent> p_event) { if (context_menu_enabled) { if (k->is_action("ui_menu", true)) { - Point2 pos = Point2(get_cursor_pixel_pos().x, (get_size().y + get_theme_font("font")->get_height(get_theme_font_size("font_size"))) / 2); + Point2 pos = Point2(get_caret_pixel_pos().x, (get_size().y + get_theme_font("font")->get_height(get_theme_font_size("font_size"))) / 2); menu->set_position(get_global_transform().xform(pos)); menu->set_size(Vector2(1, 1)); _generate_context_menu(); @@ -438,39 +442,39 @@ void LineEdit::_gui_input(Ref<InputEvent> p_event) { // Cursor Movement k = k->duplicate(); - bool shift_pressed = k->get_shift(); + bool shift_pressed = k->is_shift_pressed(); // Remove shift or else actions will not match. Use above variable for selection. - k->set_shift(false); + k->set_shift_pressed(false); if (k->is_action("ui_text_caret_word_left", true)) { - _move_cursor_left(shift_pressed, true); + _move_caret_left(shift_pressed, true); accept_event(); return; } if (k->is_action("ui_text_caret_left", true)) { - _move_cursor_left(shift_pressed); + _move_caret_left(shift_pressed); accept_event(); return; } if (k->is_action("ui_text_caret_word_right", true)) { - _move_cursor_right(shift_pressed, true); + _move_caret_right(shift_pressed, true); accept_event(); return; } if (k->is_action("ui_text_caret_right", true)) { - _move_cursor_right(shift_pressed, false); + _move_caret_right(shift_pressed, false); accept_event(); return; } // Up = Home, Down = End if (k->is_action("ui_text_caret_up", true) || k->is_action("ui_text_caret_line_start", true) || k->is_action("ui_text_caret_page_up", true)) { - _move_cursor_start(shift_pressed); + _move_caret_start(shift_pressed); accept_event(); return; } if (k->is_action("ui_text_caret_down", true) || k->is_action("ui_text_caret_line_end", true) || k->is_action("ui_text_caret_page_down", true)) { - _move_cursor_end(shift_pressed); + _move_caret_end(shift_pressed); accept_event(); return; } @@ -486,14 +490,14 @@ void LineEdit::_gui_input(Ref<InputEvent> p_event) { // Allow unicode handling if: // * No Modifiers are pressed (except shift) - bool allow_unicode_handling = !(k->get_command() || k->get_control() || k->get_alt() || k->get_metakey()); + bool allow_unicode_handling = !(k->is_command_pressed() || k->is_ctrl_pressed() || k->is_alt_pressed() || k->is_meta_pressed()); if (allow_unicode_handling && editable && k->get_unicode() >= 32) { // Handle Unicode (if no modifiers active) selection_delete(); char32_t ucodestr[2] = { (char32_t)k->get_unicode(), 0 }; int prev_len = text.length(); - append_at_cursor(ucodestr); + insert_text_at_caret(ucodestr); if (text.length() != prev_len) { _text_changed(); } @@ -540,20 +544,20 @@ void LineEdit::drop_data(const Point2 &p_point, const Variant &p_data) { Control::drop_data(p_point, p_data); if (p_data.get_type() == Variant::STRING) { - set_cursor_at_pixel_pos(p_point.x); + set_caret_at_pixel_pos(p_point.x); int selected = selection.end - selection.begin; text.erase(selection.begin, selected); _shape(); - append_at_cursor(p_data); - selection.begin = cursor_pos - selected; - selection.end = cursor_pos; + insert_text_at_caret(p_data); + selection.begin = caret_column - selected; + selection.end = caret_column; } } Control::CursorShape LineEdit::get_cursor_shape(const Point2 &p_pos) const { - if (!text.is_empty() && is_editable() && _is_over_clear_button(p_pos)) { + if ((!text.is_empty() && is_editable() && _is_over_clear_button(p_pos)) || (!is_editable() && (!is_selecting_enabled() || text.is_empty()))) { return CURSOR_ARROW; } return Control::get_cursor_shape(p_pos); @@ -573,8 +577,8 @@ void LineEdit::_notification(int p_what) { #ifdef TOOLS_ENABLED case NOTIFICATION_ENTER_TREE: { if (Engine::get_singleton()->is_editor_hint() && !get_tree()->is_node_being_edited(this)) { - cursor_set_blink_enabled(EDITOR_DEF("text_editor/cursor/caret_blink", false)); - cursor_set_blink_speed(EDITOR_DEF("text_editor/cursor/caret_blink_speed", 0.65)); + set_caret_blink_enabled(EDITOR_DEF("text_editor/cursor/caret_blink", false)); + set_caret_blink_speed(EDITOR_DEF("text_editor/cursor/caret_blink_speed", 0.65)); if (!EditorSettings::get_singleton()->is_connected("settings_changed", callable_mp(this, &LineEdit::_editor_settings_changed))) { EditorSettings::get_singleton()->connect("settings_changed", callable_mp(this, &LineEdit::_editor_settings_changed)); @@ -585,7 +589,7 @@ void LineEdit::_notification(int p_what) { case NOTIFICATION_RESIZED: { _fit_to_width(); scroll_offset = 0; - set_cursor_position(get_cursor_position()); + set_caret_column(get_caret_column()); } break; case NOTIFICATION_LAYOUT_DIRECTION_CHANGED: case NOTIFICATION_THEME_CHANGED: { @@ -672,7 +676,7 @@ void LineEdit::_notification(int p_what) { Color selection_color = get_theme_color("selection_color"); Color font_color = is_editable() ? get_theme_color("font_color") : get_theme_color("font_uneditable_color"); Color font_selected_color = get_theme_color("font_selected_color"); - Color cursor_color = get_theme_color("cursor_color"); + Color caret_color = get_theme_color("caret_color"); // Draw placeholder color. if (using_placeholder) { @@ -776,7 +780,7 @@ void LineEdit::_notification(int p_what) { // Normal caret. Rect2 l_caret, t_caret; TextServer::Direction l_dir, t_dir; - TS->shaped_text_get_carets(text_rid, cursor_pos, l_caret, l_dir, t_caret, t_dir); + TS->shaped_text_get_carets(text_rid, caret_column, l_caret, l_dir, t_caret, t_dir); if (l_caret == Rect2() && t_caret == Rect2()) { // No carets, add one at the start. @@ -789,28 +793,28 @@ void LineEdit::_notification(int p_what) { l_dir = TextServer::DIRECTION_LTR; l_caret = Rect2(Vector2(x_ofs, y), Size2(caret_width, h)); } - RenderingServer::get_singleton()->canvas_item_add_rect(ci, l_caret, cursor_color); + RenderingServer::get_singleton()->canvas_item_add_rect(ci, l_caret, caret_color); } else { if (l_caret != Rect2() && l_dir == TextServer::DIRECTION_AUTO) { // Draw extra marker on top of mid caret. Rect2 trect = Rect2(l_caret.position.x - 3 * caret_width, l_caret.position.y, 6 * caret_width, caret_width); trect.position += ofs; - RenderingServer::get_singleton()->canvas_item_add_rect(ci, trect, cursor_color); + RenderingServer::get_singleton()->canvas_item_add_rect(ci, trect, caret_color); } l_caret.position += ofs; l_caret.size.x = caret_width; - RenderingServer::get_singleton()->canvas_item_add_rect(ci, l_caret, cursor_color); + RenderingServer::get_singleton()->canvas_item_add_rect(ci, l_caret, caret_color); t_caret.position += ofs; t_caret.size.x = caret_width; - RenderingServer::get_singleton()->canvas_item_add_rect(ci, t_caret, cursor_color); + RenderingServer::get_singleton()->canvas_item_add_rect(ci, t_caret, caret_color); } } else { { // IME intermediate text range. - Vector<Vector2> sel = TS->shaped_text_get_selection(text_rid, cursor_pos, cursor_pos + ime_text.length()); + Vector<Vector2> sel = TS->shaped_text_get_selection(text_rid, caret_column, caret_column + ime_text.length()); for (int i = 0; i < sel.size(); i++) { Rect2 rect = Rect2(sel[i].x + ofs.x, ofs.y, sel[i].y - sel[i].x, text_height); if (rect.position.x + rect.size.x <= x_ofs || rect.position.x > ofs_max) { @@ -823,12 +827,12 @@ void LineEdit::_notification(int p_what) { rect.size.x = ofs_max - rect.position.x; } rect.size.y = caret_width; - RenderingServer::get_singleton()->canvas_item_add_rect(ci, rect, cursor_color); + RenderingServer::get_singleton()->canvas_item_add_rect(ci, rect, caret_color); } } { // IME caret. - Vector<Vector2> sel = TS->shaped_text_get_selection(text_rid, cursor_pos + ime_selection.x, cursor_pos + ime_selection.x + ime_selection.y); + Vector<Vector2> sel = TS->shaped_text_get_selection(text_rid, caret_column + ime_selection.x, caret_column + ime_selection.x + ime_selection.y); for (int i = 0; i < sel.size(); i++) { Rect2 rect = Rect2(sel[i].x + ofs.x, ofs.y, sel[i].y - sel[i].x, text_height); if (rect.position.x + rect.size.x <= x_ofs || rect.position.x > ofs_max) { @@ -841,7 +845,7 @@ void LineEdit::_notification(int p_what) { rect.size.x = ofs_max - rect.position.x; } rect.size.y = caret_width * 3; - RenderingServer::get_singleton()->canvas_item_add_rect(ci, rect, cursor_color); + RenderingServer::get_singleton()->canvas_item_add_rect(ci, rect, caret_color); } } } @@ -867,8 +871,8 @@ void LineEdit::_notification(int p_what) { if (get_viewport()->get_window_id() != DisplayServer::INVALID_WINDOW_ID && DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_IME)) { DisplayServer::get_singleton()->window_set_ime_active(true, get_viewport()->get_window_id()); - Point2 cursor_pos = Point2(get_cursor_position(), 1) * get_minimum_size().height; - DisplayServer::get_singleton()->window_set_ime_position(get_global_position() + cursor_pos, get_viewport()->get_window_id()); + Point2 caret_column = Point2(get_caret_column(), 1) * get_minimum_size().height; + DisplayServer::get_singleton()->window_set_ime_position(get_global_position() + caret_column, get_viewport()->get_window_id()); } show_virtual_keyboard(); @@ -885,7 +889,7 @@ void LineEdit::_notification(int p_what) { ime_text = ""; ime_selection = Point2(); _shape(); - set_cursor_position(cursor_pos); // Update scroll_offset + set_caret_column(caret_column); // Update scroll_offset if (DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_VIRTUAL_KEYBOARD) && virtual_keyboard_enabled) { DisplayServer::get_singleton()->virtual_keyboard_hide(); @@ -897,7 +901,7 @@ void LineEdit::_notification(int p_what) { ime_text = DisplayServer::get_singleton()->ime_get_text(); ime_selection = DisplayServer::get_singleton()->ime_get_selection(); _shape(); - set_cursor_position(cursor_pos); // Update scroll_offset + set_caret_column(caret_column); // Update scroll_offset update(); } @@ -931,7 +935,7 @@ void LineEdit::paste_text() { if (selection.enabled) { selection_delete(); } - append_at_cursor(paste_buffer); + insert_text_at_caret(paste_buffer); if (!text_changed_dirty) { if (is_inside_tree() && text.length() != prev_len) { @@ -959,7 +963,7 @@ void LineEdit::undo() { TextOperation op = undo_stack_pos->get(); text = op.text; scroll_offset = op.scroll_offset; - set_cursor_position(op.cursor_pos); + set_caret_column(op.caret_column); _shape(); _emit_text_change(); @@ -980,7 +984,7 @@ void LineEdit::redo() { TextOperation op = undo_stack_pos->get(); text = op.text; scroll_offset = op.scroll_offset; - set_cursor_position(op.cursor_pos); + set_caret_column(op.caret_column); _shape(); _emit_text_change(); @@ -988,7 +992,7 @@ void LineEdit::redo() { void LineEdit::shift_selection_check_pre(bool p_shift) { if (!selection.enabled && p_shift) { - selection.cursor_start = cursor_pos; + selection.start_column = caret_column; } if (!p_shift) { deselect(); @@ -997,11 +1001,11 @@ void LineEdit::shift_selection_check_pre(bool p_shift) { void LineEdit::shift_selection_check_post(bool p_shift) { if (p_shift) { - selection_fill_at_cursor(); + selection_fill_at_caret(); } } -void LineEdit::set_cursor_at_pixel_pos(int p_x) { +void LineEdit::set_caret_at_pixel_pos(int p_x) { Ref<StyleBox> style = get_theme_stylebox("normal"); bool rtl = is_layout_rtl(); @@ -1046,10 +1050,10 @@ void LineEdit::set_cursor_at_pixel_pos(int p_x) { } int ofs = TS->shaped_text_hit_test_position(text_rid, p_x - x_ofs - scroll_offset); - set_cursor_position(ofs); + set_caret_column(ofs); } -Vector2i LineEdit::get_cursor_pixel_pos() { +Vector2i LineEdit::get_caret_pixel_pos() { Ref<StyleBox> style = get_theme_stylebox("normal"); bool rtl = is_layout_rtl(); @@ -1098,9 +1102,9 @@ Vector2i LineEdit::get_cursor_pixel_pos() { TextServer::Direction l_dir, t_dir; // Get position of the start of caret. if (ime_text.length() != 0 && ime_selection.x != 0) { - TS->shaped_text_get_carets(text_rid, cursor_pos + ime_selection.x, l_caret, l_dir, t_caret, t_dir); + TS->shaped_text_get_carets(text_rid, caret_column + ime_selection.x, l_caret, l_dir, t_caret, t_dir); } else { - TS->shaped_text_get_carets(text_rid, cursor_pos, l_caret, l_dir, t_caret, t_dir); + TS->shaped_text_get_carets(text_rid, caret_column, l_caret, l_dir, t_caret, t_dir); } if ((l_caret != Rect2() && (l_dir == TextServer::DIRECTION_AUTO || l_dir == (TextServer::Direction)input_direction)) || (t_caret == Rect2())) { @@ -1112,9 +1116,9 @@ Vector2i LineEdit::get_cursor_pixel_pos() { // Get position of the end of caret. if (ime_text.length() != 0) { if (ime_selection.y != 0) { - TS->shaped_text_get_carets(text_rid, cursor_pos + ime_selection.x + ime_selection.y, l_caret, l_dir, t_caret, t_dir); + TS->shaped_text_get_carets(text_rid, caret_column + ime_selection.x + ime_selection.y, l_caret, l_dir, t_caret, t_dir); } else { - TS->shaped_text_get_carets(text_rid, cursor_pos + ime_text.size(), l_caret, l_dir, t_caret, t_dir); + TS->shaped_text_get_carets(text_rid, caret_column + ime_text.size(), l_caret, l_dir, t_caret, t_dir); } if ((l_caret != Rect2() && (l_dir == TextServer::DIRECTION_AUTO || l_dir == (TextServer::Direction)input_direction)) || (t_caret == Rect2())) { ret.y = x_ofs + l_caret.position.x + scroll_offset; @@ -1128,19 +1132,19 @@ Vector2i LineEdit::get_cursor_pixel_pos() { return ret; } -void LineEdit::set_mid_grapheme_caret_enabled(const bool p_enabled) { - mid_grapheme_caret_enabled = p_enabled; +void LineEdit::set_caret_mid_grapheme_enabled(const bool p_enabled) { + caret_mid_grapheme_enabled = p_enabled; } -bool LineEdit::get_mid_grapheme_caret_enabled() const { - return mid_grapheme_caret_enabled; +bool LineEdit::is_caret_mid_grapheme_enabled() const { + return caret_mid_grapheme_enabled; } -bool LineEdit::cursor_get_blink_enabled() const { +bool LineEdit::is_caret_blink_enabled() const { return caret_blink_enabled; } -void LineEdit::cursor_set_blink_enabled(const bool p_enabled) { +void LineEdit::set_caret_blink_enabled(const bool p_enabled) { caret_blink_enabled = p_enabled; if (has_focus() || caret_force_displayed) { @@ -1158,21 +1162,21 @@ void LineEdit::cursor_set_blink_enabled(const bool p_enabled) { notify_property_list_changed(); } -bool LineEdit::cursor_get_force_displayed() const { +bool LineEdit::is_caret_force_displayed() const { return caret_force_displayed; } -void LineEdit::cursor_set_force_displayed(const bool p_enabled) { +void LineEdit::set_caret_force_displayed(const bool p_enabled) { caret_force_displayed = p_enabled; - cursor_set_blink_enabled(caret_blink_enabled); + set_caret_blink_enabled(caret_blink_enabled); update(); } -float LineEdit::cursor_get_blink_speed() const { +float LineEdit::get_caret_blink_speed() const { return caret_blink_timer->get_wait_time(); } -void LineEdit::cursor_set_blink_speed(const float p_speed) { +void LineEdit::set_caret_blink_speed(const float p_speed) { ERR_FAIL_COND(p_speed <= 0); caret_blink_timer->set_wait_time(p_speed); } @@ -1196,14 +1200,14 @@ void LineEdit::_toggle_draw_caret() { } void LineEdit::delete_char() { - if ((text.length() <= 0) || (cursor_pos == 0)) { + if ((text.length() <= 0) || (caret_column == 0)) { return; } - text.erase(cursor_pos - 1, 1); + text.erase(caret_column - 1, 1); _shape(); - set_cursor_position(get_cursor_position() - 1); + set_caret_column(get_caret_column() - 1); _text_changed(); } @@ -1215,10 +1219,10 @@ void LineEdit::delete_text(int p_from_column, int p_to_column) { text.erase(p_from_column, p_to_column - p_from_column); _shape(); - cursor_pos -= CLAMP(cursor_pos - p_from_column, 0, p_to_column - p_from_column); + caret_column -= CLAMP(caret_column - p_from_column, 0, p_to_column - p_from_column); - if (cursor_pos >= text.length()) { - cursor_pos = text.length(); + if (caret_column >= text.length()) { + caret_column = text.length(); } if (!text_changed_dirty) { @@ -1231,10 +1235,11 @@ void LineEdit::delete_text(int p_from_column, int p_to_column) { void LineEdit::set_text(String p_text) { clear_internal(); - append_at_cursor(p_text); + insert_text_at_caret(p_text); + _create_undo_state(); update(); - cursor_pos = 0; + caret_column = 0; scroll_offset = 0; } @@ -1344,7 +1349,7 @@ void LineEdit::show_virtual_keyboard() { if (selection.enabled) { DisplayServer::get_singleton()->virtual_keyboard_show(text, get_global_rect(), false, max_length, selection.begin, selection.end); } else { - DisplayServer::get_singleton()->virtual_keyboard_show(text, get_global_rect(), false, max_length, cursor_pos); + DisplayServer::get_singleton()->virtual_keyboard_show(text, get_global_rect(), false, max_length, caret_column); } } } @@ -1373,16 +1378,16 @@ float LineEdit::get_placeholder_alpha() const { return placeholder_alpha; } -void LineEdit::set_cursor_position(int p_pos) { - if (p_pos > (int)text.length()) { - p_pos = text.length(); +void LineEdit::set_caret_column(int p_column) { + if (p_column > (int)text.length()) { + p_column = text.length(); } - if (p_pos < 0) { - p_pos = 0; + if (p_column < 0) { + p_column = 0; } - cursor_pos = p_pos; + caret_column = p_column; // Fit to window. @@ -1437,7 +1442,7 @@ void LineEdit::set_cursor_position(int p_pos) { } // Note: Use two coordinates to fit IME input range. - Vector2i primary_catret_offset = get_cursor_pixel_pos(); + Vector2i primary_catret_offset = get_caret_pixel_pos(); if (MIN(primary_catret_offset.x, primary_catret_offset.y) <= x_ofs) { scroll_offset += (x_ofs - MIN(primary_catret_offset.x, primary_catret_offset.y)); @@ -1449,8 +1454,8 @@ void LineEdit::set_cursor_position(int p_pos) { update(); } -int LineEdit::get_cursor_position() const { - return cursor_pos; +int LineEdit::get_caret_column() const { + return caret_column; } void LineEdit::set_scroll_offset(int p_pos) { @@ -1464,17 +1469,17 @@ int LineEdit::get_scroll_offset() const { return scroll_offset; } -void LineEdit::append_at_cursor(String p_text) { +void LineEdit::insert_text_at_caret(String p_text) { if ((max_length <= 0) || (text.length() + p_text.length() <= max_length)) { - String pre = text.substr(0, cursor_pos); - String post = text.substr(cursor_pos, text.length() - cursor_pos); + String pre = text.substr(0, caret_column); + String post = text.substr(caret_column, text.length() - caret_column); text = pre + p_text + post; _shape(); - TextServer::Direction dir = TS->shaped_text_get_dominant_direciton_in_range(text_rid, cursor_pos, cursor_pos + p_text.length()); + TextServer::Direction dir = TS->shaped_text_get_dominant_direciton_in_range(text_rid, caret_column, caret_column + p_text.length()); if (dir != TextServer::DIRECTION_AUTO) { input_direction = (TextDirection)dir; } - set_cursor_position(cursor_pos + p_text.length()); + set_caret_column(caret_column + p_text.length()); } else { emit_signal("text_change_rejected"); } @@ -1483,7 +1488,7 @@ void LineEdit::append_at_cursor(String p_text) { void LineEdit::clear_internal() { deselect(); _clear_undo_stack(); - cursor_pos = 0; + caret_column = 0; scroll_offset = 0; undo_text = ""; text = ""; @@ -1503,7 +1508,7 @@ Size2 LineEdit::get_minimum_size() const { min_size.width = get_theme_constant("minimum_character_width") * em_space_size; if (expand_to_text_length) { - // Add a space because some fonts are too exact, and because cursor needs a bit more when at the end. + // Add a space because some fonts are too exact, and because caret needs a bit more when at the end. min_size.width = MAX(min_size.width, full_width + em_space_size); } @@ -1524,10 +1529,10 @@ Size2 LineEdit::get_minimum_size() const { void LineEdit::deselect() { selection.begin = 0; selection.end = 0; - selection.cursor_start = 0; + selection.start_column = 0; selection.enabled = false; selection.creating = false; - selection.doubleclick = false; + selection.double_click = false; update(); } @@ -1549,13 +1554,13 @@ int LineEdit::get_max_length() const { return max_length; } -void LineEdit::selection_fill_at_cursor() { +void LineEdit::selection_fill_at_caret() { if (!selecting_enabled) { return; } - selection.begin = cursor_pos; - selection.end = selection.cursor_start; + selection.begin = caret_column; + selection.end = selection.start_column; if (selection.end < selection.begin) { int aux = selection.end; @@ -1654,7 +1659,7 @@ void LineEdit::select(int p_from, int p_to) { selection.begin = p_from; selection.end = p_to; selection.creating = false; - selection.doubleclick = false; + selection.double_click = false; update(); } @@ -1712,82 +1717,82 @@ void LineEdit::menu_option(int p_option) { } break; case MENU_INSERT_LRM: { if (editable) { - append_at_cursor(String::chr(0x200E)); + insert_text_at_caret(String::chr(0x200E)); } } break; case MENU_INSERT_RLM: { if (editable) { - append_at_cursor(String::chr(0x200F)); + insert_text_at_caret(String::chr(0x200F)); } } break; case MENU_INSERT_LRE: { if (editable) { - append_at_cursor(String::chr(0x202A)); + insert_text_at_caret(String::chr(0x202A)); } } break; case MENU_INSERT_RLE: { if (editable) { - append_at_cursor(String::chr(0x202B)); + insert_text_at_caret(String::chr(0x202B)); } } break; case MENU_INSERT_LRO: { if (editable) { - append_at_cursor(String::chr(0x202D)); + insert_text_at_caret(String::chr(0x202D)); } } break; case MENU_INSERT_RLO: { if (editable) { - append_at_cursor(String::chr(0x202E)); + insert_text_at_caret(String::chr(0x202E)); } } break; case MENU_INSERT_PDF: { if (editable) { - append_at_cursor(String::chr(0x202C)); + insert_text_at_caret(String::chr(0x202C)); } } break; case MENU_INSERT_ALM: { if (editable) { - append_at_cursor(String::chr(0x061C)); + insert_text_at_caret(String::chr(0x061C)); } } break; case MENU_INSERT_LRI: { if (editable) { - append_at_cursor(String::chr(0x2066)); + insert_text_at_caret(String::chr(0x2066)); } } break; case MENU_INSERT_RLI: { if (editable) { - append_at_cursor(String::chr(0x2067)); + insert_text_at_caret(String::chr(0x2067)); } } break; case MENU_INSERT_FSI: { if (editable) { - append_at_cursor(String::chr(0x2068)); + insert_text_at_caret(String::chr(0x2068)); } } break; case MENU_INSERT_PDI: { if (editable) { - append_at_cursor(String::chr(0x2069)); + insert_text_at_caret(String::chr(0x2069)); } } break; case MENU_INSERT_ZWJ: { if (editable) { - append_at_cursor(String::chr(0x200D)); + insert_text_at_caret(String::chr(0x200D)); } } break; case MENU_INSERT_ZWNJ: { if (editable) { - append_at_cursor(String::chr(0x200C)); + insert_text_at_caret(String::chr(0x200C)); } } break; case MENU_INSERT_WJ: { if (editable) { - append_at_cursor(String::chr(0x2060)); + insert_text_at_caret(String::chr(0x2060)); } } break; case MENU_INSERT_SHY: { if (editable) { - append_at_cursor(String::chr(0x00AD)); + insert_text_at_caret(String::chr(0x00AD)); } } } @@ -1807,18 +1812,18 @@ PopupMenu *LineEdit::get_menu() const { void LineEdit::_editor_settings_changed() { #ifdef TOOLS_ENABLED - cursor_set_blink_enabled(EDITOR_DEF("text_editor/cursor/caret_blink", false)); - cursor_set_blink_speed(EDITOR_DEF("text_editor/cursor/caret_blink_speed", 0.65)); + set_caret_blink_enabled(EDITOR_DEF("text_editor/cursor/caret_blink", false)); + set_caret_blink_speed(EDITOR_DEF("text_editor/cursor/caret_blink_speed", 0.65)); #endif } -void LineEdit::set_expand_to_text_length(bool p_enabled) { +void LineEdit::set_expand_to_text_length_enabled(bool p_enabled) { expand_to_text_length = p_enabled; minimum_size_changed(); - set_cursor_position(cursor_pos); + set_caret_column(caret_column); } -bool LineEdit::get_expand_to_text_length() const { +bool LineEdit::is_expand_to_text_length_enabled() const { return expand_to_text_length; } @@ -1903,7 +1908,7 @@ void LineEdit::_shape() { t = secret_character.repeat(text.length() + ime_text.length()); } else { if (ime_text.length() > 0) { - t = text.substr(0, cursor_pos) + ime_text + text.substr(cursor_pos, text.length()); + t = text.substr(0, caret_column) + ime_text + text.substr(caret_column, text.length()); } else { t = text; } @@ -1968,7 +1973,7 @@ void LineEdit::_clear_undo_stack() { void LineEdit::_create_undo_state() { TextOperation op; op.text = text; - op.cursor_pos = cursor_pos; + op.caret_column = caret_column; op.scroll_offset = scroll_offset; undo_stack.push_back(op); } @@ -2113,23 +2118,23 @@ void LineEdit::_bind_methods() { ClassDB::bind_method(D_METHOD("get_placeholder"), &LineEdit::get_placeholder); ClassDB::bind_method(D_METHOD("set_placeholder_alpha", "alpha"), &LineEdit::set_placeholder_alpha); ClassDB::bind_method(D_METHOD("get_placeholder_alpha"), &LineEdit::get_placeholder_alpha); - ClassDB::bind_method(D_METHOD("set_cursor_position", "position"), &LineEdit::set_cursor_position); - ClassDB::bind_method(D_METHOD("get_cursor_position"), &LineEdit::get_cursor_position); + ClassDB::bind_method(D_METHOD("set_caret_column", "position"), &LineEdit::set_caret_column); + ClassDB::bind_method(D_METHOD("get_caret_column"), &LineEdit::get_caret_column); ClassDB::bind_method(D_METHOD("get_scroll_offset"), &LineEdit::get_scroll_offset); - ClassDB::bind_method(D_METHOD("set_expand_to_text_length", "enabled"), &LineEdit::set_expand_to_text_length); - ClassDB::bind_method(D_METHOD("get_expand_to_text_length"), &LineEdit::get_expand_to_text_length); - ClassDB::bind_method(D_METHOD("cursor_set_blink_enabled", "enabled"), &LineEdit::cursor_set_blink_enabled); - ClassDB::bind_method(D_METHOD("cursor_get_blink_enabled"), &LineEdit::cursor_get_blink_enabled); - ClassDB::bind_method(D_METHOD("set_mid_grapheme_caret_enabled", "enabled"), &LineEdit::set_mid_grapheme_caret_enabled); - ClassDB::bind_method(D_METHOD("get_mid_grapheme_caret_enabled"), &LineEdit::get_mid_grapheme_caret_enabled); - ClassDB::bind_method(D_METHOD("cursor_set_force_displayed", "enabled"), &LineEdit::cursor_set_force_displayed); - ClassDB::bind_method(D_METHOD("cursor_get_force_displayed"), &LineEdit::cursor_get_force_displayed); - ClassDB::bind_method(D_METHOD("cursor_set_blink_speed", "blink_speed"), &LineEdit::cursor_set_blink_speed); - ClassDB::bind_method(D_METHOD("cursor_get_blink_speed"), &LineEdit::cursor_get_blink_speed); + ClassDB::bind_method(D_METHOD("set_expand_to_text_length_enabled", "enabled"), &LineEdit::set_expand_to_text_length_enabled); + ClassDB::bind_method(D_METHOD("is_expand_to_text_length_enabled"), &LineEdit::is_expand_to_text_length_enabled); + ClassDB::bind_method(D_METHOD("set_caret_blink_enabled", "enabled"), &LineEdit::set_caret_blink_enabled); + ClassDB::bind_method(D_METHOD("is_caret_blink_enabled"), &LineEdit::is_caret_blink_enabled); + ClassDB::bind_method(D_METHOD("set_caret_mid_grapheme_enabled", "enabled"), &LineEdit::set_caret_mid_grapheme_enabled); + ClassDB::bind_method(D_METHOD("is_caret_mid_grapheme_enabled"), &LineEdit::is_caret_mid_grapheme_enabled); + ClassDB::bind_method(D_METHOD("set_caret_force_displayed", "enabled"), &LineEdit::set_caret_force_displayed); + ClassDB::bind_method(D_METHOD("is_caret_force_displayed"), &LineEdit::is_caret_force_displayed); + ClassDB::bind_method(D_METHOD("set_caret_blink_speed", "blink_speed"), &LineEdit::set_caret_blink_speed); + ClassDB::bind_method(D_METHOD("get_caret_blink_speed"), &LineEdit::get_caret_blink_speed); ClassDB::bind_method(D_METHOD("set_max_length", "chars"), &LineEdit::set_max_length); ClassDB::bind_method(D_METHOD("get_max_length"), &LineEdit::get_max_length); - ClassDB::bind_method(D_METHOD("append_at_cursor", "text"), &LineEdit::append_at_cursor); - ClassDB::bind_method(D_METHOD("delete_char_at_cursor"), &LineEdit::delete_char); + ClassDB::bind_method(D_METHOD("insert_text_at_caret", "text"), &LineEdit::insert_text_at_caret); + ClassDB::bind_method(D_METHOD("delete_char_at_caret"), &LineEdit::delete_char); ClassDB::bind_method(D_METHOD("delete_text", "from_column", "to_column"), &LineEdit::delete_text); ClassDB::bind_method(D_METHOD("set_editable", "enabled"), &LineEdit::set_editable); ClassDB::bind_method(D_METHOD("is_editable"), &LineEdit::is_editable); @@ -2197,14 +2202,14 @@ void LineEdit::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::BOOL, "editable"), "set_editable", "is_editable"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "secret"), "set_secret", "is_secret"); ADD_PROPERTY(PropertyInfo(Variant::STRING, "secret_character"), "set_secret_character", "get_secret_character"); - ADD_PROPERTY(PropertyInfo(Variant::BOOL, "expand_to_text_length"), "set_expand_to_text_length", "get_expand_to_text_length"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "expand_to_text_length"), "set_expand_to_text_length_enabled", "is_expand_to_text_length_enabled"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "context_menu_enabled"), "set_context_menu_enabled", "is_context_menu_enabled"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "virtual_keyboard_enabled"), "set_virtual_keyboard_enabled", "is_virtual_keyboard_enabled"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "clear_button_enabled"), "set_clear_button_enabled", "is_clear_button_enabled"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "shortcut_keys_enabled"), "set_shortcut_keys_enabled", "is_shortcut_keys_enabled"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "selecting_enabled"), "set_selecting_enabled", "is_selecting_enabled"); ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "right_icon", PROPERTY_HINT_RESOURCE_TYPE, "Texture"), "set_right_icon", "get_right_icon"); - ADD_PROPERTY(PropertyInfo(Variant::INT, "text_direction", PROPERTY_HINT_ENUM, "Auto,LTR,RTL,Inherited"), "set_text_direction", "get_text_direction"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "text_direction", PROPERTY_HINT_ENUM, "Auto,Left-to-Right,Right-to-Left,Inherited"), "set_text_direction", "get_text_direction"); ADD_PROPERTY(PropertyInfo(Variant::STRING, "language"), "set_language", "get_language"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "draw_control_chars"), "set_draw_control_chars", "get_draw_control_chars"); ADD_GROUP("Structured Text", "structured_text_"); @@ -2214,11 +2219,11 @@ void LineEdit::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::STRING, "placeholder_text"), "set_placeholder", "get_placeholder"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "placeholder_alpha", PROPERTY_HINT_RANGE, "0,1,0.001"), "set_placeholder_alpha", "get_placeholder_alpha"); ADD_GROUP("Caret", "caret_"); - ADD_PROPERTY(PropertyInfo(Variant::BOOL, "caret_blink"), "cursor_set_blink_enabled", "cursor_get_blink_enabled"); - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "caret_blink_speed", PROPERTY_HINT_RANGE, "0.1,10,0.01"), "cursor_set_blink_speed", "cursor_get_blink_speed"); - ADD_PROPERTY(PropertyInfo(Variant::INT, "caret_position"), "set_cursor_position", "get_cursor_position"); - ADD_PROPERTY(PropertyInfo(Variant::BOOL, "caret_force_displayed"), "cursor_set_force_displayed", "cursor_get_force_displayed"); - ADD_PROPERTY(PropertyInfo(Variant::BOOL, "caret_mid_grapheme"), "set_mid_grapheme_caret_enabled", "get_mid_grapheme_caret_enabled"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "caret_blink"), "set_caret_blink_enabled", "is_caret_blink_enabled"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "caret_blink_speed", PROPERTY_HINT_RANGE, "0.1,10,0.01"), "set_caret_blink_speed", "get_caret_blink_speed"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "caret_column"), "set_caret_column", "get_caret_column"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "caret_force_displayed"), "set_caret_force_displayed", "is_caret_force_displayed"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "caret_mid_grapheme"), "set_caret_mid_grapheme_enabled", "is_caret_mid_grapheme_enabled"); } LineEdit::LineEdit() { @@ -2234,7 +2239,7 @@ LineEdit::LineEdit() { add_child(caret_blink_timer); caret_blink_timer->set_wait_time(0.65); caret_blink_timer->connect("timeout", callable_mp(this, &LineEdit::_toggle_draw_caret)); - cursor_set_blink_enabled(false); + set_caret_blink_enabled(false); menu = memnew(PopupMenu); add_child(menu); diff --git a/scene/gui/line_edit.h b/scene/gui/line_edit.h index ef36377f2e..12fec2f98b 100644 --- a/scene/gui/line_edit.h +++ b/scene/gui/line_edit.h @@ -103,9 +103,9 @@ private: PopupMenu *menu_dir = nullptr; PopupMenu *menu_ctl = nullptr; - bool mid_grapheme_caret_enabled = false; + bool caret_mid_grapheme_enabled = false; - int cursor_pos = 0; + int caret_column = 0; int scroll_offset = 0; int max_length = 0; // 0 for no maximum. @@ -131,16 +131,16 @@ private: struct Selection { int begin = 0; int end = 0; - int cursor_start = 0; + int start_column = 0; bool enabled = false; bool creating = false; - bool doubleclick = false; + bool double_click = false; bool drag_attempt = false; uint64_t last_dblclk = 0; } selection; struct TextOperation { - int cursor_pos = 0; + int caret_column = 0; int scroll_offset = 0; int cached_width = 0; String text; @@ -175,12 +175,12 @@ private: void shift_selection_check_pre(bool); void shift_selection_check_post(bool); - void selection_fill_at_cursor(); + void selection_fill_at_caret(); void set_scroll_offset(int p_pos); int get_scroll_offset() const; - void set_cursor_at_pixel_pos(int p_x); - Vector2i get_cursor_pixel_pos(); + void set_caret_at_pixel_pos(int p_x); + Vector2i get_caret_pixel_pos(); void _reset_caret_blink_timer(); void _toggle_draw_caret(); @@ -191,10 +191,10 @@ private: void _editor_settings_changed(); void _swap_current_input_direction(); - void _move_cursor_left(bool p_select, bool p_move_by_word = false); - void _move_cursor_right(bool p_select, bool p_move_by_word = false); - void _move_cursor_start(bool p_select); - void _move_cursor_end(bool p_select); + void _move_caret_left(bool p_select, bool p_move_by_word = false); + void _move_caret_right(bool p_select, bool p_move_by_word = false); + void _move_caret_start(bool p_select); + void _move_caret_end(bool p_select); void _backspace(bool p_word = false, bool p_all_to_left = false); void _delete(bool p_word = false, bool p_all_to_right = false); @@ -259,26 +259,26 @@ public: void set_placeholder_alpha(float p_alpha); float get_placeholder_alpha() const; - void set_cursor_position(int p_pos); - int get_cursor_position() const; + void set_caret_column(int p_column); + int get_caret_column() const; void set_max_length(int p_max_length); int get_max_length() const; - void append_at_cursor(String p_text); + void insert_text_at_caret(String p_text); void clear(); - void set_mid_grapheme_caret_enabled(const bool p_enabled); - bool get_mid_grapheme_caret_enabled() const; + void set_caret_mid_grapheme_enabled(const bool p_enabled); + bool is_caret_mid_grapheme_enabled() const; - bool cursor_get_blink_enabled() const; - void cursor_set_blink_enabled(const bool p_enabled); + bool is_caret_blink_enabled() const; + void set_caret_blink_enabled(const bool p_enabled); - float cursor_get_blink_speed() const; - void cursor_set_blink_speed(const float p_speed); + float get_caret_blink_speed() const; + void set_caret_blink_speed(const float p_speed); - bool cursor_get_force_displayed() const; - void cursor_set_force_displayed(const bool p_enabled); + void set_caret_force_displayed(const bool p_enabled); + bool is_caret_force_displayed() const; void copy_text(); void cut_text(); @@ -297,8 +297,8 @@ public: virtual Size2 get_minimum_size() const override; - void set_expand_to_text_length(bool p_enabled); - bool get_expand_to_text_length() const; + void set_expand_to_text_length_enabled(bool p_enabled); + bool is_expand_to_text_length_enabled() const; void set_clear_button_enabled(bool p_enabled); bool is_clear_button_enabled() const; diff --git a/scene/gui/link_button.cpp b/scene/gui/link_button.cpp index 1f7b61e3d1..d45ffde715 100644 --- a/scene/gui/link_button.cpp +++ b/scene/gui/link_button.cpp @@ -292,7 +292,7 @@ void LinkButton::_bind_methods() { BIND_ENUM_CONSTANT(UNDERLINE_MODE_NEVER); ADD_PROPERTY(PropertyInfo(Variant::STRING, "text"), "set_text", "get_text"); - ADD_PROPERTY(PropertyInfo(Variant::INT, "text_direction", PROPERTY_HINT_ENUM, "Auto,LTR,RTL,Inherited"), "set_text_direction", "get_text_direction"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "text_direction", PROPERTY_HINT_ENUM, "Auto,Left-to-Right,Right-to-Left,Inherited"), "set_text_direction", "get_text_direction"); ADD_PROPERTY(PropertyInfo(Variant::STRING, "language"), "set_language", "get_language"); ADD_PROPERTY(PropertyInfo(Variant::INT, "underline", PROPERTY_HINT_ENUM, "Always,On Hover,Never"), "set_underline_mode", "get_underline_mode"); ADD_GROUP("Structured Text", "structured_text_"); diff --git a/scene/gui/menu_button.cpp b/scene/gui/menu_button.cpp index 5acc7e808a..1e9baa77fc 100644 --- a/scene/gui/menu_button.cpp +++ b/scene/gui/menu_button.cpp @@ -34,6 +34,8 @@ #include "scene/main/window.h" void MenuButton::_unhandled_key_input(Ref<InputEvent> p_event) { + ERR_FAIL_COND(p_event.is_null()); + if (!_is_focus_owner_in_shorcut_context()) { return; } diff --git a/scene/gui/popup_menu.cpp b/scene/gui/popup_menu.cpp index d733c33c5f..2100707d2d 100644 --- a/scene/gui/popup_menu.cpp +++ b/scene/gui/popup_menu.cpp @@ -252,6 +252,8 @@ void PopupMenu::_submenu_timeout() { } void PopupMenu::_gui_input(const Ref<InputEvent> &p_event) { + ERR_FAIL_COND(p_event.is_null()); + if (p_event->is_action("ui_down") && p_event->is_pressed()) { int search_from = mouse_over + 1; if (search_from >= items.size()) { @@ -359,7 +361,7 @@ void PopupMenu::_gui_input(const Ref<InputEvent> &p_event) { 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)))) { + if (button_idx == MOUSE_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; @@ -722,6 +724,7 @@ void PopupMenu::_notification(int p_what) { for (int i = 0; i < items.size(); i++) { items.write[i].xl_text = tr(items[i].text); items.write[i].dirty = true; + _shape_item(i); } child_controls_changed(); @@ -1279,16 +1282,16 @@ bool PopupMenu::activate_item_by_event(const Ref<InputEvent> &p_event, bool p_fo if (code == 0) { code = k->get_unicode(); } - if (k->get_control()) { + if (k->is_ctrl_pressed()) { code |= KEY_MASK_CTRL; } - if (k->get_alt()) { + if (k->is_alt_pressed()) { code |= KEY_MASK_ALT; } - if (k->get_metakey()) { + if (k->is_meta_pressed()) { code |= KEY_MASK_META; } - if (k->get_shift()) { + if (k->is_shift_pressed()) { code |= KEY_MASK_SHIFT; } } diff --git a/scene/gui/range.cpp b/scene/gui/range.cpp index 86b775e795..adc1ed67ca 100644 --- a/scene/gui/range.cpp +++ b/scene/gui/range.cpp @@ -30,17 +30,14 @@ #include "range.h" -String Range::get_configuration_warning() const { - String warning = Control::get_configuration_warning(); +TypedArray<String> Range::get_configuration_warnings() const { + TypedArray<String> warnings = Node::get_configuration_warnings(); if (shared->exp_ratio && shared->min <= 0) { - if (!warning.is_empty()) { - warning += "\n\n"; - } - warning += TTR("If \"Exp Edit\" is enabled, \"Min Value\" must be greater than 0."); + warnings.push_back(TTR("If \"Exp Edit\" is enabled, \"Min Value\" must be greater than 0.")); } - return warning; + return warnings; } void Range::_value_changed_notify() { @@ -106,7 +103,7 @@ void Range::set_min(double p_min) { shared->emit_changed("min"); - update_configuration_warning(); + update_configuration_warnings(); } void Range::set_max(double p_max) { @@ -181,7 +178,6 @@ double Range::get_as_ratio() const { double v = Math::log(value) / Math::log((double)2); return CLAMP((v - exp_min) / (exp_max - exp_min), 0, 1); - } else { float value = CLAMP(get_value(), shared->min, shared->max); return CLAMP((value - get_min()) / (get_max() - get_min()), 0, 1); @@ -287,7 +283,7 @@ bool Range::is_using_rounded_values() const { void Range::set_exp_ratio(bool p_enable) { shared->exp_ratio = p_enable; - update_configuration_warning(); + update_configuration_warnings(); } bool Range::is_ratio_exp() const { diff --git a/scene/gui/range.h b/scene/gui/range.h index 1072a109c6..7a129e88d6 100644 --- a/scene/gui/range.h +++ b/scene/gui/range.h @@ -97,7 +97,7 @@ public: void share(Range *p_range); void unshare(); - virtual String get_configuration_warning() const override; + TypedArray<String> get_configuration_warnings() const override; Range(); ~Range(); diff --git a/scene/gui/rich_text_effect.h b/scene/gui/rich_text_effect.h index f2e2823eff..d809fd502f 100644 --- a/scene/gui/rich_text_effect.h +++ b/scene/gui/rich_text_effect.h @@ -47,8 +47,8 @@ public: RichTextEffect(); }; -class CharFXTransform : public Reference { - GDCLASS(CharFXTransform, Reference); +class CharFXTransform : public RefCounted { + GDCLASS(CharFXTransform, RefCounted); protected: static void _bind_methods(); diff --git a/scene/gui/rich_text_label.cpp b/scene/gui/rich_text_label.cpp index bce30e7cd3..a2c3b4ed8a 100644 --- a/scene/gui/rich_text_label.cpp +++ b/scene/gui/rich_text_label.cpp @@ -760,7 +760,7 @@ int RichTextLabel::_draw_line(ItemFrame *p_frame, int p_line, const Vector2 &p_o //draw_rect(Rect2(p_ofs + off, TS->shaped_text_get_size(rid)), Color(1,0,0), false, 2); //DEBUG_RECTS - off.y += TS->shaped_text_get_ascent(rid); + off.y += TS->shaped_text_get_ascent(rid) + l.text_buf->get_spacing_top(); // Draw inlined objects. Array objects = TS->shaped_text_get_objects(rid); for (int i = 0; i < objects.size(); i++) { @@ -1079,7 +1079,7 @@ int RichTextLabel::_draw_line(ItemFrame *p_frame, int p_line, const Vector2 &p_o off.x += glyphs[i].advance; } } - off.y += TS->shaped_text_get_descent(rid); + off.y += TS->shaped_text_get_descent(rid) + l.text_buf->get_spacing_bottom(); } return line_count; @@ -1174,7 +1174,7 @@ float RichTextLabel::_find_click_in_line(ItemFrame *p_frame, int p_line, const V } break; } - off.y += TS->shaped_text_get_ascent(rid); + off.y += TS->shaped_text_get_ascent(rid) + l.text_buf->get_spacing_top(); Array objects = TS->shaped_text_get_objects(rid); for (int i = 0; i < objects.size(); i++) { @@ -1238,7 +1238,7 @@ float RichTextLabel::_find_click_in_line(ItemFrame *p_frame, int p_line, const V if (rect.has_point(p_click) && !table_hit) { char_pos = TS->shaped_text_hit_test_position(rid, p_click.x - rect.position.x); } - off.y += TS->shaped_text_get_descent(rid); + off.y += TS->shaped_text_get_descent(rid) + l.text_buf->get_spacing_bottom(); } if (char_pos >= 0) { @@ -1432,10 +1432,11 @@ void RichTextLabel::_notification(int p_what) { } } break; case NOTIFICATION_INTERNAL_PROCESS: { - float dt = get_process_delta_time(); - - _update_fx(main, dt); - update(); + if (is_visible_in_tree()) { + float dt = get_process_delta_time(); + _update_fx(main, dt); + update(); + } } } } @@ -1469,6 +1470,8 @@ Control::CursorShape RichTextLabel::get_cursor_shape(const Point2 &p_pos) const } void RichTextLabel::_gui_input(Ref<InputEvent> p_event) { + ERR_FAIL_COND(p_event.is_null()); + Ref<InputEventMouseButton> b = p_event; if (b.is_valid()) { @@ -1479,8 +1482,8 @@ void RichTextLabel::_gui_input(Ref<InputEvent> p_event) { return; } - if (b->get_button_index() == BUTTON_LEFT) { - if (b->is_pressed() && !b->is_doubleclick()) { + if (b->get_button_index() == MOUSE_BUTTON_LEFT) { + if (b->is_pressed() && !b->is_double_click()) { scroll_updated = false; ItemFrame *c_frame = nullptr; int c_line = 0; @@ -1512,8 +1515,8 @@ void RichTextLabel::_gui_input(Ref<InputEvent> p_event) { } } } - } else if (b->is_pressed() && b->is_doubleclick() && selection.enabled) { - //doubleclick: select word + } else if (b->is_pressed() && b->is_double_click() && selection.enabled) { + //double_click: select word ItemFrame *c_frame = nullptr; int c_line = 0; @@ -1547,7 +1550,7 @@ void RichTextLabel::_gui_input(Ref<InputEvent> p_event) { } else if (!b->is_pressed()) { selection.click_item = nullptr; - if (!b->is_doubleclick() && !scroll_updated) { + if (!b->is_double_click() && !scroll_updated) { Item *c_item = nullptr; bool outside = true; @@ -1564,12 +1567,12 @@ void RichTextLabel::_gui_input(Ref<InputEvent> p_event) { } } - if (b->get_button_index() == BUTTON_WHEEL_UP) { + if (b->get_button_index() == MOUSE_BUTTON_WHEEL_UP) { if (scroll_active) { vscroll->set_value(vscroll->get_value() - vscroll->get_page() * b->get_factor() * 0.5 / 8); } } - if (b->get_button_index() == BUTTON_WHEEL_DOWN) { + if (b->get_button_index() == MOUSE_BUTTON_WHEEL_DOWN) { if (scroll_active) { vscroll->set_value(vscroll->get_value() + vscroll->get_page() * b->get_factor() * 0.5 / 8); } @@ -2229,18 +2232,22 @@ void RichTextLabel::_remove_item(Item *p_item, const int p_line, const int p_sub int size = p_item->subitems.size(); if (size == 0) { p_item->parent->subitems.erase(p_item); + // If a newline was erased, all lines AFTER the newline need to be decremented. if (p_item->type == ITEM_NEWLINE) { current_frame->lines.remove(p_line); - for (int i = p_subitem_line; i < current->subitems.size(); i++) { - if (current->subitems[i]->line > 0) { + for (int i = 0; i < current->subitems.size(); i++) { + if (current->subitems[i]->line > p_subitem_line) { current->subitems[i]->line--; } } } } else { + // First, remove all child items for the provided item. for (int i = 0; i < size; i++) { _remove_item(p_item->subitems.front()->get(), p_line, p_subitem_line); } + // Then remove the provided item itself. + p_item->parent->subitems.erase(p_item); } } @@ -2300,21 +2307,23 @@ bool RichTextLabel::remove_line(const int p_line) { return false; } - int i = 0; - while (i < current->subitems.size() && current->subitems[i]->line < p_line) { - i++; + // Remove all subitems with the same line as that provided. + Vector<int> subitem_indices_to_remove; + for (int i = 0; i < current->subitems.size(); i++) { + if (current->subitems[i]->line == p_line) { + subitem_indices_to_remove.push_back(i); + } } - bool was_newline = false; - while (i < current->subitems.size()) { - was_newline = current->subitems[i]->type == ITEM_NEWLINE; - _remove_item(current->subitems[i], current->subitems[i]->line, p_line); - if (was_newline) { - break; - } + bool had_newline = false; + // Reverse for loop to remove items from the end first. + for (int i = subitem_indices_to_remove.size() - 1; i >= 0; i--) { + int subitem_idx = subitem_indices_to_remove[i]; + had_newline = had_newline || current->subitems[subitem_idx]->type == ITEM_NEWLINE; + _remove_item(current->subitems[subitem_idx], current->subitems[subitem_idx]->line, p_line); } - if (!was_newline) { + if (!had_newline) { current_frame->lines.remove(p_line); if (current_frame->lines.size() == 0) { current_frame->lines.resize(1); @@ -2326,6 +2335,7 @@ bool RichTextLabel::remove_line(const int p_line) { } main->first_invalid_line = 0; // p_line ??? + update(); return true; } @@ -3756,7 +3766,9 @@ void RichTextLabel::set_effects(const Vector<Variant> &effects) { custom_effects.push_back(effect); } - parse_bbcode(bbcode); + if ((bbcode != "") && use_bbcode) { + parse_bbcode(bbcode); + } } Vector<Variant> RichTextLabel::get_effects() { @@ -3773,7 +3785,9 @@ void RichTextLabel::install_effect(const Variant effect) { if (rteffect.is_valid()) { custom_effects.push_back(effect); - parse_bbcode(bbcode); + if ((bbcode != "") && use_bbcode) { + parse_bbcode(bbcode); + } } } @@ -3920,7 +3934,7 @@ void RichTextLabel::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "custom_effects", PROPERTY_HINT_ARRAY_TYPE, "RichTextEffect", (PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_SCRIPT_VARIABLE)), "set_effects", "get_effects"); - ADD_PROPERTY(PropertyInfo(Variant::INT, "text_direction", PROPERTY_HINT_ENUM, "Auto,LTR,RTL,Inherited"), "set_text_direction", "get_text_direction"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "text_direction", PROPERTY_HINT_ENUM, "Auto,Left-to-Right,Right-to-Left,Inherited"), "set_text_direction", "get_text_direction"); ADD_PROPERTY(PropertyInfo(Variant::STRING, "language"), "set_language", "get_language"); ADD_GROUP("Structured Text", "structured_text_"); @@ -3999,14 +4013,16 @@ void RichTextLabel::set_fixed_size_to_width(int p_width) { } Size2 RichTextLabel::get_minimum_size() const { - Size2 size(0, 0); + Ref<StyleBox> style = get_theme_stylebox("normal"); + Size2 size = style->get_minimum_size(); + if (fixed_width != -1) { - size.x = fixed_width; + size.x += fixed_width; } if (fixed_width != -1 || fit_content_height) { const_cast<RichTextLabel *>(this)->_validate_line_caches(main); - size.y = get_content_height(); + size.y += get_content_height(); } return size; diff --git a/scene/gui/scroll_bar.cpp b/scene/gui/scroll_bar.cpp index f2516e76a5..62276e3af0 100644 --- a/scene/gui/scroll_bar.cpp +++ b/scene/gui/scroll_bar.cpp @@ -42,6 +42,8 @@ void ScrollBar::set_can_focus_by_default(bool p_can_focus) { } void ScrollBar::_gui_input(Ref<InputEvent> p_event) { + ERR_FAIL_COND(p_event.is_null()); + Ref<InputEventMouseMotion> m = p_event; if (!m.is_valid() || drag.active) { emit_signal("scrolling"); @@ -52,17 +54,17 @@ void ScrollBar::_gui_input(Ref<InputEvent> p_event) { if (b.is_valid()) { accept_event(); - if (b->get_button_index() == BUTTON_WHEEL_DOWN && b->is_pressed()) { + if (b->get_button_index() == MOUSE_BUTTON_WHEEL_DOWN && b->is_pressed()) { set_value(get_value() + get_page() / 4.0); accept_event(); } - if (b->get_button_index() == BUTTON_WHEEL_UP && b->is_pressed()) { + if (b->get_button_index() == MOUSE_BUTTON_WHEEL_UP && b->is_pressed()) { set_value(get_value() - get_page() / 4.0); accept_event(); } - if (b->get_button_index() != BUTTON_LEFT) { + if (b->get_button_index() != MOUSE_BUTTON_LEFT) { return; } diff --git a/scene/gui/scroll_container.cpp b/scene/gui/scroll_container.cpp index 411891ece8..5f872644ab 100644 --- a/scene/gui/scroll_container.cpp +++ b/scene/gui/scroll_container.cpp @@ -88,37 +88,39 @@ void ScrollContainer::_cancel_drag() { } void ScrollContainer::_gui_input(const Ref<InputEvent> &p_gui_input) { + ERR_FAIL_COND(p_gui_input.is_null()); + double prev_v_scroll = v_scroll->get_value(); double prev_h_scroll = h_scroll->get_value(); Ref<InputEventMouseButton> mb = p_gui_input; if (mb.is_valid()) { - if (mb->get_button_index() == BUTTON_WHEEL_UP && mb->is_pressed()) { + if (mb->get_button_index() == MOUSE_BUTTON_WHEEL_UP && mb->is_pressed()) { // only horizontal is enabled, scroll horizontally - if (h_scroll->is_visible() && (!v_scroll->is_visible() || mb->get_shift())) { + if (h_scroll->is_visible() && (!v_scroll->is_visible() || mb->is_shift_pressed())) { h_scroll->set_value(h_scroll->get_value() - h_scroll->get_page() / 8 * mb->get_factor()); } else if (v_scroll->is_visible_in_tree()) { v_scroll->set_value(v_scroll->get_value() - v_scroll->get_page() / 8 * mb->get_factor()); } } - if (mb->get_button_index() == BUTTON_WHEEL_DOWN && mb->is_pressed()) { + if (mb->get_button_index() == MOUSE_BUTTON_WHEEL_DOWN && mb->is_pressed()) { // only horizontal is enabled, scroll horizontally - if (h_scroll->is_visible() && (!v_scroll->is_visible() || mb->get_shift())) { + if (h_scroll->is_visible() && (!v_scroll->is_visible() || mb->is_shift_pressed())) { h_scroll->set_value(h_scroll->get_value() + h_scroll->get_page() / 8 * mb->get_factor()); } else if (v_scroll->is_visible()) { v_scroll->set_value(v_scroll->get_value() + v_scroll->get_page() / 8 * mb->get_factor()); } } - if (mb->get_button_index() == BUTTON_WHEEL_LEFT && mb->is_pressed()) { + if (mb->get_button_index() == MOUSE_BUTTON_WHEEL_LEFT && mb->is_pressed()) { if (h_scroll->is_visible_in_tree()) { h_scroll->set_value(h_scroll->get_value() - h_scroll->get_page() * mb->get_factor() / 8); } } - if (mb->get_button_index() == BUTTON_WHEEL_RIGHT && mb->is_pressed()) { + if (mb->get_button_index() == MOUSE_BUTTON_WHEEL_RIGHT && mb->is_pressed()) { if (h_scroll->is_visible_in_tree()) { h_scroll->set_value(h_scroll->get_value() + h_scroll->get_page() * mb->get_factor() / 8); } @@ -132,7 +134,7 @@ void ScrollContainer::_gui_input(const Ref<InputEvent> &p_gui_input) { return; } - if (mb->get_button_index() != BUTTON_LEFT) { + if (mb->get_button_index() != MOUSE_BUTTON_LEFT) { return; } @@ -236,32 +238,25 @@ void ScrollContainer::_update_scrollbar_position() { _updating_scrollbars = false; } -void ScrollContainer::_ensure_focused_visible(Control *p_control) { - if (!follow_focus) { - return; +void ScrollContainer::_gui_focus_changed(Control *p_control) { + if (follow_focus && is_a_parent_of(p_control)) { + ensure_control_visible(p_control); } +} - if (is_a_parent_of(p_control)) { - Rect2 global_rect = get_global_rect(); - Rect2 other_rect = p_control->get_global_rect(); - float right_margin = 0.0; - if (v_scroll->is_visible()) { - right_margin += v_scroll->get_size().x; - } - float bottom_margin = 0.0; - if (h_scroll->is_visible()) { - bottom_margin += h_scroll->get_size().y; - } +void ScrollContainer::ensure_control_visible(Control *p_control) { + ERR_FAIL_COND_MSG(!is_a_parent_of(p_control), "Must be a parent of the control."); - float diff = MAX(MIN(other_rect.position.y, global_rect.position.y), other_rect.position.y + other_rect.size.y - global_rect.size.y + bottom_margin); - set_v_scroll(get_v_scroll() + (diff - global_rect.position.y)); - if (is_layout_rtl()) { - diff = MAX(MIN(other_rect.position.x, global_rect.position.x), other_rect.position.x + other_rect.size.x - global_rect.size.x); - } else { - diff = MAX(MIN(other_rect.position.x, global_rect.position.x), other_rect.position.x + other_rect.size.x - global_rect.size.x + right_margin); - } - set_h_scroll(get_h_scroll() + (diff - global_rect.position.x)); - } + Rect2 global_rect = get_global_rect(); + Rect2 other_rect = p_control->get_global_rect(); + float right_margin = v_scroll->is_visible() ? v_scroll->get_size().x : 0.0f; + float bottom_margin = h_scroll->is_visible() ? h_scroll->get_size().y : 0.0f; + + Vector2 diff = Vector2(MAX(MIN(other_rect.position.x, global_rect.position.x), other_rect.position.x + other_rect.size.x - global_rect.size.x + (!is_layout_rtl() ? right_margin : 0.0f)), + MAX(MIN(other_rect.position.y, global_rect.position.y), other_rect.position.y + other_rect.size.y - global_rect.size.y + bottom_margin)); + + set_h_scroll(get_h_scroll() + (diff.x - global_rect.position.x)); + set_v_scroll(get_v_scroll() + (diff.y - global_rect.position.y)); } void ScrollContainer::_update_dimensions() { @@ -297,7 +292,7 @@ void ScrollContainer::_update_dimensions() { child_max_size.x = MAX(child_max_size.x, minsize.x); child_max_size.y = MAX(child_max_size.y, minsize.y); - Rect2 r = Rect2(-scroll, minsize); + Rect2 r = Rect2(-Size2(get_h_scroll(), get_v_scroll()), minsize); if (!scroll_h || (!h_scroll->is_visible_in_tree() && c->get_h_size_flags() & SIZE_EXPAND)) { r.position.x = 0; if (c->get_h_size_flags() & SIZE_EXPAND) { @@ -332,7 +327,7 @@ void ScrollContainer::_notification(int p_what) { }; if (p_what == NOTIFICATION_READY) { - get_viewport()->connect("gui_focus_changed", callable_mp(this, &ScrollContainer::_ensure_focused_visible)); + get_viewport()->connect("gui_focus_changed", callable_mp(this, &ScrollContainer::_gui_focus_changed)); _update_dimensions(); } @@ -432,40 +427,16 @@ void ScrollContainer::update_scrollbars() { Size2 min = child_max_size; - bool hide_scroll_v = !scroll_v || min.height <= size.height; - bool hide_scroll_h = !scroll_h || min.width <= size.width; - - v_scroll->set_max(min.height); - if (hide_scroll_v) { - v_scroll->set_page(size.height); - v_scroll->hide(); - scroll.y = 0; - } else { - v_scroll->show(); - if (hide_scroll_h) { - v_scroll->set_page(size.height); - } else { - v_scroll->set_page(size.height - hmin.height); - } - - scroll.y = v_scroll->get_value(); - } + bool hide_scroll_h = !scroll_h || min.width <= size.width || !h_scroll_visible; + bool hide_scroll_v = !scroll_v || min.height <= size.height || !v_scroll_visible; h_scroll->set_max(min.width); - if (hide_scroll_h) { - h_scroll->set_page(size.width); - h_scroll->hide(); - scroll.x = 0; - } else { - h_scroll->show(); - if (hide_scroll_v) { - h_scroll->set_page(size.width); - } else { - h_scroll->set_page(size.width - vmin.width); - } + h_scroll->set_page(size.width - (hide_scroll_v ? 0 : vmin.width)); + h_scroll->set_visible(!hide_scroll_h); - scroll.x = h_scroll->get_value(); - } + v_scroll->set_max(min.height); + v_scroll->set_page(size.height - (hide_scroll_h ? 0 : hmin.height)); + v_scroll->set_visible(!hide_scroll_v); // Avoid scrollbar overlapping. h_scroll->set_anchor_and_offset(SIDE_RIGHT, ANCHOR_END, hide_scroll_v ? 0 : -vmin.width); @@ -473,13 +444,28 @@ void ScrollContainer::update_scrollbars() { } void ScrollContainer::_scroll_moved(float) { - scroll.x = h_scroll->get_value(); - scroll.y = v_scroll->get_value(); queue_sort(); - update(); }; +void ScrollContainer::set_h_scroll(int p_pos) { + h_scroll->set_value(p_pos); + _cancel_drag(); +} + +int ScrollContainer::get_h_scroll() const { + return h_scroll->get_value(); +} + +void ScrollContainer::set_v_scroll(int p_pos) { + v_scroll->set_value(p_pos); + _cancel_drag(); +} + +int ScrollContainer::get_v_scroll() const { + return v_scroll->get_value(); +} + void ScrollContainer::set_enable_h_scroll(bool p_enable) { if (scroll_h == p_enable) { return; @@ -508,22 +494,30 @@ bool ScrollContainer::is_v_scroll_enabled() const { return scroll_v; } -int ScrollContainer::get_v_scroll() const { - return v_scroll->get_value(); +void ScrollContainer::set_h_scroll_visible(bool p_visible) { + if (h_scroll_visible == p_visible) { + return; + } + + h_scroll_visible = p_visible; + update_scrollbars(); } -void ScrollContainer::set_v_scroll(int p_pos) { - v_scroll->set_value(p_pos); - _cancel_drag(); +bool ScrollContainer::is_h_scroll_visible() const { + return h_scroll_visible; } -int ScrollContainer::get_h_scroll() const { - return h_scroll->get_value(); +void ScrollContainer::set_v_scroll_visible(bool p_visible) { + if (v_scroll_visible == p_visible) { + return; + } + + v_scroll_visible = p_visible; + update_scrollbars(); } -void ScrollContainer::set_h_scroll(int p_pos) { - h_scroll->set_value(p_pos); - _cancel_drag(); +bool ScrollContainer::is_v_scroll_visible() const { + return v_scroll_visible; } int ScrollContainer::get_deadzone() const { @@ -542,8 +536,8 @@ void ScrollContainer::set_follow_focus(bool p_follow) { follow_focus = p_follow; } -String ScrollContainer::get_configuration_warning() const { - String warning = Container::get_configuration_warning(); +TypedArray<String> ScrollContainer::get_configuration_warnings() const { + TypedArray<String> warnings = Container::get_configuration_warnings(); int found = 0; @@ -563,12 +557,10 @@ String ScrollContainer::get_configuration_warning() const { } if (found != 1) { - if (!warning.is_empty()) { - warning += "\n\n"; - } - warning += TTR("ScrollContainer is intended to work with a single child control.\nUse a container as child (VBox, HBox, etc.), or a Control and set the custom minimum size manually."); + warnings.push_back(TTR("ScrollContainer is intended to work with a single child control.\nUse a container as child (VBox, HBox, etc.), or a Control and set the custom minimum size manually.")); } - return warning; + + return warnings; } HScrollBar *ScrollContainer::get_h_scrollbar() { @@ -581,22 +573,35 @@ VScrollBar *ScrollContainer::get_v_scrollbar() { void ScrollContainer::_bind_methods() { ClassDB::bind_method(D_METHOD("_gui_input"), &ScrollContainer::_gui_input); - ClassDB::bind_method(D_METHOD("set_enable_h_scroll", "enable"), &ScrollContainer::set_enable_h_scroll); - ClassDB::bind_method(D_METHOD("is_h_scroll_enabled"), &ScrollContainer::is_h_scroll_enabled); - ClassDB::bind_method(D_METHOD("set_enable_v_scroll", "enable"), &ScrollContainer::set_enable_v_scroll); - ClassDB::bind_method(D_METHOD("is_v_scroll_enabled"), &ScrollContainer::is_v_scroll_enabled); ClassDB::bind_method(D_METHOD("_update_scrollbar_position"), &ScrollContainer::_update_scrollbar_position); + ClassDB::bind_method(D_METHOD("set_h_scroll", "value"), &ScrollContainer::set_h_scroll); ClassDB::bind_method(D_METHOD("get_h_scroll"), &ScrollContainer::get_h_scroll); + ClassDB::bind_method(D_METHOD("set_v_scroll", "value"), &ScrollContainer::set_v_scroll); ClassDB::bind_method(D_METHOD("get_v_scroll"), &ScrollContainer::get_v_scroll); + + ClassDB::bind_method(D_METHOD("set_enable_h_scroll", "enable"), &ScrollContainer::set_enable_h_scroll); + ClassDB::bind_method(D_METHOD("is_h_scroll_enabled"), &ScrollContainer::is_h_scroll_enabled); + + ClassDB::bind_method(D_METHOD("set_enable_v_scroll", "enable"), &ScrollContainer::set_enable_v_scroll); + ClassDB::bind_method(D_METHOD("is_v_scroll_enabled"), &ScrollContainer::is_v_scroll_enabled); + + ClassDB::bind_method(D_METHOD("set_h_scroll_visible", "visible"), &ScrollContainer::set_h_scroll_visible); + ClassDB::bind_method(D_METHOD("is_h_scroll_visible"), &ScrollContainer::is_h_scroll_visible); + + ClassDB::bind_method(D_METHOD("set_v_scroll_visible", "visible"), &ScrollContainer::set_v_scroll_visible); + ClassDB::bind_method(D_METHOD("is_v_scroll_visible"), &ScrollContainer::is_v_scroll_visible); + ClassDB::bind_method(D_METHOD("set_deadzone", "deadzone"), &ScrollContainer::set_deadzone); ClassDB::bind_method(D_METHOD("get_deadzone"), &ScrollContainer::get_deadzone); + ClassDB::bind_method(D_METHOD("set_follow_focus", "enabled"), &ScrollContainer::set_follow_focus); ClassDB::bind_method(D_METHOD("is_following_focus"), &ScrollContainer::is_following_focus); ClassDB::bind_method(D_METHOD("get_h_scrollbar"), &ScrollContainer::get_h_scrollbar); ClassDB::bind_method(D_METHOD("get_v_scrollbar"), &ScrollContainer::get_v_scrollbar); + ClassDB::bind_method(D_METHOD("ensure_control_visible", "control"), &ScrollContainer::ensure_control_visible); ADD_SIGNAL(MethodInfo("scroll_started")); ADD_SIGNAL(MethodInfo("scroll_ended")); @@ -604,10 +609,12 @@ void ScrollContainer::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::BOOL, "follow_focus"), "set_follow_focus", "is_following_focus"); ADD_GROUP("Scroll", "scroll_"); - ADD_PROPERTY(PropertyInfo(Variant::BOOL, "scroll_horizontal_enabled"), "set_enable_h_scroll", "is_h_scroll_enabled"); ADD_PROPERTY(PropertyInfo(Variant::INT, "scroll_horizontal"), "set_h_scroll", "get_h_scroll"); - ADD_PROPERTY(PropertyInfo(Variant::BOOL, "scroll_vertical_enabled"), "set_enable_v_scroll", "is_v_scroll_enabled"); ADD_PROPERTY(PropertyInfo(Variant::INT, "scroll_vertical"), "set_v_scroll", "get_v_scroll"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "scroll_horizontal_enabled"), "set_enable_h_scroll", "is_h_scroll_enabled"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "scroll_vertical_enabled"), "set_enable_v_scroll", "is_v_scroll_enabled"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "scroll_horizontal_visible"), "set_h_scroll_visible", "is_h_scroll_visible"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "scroll_vertical_visible"), "set_v_scroll_visible", "is_v_scroll_visible"); ADD_PROPERTY(PropertyInfo(Variant::INT, "scroll_deadzone"), "set_deadzone", "get_deadzone"); GLOBAL_DEF("gui/common/default_scroll_deadzone", 0); diff --git a/scene/gui/scroll_container.h b/scene/gui/scroll_container.h index 9d3ce39345..c77a0d62f5 100644 --- a/scene/gui/scroll_container.h +++ b/scene/gui/scroll_container.h @@ -42,7 +42,6 @@ class ScrollContainer : public Container { VScrollBar *v_scroll; Size2 child_max_size; - Size2 scroll; void update_scrollbars(); @@ -50,16 +49,17 @@ class ScrollContainer : public Container { Vector2 drag_accum; Vector2 drag_from; Vector2 last_drag_accum; - float last_drag_time = 0.0; - float time_since_motion = 0.0; + float time_since_motion = 0.0f; bool drag_touching = false; bool drag_touching_deaccel = false; - bool click_handled = false; bool beyond_deadzone = false; bool scroll_h = true; bool scroll_v = true; + bool h_scroll_visible = true; + bool v_scroll_visible = true; + int deadzone = 0; bool follow_focus = false; @@ -69,6 +69,7 @@ protected: Size2 get_minimum_size() const override; void _gui_input(const Ref<InputEvent> &p_gui_input); + void _gui_focus_changed(Control *p_control); void _update_dimensions(); void _notification(int p_what); @@ -77,14 +78,13 @@ protected: bool _updating_scrollbars = false; void _update_scrollbar_position(); - void _ensure_focused_visible(Control *p_node); public: - int get_v_scroll() const; - void set_v_scroll(int p_pos); - - int get_h_scroll() const; void set_h_scroll(int p_pos); + int get_h_scroll() const; + + void set_v_scroll(int p_pos); + int get_v_scroll() const; void set_enable_h_scroll(bool p_enable); bool is_h_scroll_enabled() const; @@ -92,6 +92,12 @@ public: void set_enable_v_scroll(bool p_enable); bool is_v_scroll_enabled() const; + void set_h_scroll_visible(bool p_visible); + bool is_h_scroll_visible() const; + + void set_v_scroll_visible(bool p_visible); + bool is_v_scroll_visible() const; + int get_deadzone() const; void set_deadzone(int p_deadzone); @@ -100,10 +106,11 @@ public: HScrollBar *get_h_scrollbar(); VScrollBar *get_v_scrollbar(); + void ensure_control_visible(Control *p_control); virtual bool clips_input() const override; - virtual String get_configuration_warning() const override; + TypedArray<String> get_configuration_warnings() const override; ScrollContainer(); }; diff --git a/scene/gui/slider.cpp b/scene/gui/slider.cpp index 2239226c78..a407ef21cb 100644 --- a/scene/gui/slider.cpp +++ b/scene/gui/slider.cpp @@ -46,6 +46,8 @@ Size2 Slider::get_minimum_size() const { } void Slider::_gui_input(Ref<InputEvent> p_event) { + ERR_FAIL_COND(p_event.is_null()); + if (!editable) { return; } @@ -53,7 +55,7 @@ void Slider::_gui_input(Ref<InputEvent> p_event) { Ref<InputEventMouseButton> mb = p_event; if (mb.is_valid()) { - if (mb->get_button_index() == BUTTON_LEFT) { + if (mb->get_button_index() == MOUSE_BUTTON_LEFT) { if (mb->is_pressed()) { Ref<Texture2D> grabber = get_theme_icon(mouse_inside || has_focus() ? "grabber_highlight" : "grabber"); grab.pos = orientation == VERTICAL ? mb->get_position().y : mb->get_position().x; @@ -72,10 +74,10 @@ void Slider::_gui_input(Ref<InputEvent> p_event) { grab.active = false; } } else if (scrollable) { - if (mb->is_pressed() && mb->get_button_index() == BUTTON_WHEEL_UP) { + if (mb->is_pressed() && mb->get_button_index() == MOUSE_BUTTON_WHEEL_UP) { grab_focus(); set_value(get_value() + get_step()); - } else if (mb->is_pressed() && mb->get_button_index() == BUTTON_WHEEL_DOWN) { + } else if (mb->is_pressed() && mb->get_button_index() == MOUSE_BUTTON_WHEEL_DOWN) { grab_focus(); set_value(get_value() - get_step()); } diff --git a/scene/gui/spin_box.cpp b/scene/gui/spin_box.cpp index d82cc98e01..9dc2afdb2d 100644 --- a/scene/gui/spin_box.cpp +++ b/scene/gui/spin_box.cpp @@ -76,7 +76,7 @@ void SpinBox::_line_edit_input(const Ref<InputEvent> &p_event) { } void SpinBox::_range_click_timeout() { - if (!drag.enabled && Input::get_singleton()->is_mouse_button_pressed(BUTTON_LEFT)) { + if (!drag.enabled && Input::get_singleton()->is_mouse_button_pressed(MOUSE_BUTTON_LEFT)) { bool up = get_local_mouse_position().y < (get_size().height / 2); set_value(get_value() + (up ? get_step() : -get_step())); @@ -100,6 +100,8 @@ void SpinBox::_release_mouse() { } void SpinBox::_gui_input(const Ref<InputEvent> &p_event) { + ERR_FAIL_COND(p_event.is_null()); + if (!is_editable()) { return; } @@ -110,7 +112,7 @@ void SpinBox::_gui_input(const Ref<InputEvent> &p_event) { bool up = mb->get_position().y < (get_size().height / 2); switch (mb->get_button_index()) { - case BUTTON_LEFT: { + case MOUSE_BUTTON_LEFT: { line_edit->grab_focus(); set_value(get_value() + (up ? get_step() : -get_step())); @@ -122,17 +124,17 @@ void SpinBox::_gui_input(const Ref<InputEvent> &p_event) { drag.allowed = true; drag.capture_pos = mb->get_position(); } break; - case BUTTON_RIGHT: { + case MOUSE_BUTTON_RIGHT: { line_edit->grab_focus(); set_value((up ? get_max() : get_min())); } break; - case BUTTON_WHEEL_UP: { + case MOUSE_BUTTON_WHEEL_UP: { if (line_edit->has_focus()) { set_value(get_value() + get_step() * mb->get_factor()); accept_event(); } } break; - case BUTTON_WHEEL_DOWN: { + case MOUSE_BUTTON_WHEEL_DOWN: { if (line_edit->has_focus()) { set_value(get_value() - get_step() * mb->get_factor()); accept_event(); @@ -141,7 +143,7 @@ void SpinBox::_gui_input(const Ref<InputEvent> &p_event) { } } - if (mb.is_valid() && !mb->is_pressed() && mb->get_button_index() == BUTTON_LEFT) { + if (mb.is_valid() && !mb->is_pressed() && mb->get_button_index() == MOUSE_BUTTON_LEFT) { //set_default_cursor_shape(CURSOR_ARROW); range_click_timer->stop(); _release_mouse(); @@ -150,7 +152,7 @@ void SpinBox::_gui_input(const Ref<InputEvent> &p_event) { Ref<InputEventMouseMotion> mm = p_event; - if (mm.is_valid() && mm->get_button_mask() & BUTTON_MASK_LEFT) { + if (mm.is_valid() && mm->get_button_mask() & MOUSE_BUTTON_MASK_LEFT) { if (drag.enabled) { drag.diff_y += mm->get_relative().y; float diff_y = -0.01 * Math::pow(ABS(drag.diff_y), 1.8f) * SGN(drag.diff_y); diff --git a/scene/gui/split_container.cpp b/scene/gui/split_container.cpp index d43e195df1..df4cf9a740 100644 --- a/scene/gui/split_container.cpp +++ b/scene/gui/split_container.cpp @@ -207,6 +207,8 @@ void SplitContainer::_notification(int p_what) { } void SplitContainer::_gui_input(const Ref<InputEvent> &p_event) { + ERR_FAIL_COND(p_event.is_null()); + if (collapsed || !_getch(0) || !_getch(1) || dragger_visibility != DRAGGER_VISIBLE) { return; } @@ -214,7 +216,7 @@ void SplitContainer::_gui_input(const Ref<InputEvent> &p_event) { Ref<InputEventMouseButton> mb = p_event; if (mb.is_valid()) { - if (mb->get_button_index() == BUTTON_LEFT) { + if (mb->get_button_index() == MOUSE_BUTTON_LEFT) { if (mb->is_pressed()) { int sep = get_theme_constant("separation"); @@ -351,7 +353,7 @@ void SplitContainer::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::INT, "split_offset"), "set_split_offset", "get_split_offset"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "collapsed"), "set_collapsed", "is_collapsed"); - ADD_PROPERTY(PropertyInfo(Variant::INT, "dragger_visibility", PROPERTY_HINT_ENUM, "Visible,Hidden,Hidden & Collapsed"), "set_dragger_visibility", "get_dragger_visibility"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "dragger_visibility", PROPERTY_HINT_ENUM, "Visible,Hidden,Hidden and Collapsed"), "set_dragger_visibility", "get_dragger_visibility"); BIND_ENUM_CONSTANT(DRAGGER_VISIBLE); BIND_ENUM_CONSTANT(DRAGGER_HIDDEN); diff --git a/scene/gui/subviewport_container.cpp b/scene/gui/subviewport_container.cpp index 8ffdd269a4..bfc7e29f9c 100644 --- a/scene/gui/subviewport_container.cpp +++ b/scene/gui/subviewport_container.cpp @@ -140,6 +140,8 @@ void SubViewportContainer::_notification(int p_what) { } void SubViewportContainer::_input(const Ref<InputEvent> &p_event) { + ERR_FAIL_COND(p_event.is_null()); + if (Engine::get_singleton()->is_editor_hint()) { return; } @@ -165,6 +167,8 @@ void SubViewportContainer::_input(const Ref<InputEvent> &p_event) { } void SubViewportContainer::_unhandled_input(const Ref<InputEvent> &p_event) { + ERR_FAIL_COND(p_event.is_null()); + if (Engine::get_singleton()->is_editor_hint()) { return; } diff --git a/scene/gui/tab_container.cpp b/scene/gui/tab_container.cpp index 3bf163f670..acf0641005 100644 --- a/scene/gui/tab_container.cpp +++ b/scene/gui/tab_container.cpp @@ -72,11 +72,13 @@ int TabContainer::_get_top_margin() const { } void TabContainer::_gui_input(const Ref<InputEvent> &p_event) { + ERR_FAIL_COND(p_event.is_null()); + Ref<InputEventMouseButton> mb = p_event; Popup *popup = get_popup(); - if (mb.is_valid() && mb->is_pressed() && mb->get_button_index() == BUTTON_LEFT) { + if (mb.is_valid() && mb->is_pressed() && mb->get_button_index() == MOUSE_BUTTON_LEFT) { Point2 pos(mb->get_position().x, mb->get_position().y); Size2 size = get_size(); @@ -392,6 +394,7 @@ void TabContainer::_notification(int p_what) { Vector<int> tab_widths; for (int i = first_tab_cache; i < tabs.size(); i++) { if (get_tab_hidden(i)) { + tab_widths.push_back(0); continue; } int tab_width = _get_tab_width(i); diff --git a/scene/gui/tabs.cpp b/scene/gui/tabs.cpp index da1a9698d0..11096e7976 100644 --- a/scene/gui/tabs.cpp +++ b/scene/gui/tabs.cpp @@ -83,11 +83,16 @@ Size2 Tabs::get_minimum_size() const { } } - ms.width = 0; //TODO: should make this optional + if (clip_tabs) { + ms.width = 0; + } + return ms; } void Tabs::_gui_input(const Ref<InputEvent> &p_event) { + ERR_FAIL_COND(p_event.is_null()); + Ref<InputEventMouseMotion> mm = p_event; if (mm.is_valid()) { @@ -105,10 +110,10 @@ void Tabs::_gui_input(const Ref<InputEvent> &p_event) { highlight_arrow = 0; } } else { - int limit = get_size().width - incr->get_width() - decr->get_width(); - if (pos.x > limit + decr->get_width()) { + int limit_minus_buttons = get_size().width - incr->get_width() - decr->get_width(); + if (pos.x > limit_minus_buttons + decr->get_width()) { highlight_arrow = 1; - } else if (pos.x > limit) { + } else if (pos.x > limit_minus_buttons) { highlight_arrow = 0; } } @@ -122,7 +127,7 @@ void Tabs::_gui_input(const Ref<InputEvent> &p_event) { Ref<InputEventMouseButton> mb = p_event; if (mb.is_valid()) { - if (mb->is_pressed() && mb->get_button_index() == BUTTON_WHEEL_UP && !mb->get_command()) { + if (mb->is_pressed() && mb->get_button_index() == MOUSE_BUTTON_WHEEL_UP && !mb->is_command_pressed()) { if (scrolling_enabled && buttons_visible) { if (offset > 0) { offset--; @@ -131,7 +136,7 @@ void Tabs::_gui_input(const Ref<InputEvent> &p_event) { } } - if (mb->is_pressed() && mb->get_button_index() == BUTTON_WHEEL_DOWN && !mb->get_command()) { + if (mb->is_pressed() && mb->get_button_index() == MOUSE_BUTTON_WHEEL_DOWN && !mb->is_command_pressed()) { if (scrolling_enabled && buttons_visible) { if (missing_right) { offset++; @@ -140,7 +145,7 @@ void Tabs::_gui_input(const Ref<InputEvent> &p_event) { } } - if (rb_pressing && !mb->is_pressed() && mb->get_button_index() == BUTTON_LEFT) { + if (rb_pressing && !mb->is_pressed() && mb->get_button_index() == MOUSE_BUTTON_LEFT) { if (rb_hover != -1) { //pressed emit_signal("right_button_pressed", rb_hover); @@ -150,7 +155,7 @@ void Tabs::_gui_input(const Ref<InputEvent> &p_event) { update(); } - if (cb_pressing && !mb->is_pressed() && mb->get_button_index() == BUTTON_LEFT) { + if (cb_pressing && !mb->is_pressed() && mb->get_button_index() == MOUSE_BUTTON_LEFT) { if (cb_hover != -1) { //pressed emit_signal("tab_closed", cb_hover); @@ -160,7 +165,7 @@ void Tabs::_gui_input(const Ref<InputEvent> &p_event) { update(); } - if (mb->is_pressed() && (mb->get_button_index() == BUTTON_LEFT || (select_with_rmb && mb->get_button_index() == BUTTON_RIGHT))) { + if (mb->is_pressed() && (mb->get_button_index() == MOUSE_BUTTON_LEFT || (select_with_rmb && mb->get_button_index() == MOUSE_BUTTON_RIGHT))) { // clicks Point2 pos(mb->get_position().x, mb->get_position().y); @@ -251,6 +256,7 @@ void Tabs::_notification(int p_what) { _update_cache(); update(); } break; + case NOTIFICATION_THEME_CHANGED: case NOTIFICATION_TRANSLATION_CHANGED: { for (int i = 0; i < tabs.size(); ++i) { _shape(i); @@ -305,7 +311,8 @@ void Tabs::_notification(int p_what) { Ref<Texture2D> incr_hl = get_theme_icon("increment_highlight"); Ref<Texture2D> decr_hl = get_theme_icon("decrement_highlight"); - int limit = get_size().width - incr->get_size().width - decr->get_size().width; + int limit = get_size().width; + int limit_minus_buttons = get_size().width - incr->get_width() - decr->get_width(); missing_right = false; @@ -328,7 +335,8 @@ void Tabs::_notification(int p_what) { col = font_unselected_color; } - if (w + lsize > limit) { + int new_width = w + lsize; + if (new_width > limit || (i < tabs.size() - 1 && new_width > limit_minus_buttons)) { // For the last tab, we accept if the tab covers the buttons. max_drawn_tab = i - 1; missing_right = true; break; @@ -459,15 +467,15 @@ void Tabs::_notification(int p_what) { } } else { if (offset > 0) { - draw_texture(highlight_arrow == 0 ? decr_hl : decr, Point2(limit, vofs)); + draw_texture(highlight_arrow == 0 ? decr_hl : decr, Point2(limit_minus_buttons, vofs)); } else { - draw_texture(decr, Point2(limit, vofs), Color(1, 1, 1, 0.5)); + draw_texture(decr, Point2(limit_minus_buttons, vofs), Color(1, 1, 1, 0.5)); } if (missing_right) { - draw_texture(highlight_arrow == 1 ? incr_hl : incr, Point2(limit + decr->get_size().width, vofs)); + draw_texture(highlight_arrow == 1 ? incr_hl : incr, Point2(limit_minus_buttons + decr->get_size().width, vofs)); } else { - draw_texture(incr, Point2(limit + decr->get_size().width, vofs), Color(1, 1, 1, 0.5)); + draw_texture(incr, Point2(limit_minus_buttons + decr->get_size().width, vofs), Color(1, 1, 1, 0.5)); } } @@ -666,7 +674,7 @@ void Tabs::_update_cache() { Ref<StyleBox> tab_selected = get_theme_stylebox("tab_selected"); Ref<Texture2D> incr = get_theme_icon("increment"); Ref<Texture2D> decr = get_theme_icon("decrement"); - int limit = get_size().width - incr->get_width() - decr->get_width(); + int limit_minus_buttons = get_size().width - incr->get_width() - decr->get_width(); int w = 0; int mw = 0; @@ -686,7 +694,7 @@ void Tabs::_update_cache() { } int m_width = min_width; if (count_resize > 0) { - m_width = MAX((limit - size_fixed) / count_resize, min_width); + m_width = MAX((limit_minus_buttons - size_fixed) / count_resize, min_width); } for (int i = offset; i < tabs.size(); i++) { Ref<StyleBox> sb; @@ -699,7 +707,7 @@ void Tabs::_update_cache() { } int lsize = tabs[i].size_cache; int slen = tabs[i].size_text; - if (min_width > 0 && mw > limit && i != current) { + if (min_width > 0 && mw > limit_minus_buttons && i != current) { if (lsize > m_width) { slen = m_width - (sb->get_margin(SIDE_LEFT) + sb->get_margin(SIDE_RIGHT)); if (tabs[i].icon.is_valid()) { @@ -909,6 +917,19 @@ Tabs::TabAlign Tabs::get_tab_align() const { return tab_align; } +void Tabs::set_clip_tabs(bool p_clip_tabs) { + if (clip_tabs == p_clip_tabs) { + return; + } + clip_tabs = p_clip_tabs; + update(); + minimum_size_changed(); +} + +bool Tabs::get_clip_tabs() const { + return clip_tabs; +} + void Tabs::move_tab(int from, int to) { if (from == to) { return; @@ -975,7 +996,8 @@ void Tabs::_ensure_no_over_offset() { Ref<Texture2D> incr = get_theme_icon("increment"); Ref<Texture2D> decr = get_theme_icon("decrement"); - int limit = get_size().width - incr->get_width() - decr->get_width(); + int limit = get_size().width; + int limit_minus_buttons = get_size().width - incr->get_width() - decr->get_width(); while (offset > 0) { int total_w = 0; @@ -983,7 +1005,7 @@ void Tabs::_ensure_no_over_offset() { total_w += tabs[i].size_cache; } - if (total_w < limit) { + if ((buttons_visible && total_w < limit_minus_buttons) || total_w < limit) { // For the last tab, we accept if the tab covers the buttons. offset--; update(); } else { @@ -1014,9 +1036,12 @@ void Tabs::ensure_tab_visible(int p_idx) { int prev_offset = offset; Ref<Texture2D> incr = get_theme_icon("increment"); Ref<Texture2D> decr = get_theme_icon("decrement"); - int limit = get_size().width - incr->get_width() - decr->get_width(); + int limit = get_size().width; + int limit_minus_buttons = get_size().width - incr->get_width() - decr->get_width(); + for (int i = offset; i <= p_idx; i++) { - if (tabs[i].ofs_cache + tabs[i].size_cache > limit) { + int total_w = tabs[i].ofs_cache + tabs[i].size_cache; + if (total_w > limit || (buttons_visible && total_w > limit_minus_buttons)) { offset++; } } @@ -1105,6 +1130,8 @@ void Tabs::_bind_methods() { ClassDB::bind_method(D_METHOD("add_tab", "title", "icon"), &Tabs::add_tab, DEFVAL(""), DEFVAL(Ref<Texture2D>())); ClassDB::bind_method(D_METHOD("set_tab_align", "align"), &Tabs::set_tab_align); ClassDB::bind_method(D_METHOD("get_tab_align"), &Tabs::get_tab_align); + ClassDB::bind_method(D_METHOD("set_clip_tabs", "clip_tabs"), &Tabs::set_clip_tabs); + ClassDB::bind_method(D_METHOD("get_clip_tabs"), &Tabs::get_clip_tabs); ClassDB::bind_method(D_METHOD("get_tab_offset"), &Tabs::get_tab_offset); ClassDB::bind_method(D_METHOD("get_offset_buttons_visible"), &Tabs::get_offset_buttons_visible); ClassDB::bind_method(D_METHOD("ensure_tab_visible", "idx"), &Tabs::ensure_tab_visible); @@ -1131,6 +1158,7 @@ void Tabs::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::INT, "current_tab", PROPERTY_HINT_RANGE, "-1,4096,1", PROPERTY_USAGE_EDITOR), "set_current_tab", "get_current_tab"); ADD_PROPERTY(PropertyInfo(Variant::INT, "tab_align", PROPERTY_HINT_ENUM, "Left,Center,Right"), "set_tab_align", "get_tab_align"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "clip_tabs"), "set_clip_tabs", "get_clip_tabs"); ADD_PROPERTY(PropertyInfo(Variant::INT, "tab_close_display_policy", PROPERTY_HINT_ENUM, "Show Never,Show Active Only,Show Always"), "set_tab_close_display_policy", "get_tab_close_display_policy"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "scrolling_enabled"), "set_scrolling_enabled", "get_scrolling_enabled"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "drag_to_rearrange_enabled"), "set_drag_to_rearrange_enabled", "get_drag_to_rearrange_enabled"); diff --git a/scene/gui/tabs.h b/scene/gui/tabs.h index 86877f4d80..61c9a5d96a 100644 --- a/scene/gui/tabs.h +++ b/scene/gui/tabs.h @@ -85,6 +85,7 @@ private: int previous = 0; int _get_top_margin() const; TabAlign tab_align = ALIGN_CENTER; + bool clip_tabs = true; int rb_hover = -1; bool rb_pressing = false; @@ -148,6 +149,9 @@ public: void set_tab_align(TabAlign p_align); TabAlign get_tab_align() const; + void set_clip_tabs(bool p_clip_tabs); + bool get_clip_tabs() const; + void move_tab(int from, int to); void set_tab_close_display_policy(CloseButtonDisplayPolicy p_policy); diff --git a/scene/gui/text_edit.cpp b/scene/gui/text_edit.cpp index 61266f6d22..07ccad70b1 100644 --- a/scene/gui/text_edit.cpp +++ b/scene/gui/text_edit.cpp @@ -253,7 +253,6 @@ void TextEdit::Text::set(int p_line, const String &p_text, const Vector<Vector2i void TextEdit::Text::insert(int p_at, const String &p_text, const Vector<Vector2i> &p_bidi_override) { Line line; line.gutters.resize(gutter_count); - line.marked = false; line.hidden = false; line.data = p_text; line.bidi_override = p_bidi_override; @@ -357,10 +356,10 @@ void TextEdit::_update_scrollbars() { } void TextEdit::_click_selection_held() { - // Warning: is_mouse_button_pressed(BUTTON_LEFT) returns false for double+ clicks, so this doesn't work for MODE_WORD + // Warning: is_mouse_button_pressed(MOUSE_BUTTON_LEFT) returns false for double+ clicks, so this doesn't work for MODE_WORD // and MODE_LINE. However, moving the mouse triggers _gui_input, which calls these functions too, so that's not a huge problem. // I'm unsure if there's an actual fix that doesn't have a ton of side effects. - if (Input::get_singleton()->is_mouse_button_pressed(BUTTON_LEFT) && selection.selecting_mode != SelectionMode::SELECTION_MODE_NONE) { + if (Input::get_singleton()->is_mouse_button_pressed(MOUSE_BUTTON_LEFT) && selection.selecting_mode != SelectionMode::SELECTION_MODE_NONE) { switch (selection.selecting_mode) { case SelectionMode::SELECTION_MODE_POINTER: { _update_selection_mode_pointer(); @@ -802,9 +801,6 @@ void TextEdit::_notification(int p_what) { } } - bool is_cursor_line_visible = false; - Point2 cursor_pos; - // Get the highlighted words. String highlighted_text = get_selection_text(); @@ -867,6 +863,8 @@ void TextEdit::_notification(int p_what) { Dictionary color_map = _get_line_syntax_highlighting(minimap_line); + Color line_background_color = text.get_line_background_color(minimap_line); + line_background_color.a *= 0.6; Color current_color = cache.font_color; if (readonly) { current_color = cache.font_readonly_color; @@ -901,6 +899,12 @@ void TextEdit::_notification(int p_what) { } else { RenderingServer::get_singleton()->canvas_item_add_rect(ci, Rect2((xmargin_end + 2), i * 3, cache.minimap_width, 2), cache.current_line_color); } + } else if (line_background_color != Color(0, 0, 0, 0)) { + if (rtl) { + RenderingServer::get_singleton()->canvas_item_add_rect(ci, Rect2(size.width - (xmargin_end + 2) - cache.minimap_width, i * 3, cache.minimap_width, 2), line_background_color); + } else { + RenderingServer::get_singleton()->canvas_item_add_rect(ci, Rect2((xmargin_end + 2), i * 3, cache.minimap_width, 2), line_background_color); + } } Color previous_color; @@ -980,6 +984,8 @@ void TextEdit::_notification(int p_what) { } // draw main text + cursor.visible = false; + const int caret_wrap_index = get_cursor_wrap_index(); int row_height = get_row_height(); int line = first_visible_line; for (int i = 0; i < draw_amount; i++) { @@ -1048,11 +1054,11 @@ void TextEdit::_notification(int p_what) { break; } - if (text.is_marked(line)) { + if (text.get_line_background_color(line) != Color(0, 0, 0, 0)) { if (rtl) { - RenderingServer::get_singleton()->canvas_item_add_rect(ci, Rect2(size.width - ofs_x - xmargin_end, ofs_y, xmargin_end - xmargin_beg, row_height), cache.mark_color); + RenderingServer::get_singleton()->canvas_item_add_rect(ci, Rect2(size.width - ofs_x - xmargin_end, ofs_y, xmargin_end - xmargin_beg, row_height), text.get_line_background_color(line)); } else { - RenderingServer::get_singleton()->canvas_item_add_rect(ci, Rect2(xmargin_beg + ofs_x, ofs_y, xmargin_end - xmargin_beg, row_height), cache.mark_color); + RenderingServer::get_singleton()->canvas_item_add_rect(ci, Rect2(xmargin_beg + ofs_x, ofs_y, xmargin_end - xmargin_beg, row_height), text.get_line_background_color(line)); } } @@ -1180,7 +1186,8 @@ void TextEdit::_notification(int p_what) { if (rect.position.x < xmargin_beg) { rect.size.x -= (xmargin_beg - rect.position.x); rect.position.x = xmargin_beg; - } else if (rect.position.x + rect.size.x > xmargin_end) { + } + if (rect.position.x + rect.size.x > xmargin_end) { rect.size.x = xmargin_end - rect.position.x; } draw_rect(rect, cache.selection_color, true); @@ -1259,7 +1266,6 @@ void TextEdit::_notification(int p_what) { } } - const int line_top_offset_y = ofs_y; ofs_y += (row_height - text_height) / 2; const Vector<TextServer::Glyph> visual = TS->shaped_text_get_glyphs(rid); @@ -1364,9 +1370,9 @@ void TextEdit::_notification(int p_what) { #else int caret_width = 1; #endif - if (!clipped && cursor.line == line && ((line_wrap_index == line_wrap_amount) || (cursor.column != TS->shaped_text_get_range(rid).y))) { - is_cursor_line_visible = true; - cursor_pos.y = line_top_offset_y; + + if (!clipped && cursor.line == line && line_wrap_index == caret_wrap_index) { + cursor.draw_pos.y = ofs_y + ldata->get_line_descent(line_wrap_index); if (ime_text.length() == 0) { Rect2 l_caret, t_caret; @@ -1387,57 +1393,60 @@ void TextEdit::_notification(int p_what) { } if ((l_caret != Rect2() && (l_dir == TextServer::DIRECTION_AUTO || l_dir == (TextServer::Direction)input_direction)) || (t_caret == Rect2())) { - cursor_pos.x = char_margin + ofs_x + l_caret.position.x; + cursor.draw_pos.x = char_margin + ofs_x + l_caret.position.x; } else { - cursor_pos.x = char_margin + ofs_x + t_caret.position.x; + cursor.draw_pos.x = char_margin + ofs_x + t_caret.position.x; } - if (draw_caret && cursor_pos.x >= xmargin_beg && cursor_pos.x < xmargin_end) { - if (block_caret || insert_mode) { - //Block or underline caret, draw trailing carets at full height. - int h = cache.font->get_height(cache.font_size); - - if (t_caret != Rect2()) { - if (insert_mode) { - t_caret.position.y = TS->shaped_text_get_descent(rid); - t_caret.size.y = caret_width; - } else { - t_caret.position.y = -TS->shaped_text_get_ascent(rid); - t_caret.size.y = h; - } - t_caret.position += Vector2(char_margin + ofs_x, ofs_y); + if (cursor.draw_pos.x >= xmargin_beg && cursor.draw_pos.x < xmargin_end) { + cursor.visible = true; + if (draw_caret) { + if (block_caret || insert_mode) { + //Block or underline caret, draw trailing carets at full height. + int h = cache.font->get_height(cache.font_size); + + if (t_caret != Rect2()) { + if (insert_mode) { + t_caret.position.y = TS->shaped_text_get_descent(rid); + t_caret.size.y = caret_width; + } else { + t_caret.position.y = -TS->shaped_text_get_ascent(rid); + t_caret.size.y = h; + } + t_caret.position += Vector2(char_margin + ofs_x, ofs_y); + + draw_rect(t_caret, cache.caret_color, false); + } else { // End of the line. + if (insert_mode) { + l_caret.position.y = TS->shaped_text_get_descent(rid); + l_caret.size.y = caret_width; + } else { + l_caret.position.y = -TS->shaped_text_get_ascent(rid); + l_caret.size.y = h; + } + l_caret.position += Vector2(char_margin + ofs_x, ofs_y); + l_caret.size.x = cache.font->get_char_size('M', 0, cache.font_size).x; - draw_rect(t_caret, cache.caret_color, false); - } else { // End of the line. - if (insert_mode) { - l_caret.position.y = TS->shaped_text_get_descent(rid); - l_caret.size.y = caret_width; - } else { - l_caret.position.y = -TS->shaped_text_get_ascent(rid); - l_caret.size.y = h; + draw_rect(l_caret, cache.caret_color, false); + } + } else { + // Normal caret. + if (l_caret != Rect2() && l_dir == TextServer::DIRECTION_AUTO) { + // Draw extra marker on top of mid caret. + Rect2 trect = Rect2(l_caret.position.x - 3 * caret_width, l_caret.position.y, 6 * caret_width, caret_width); + trect.position += Vector2(char_margin + ofs_x, ofs_y); + RenderingServer::get_singleton()->canvas_item_add_rect(ci, trect, cache.caret_color); } l_caret.position += Vector2(char_margin + ofs_x, ofs_y); - l_caret.size.x = cache.font->get_char_size('M', 0, cache.font_size).x; - - draw_rect(l_caret, cache.caret_color, false); - } - } else { - // Normal caret. - if (l_caret != Rect2() && l_dir == TextServer::DIRECTION_AUTO) { - // Draw extra marker on top of mid caret. - Rect2 trect = Rect2(l_caret.position.x - 3 * caret_width, l_caret.position.y, 6 * caret_width, caret_width); - trect.position += Vector2(char_margin + ofs_x, ofs_y); - RenderingServer::get_singleton()->canvas_item_add_rect(ci, trect, cache.caret_color); - } - l_caret.position += Vector2(char_margin + ofs_x, ofs_y); - l_caret.size.x = caret_width; + l_caret.size.x = caret_width; - draw_rect(l_caret, cache.caret_color); + draw_rect(l_caret, cache.caret_color); - t_caret.position += Vector2(char_margin + ofs_x, ofs_y); - t_caret.size.x = caret_width; + t_caret.position += Vector2(char_margin + ofs_x, ofs_y); + t_caret.size.x = caret_width; - draw_rect(t_caret, cache.caret_color); + draw_rect(t_caret, cache.caret_color); + } } } } else { @@ -1457,7 +1466,7 @@ void TextEdit::_notification(int p_what) { } rect.size.y = caret_width; draw_rect(rect, cache.caret_color); - cursor_pos.x = rect.position.x; + cursor.draw_pos.x = rect.position.x; } } { @@ -1476,7 +1485,7 @@ void TextEdit::_notification(int p_what) { } rect.size.y = caret_width * 3; draw_rect(rect, cache.caret_color); - cursor_pos.x = rect.position.x; + cursor.draw_pos.x = rect.position.x; } } } @@ -1484,227 +1493,10 @@ void TextEdit::_notification(int p_what) { } } - bool completion_below = false; - if (completion_active && is_cursor_line_visible && completion_options.size() > 0) { - // Completion panel - - const Ref<StyleBox> csb = get_theme_stylebox("completion"); - const int maxlines = get_theme_constant("completion_lines"); - const int cmax_width = get_theme_constant("completion_max_width") * cache.font->get_char_size('x', 0, cache.font_size).x; - const Color scrollc = get_theme_color("completion_scroll_color"); - - const int completion_options_size = completion_options.size(); - const int row_count = MIN(completion_options_size, maxlines); - const int completion_rows_height = row_count * row_height; - const int completion_base_width = cache.font->get_string_size(completion_base, cache.font_size).width; - - int scroll_rectangle_width = get_theme_constant("completion_scroll_width"); - int width = 0; - - // Compute max width of the panel based on the longest completion option - if (completion_options_size < 50) { - for (int i = 0; i < completion_options_size; i++) { - int line_width = MIN(cache.font->get_string_size(completion_options[i].display, cache.font_size).x, cmax_width); - if (line_width > width) { - width = line_width; - } - } - } else { - width = cmax_width; - } - - // Add space for completion icons. - const int icon_hsep = get_theme_constant("hseparation", "ItemList"); - const Size2 icon_area_size(row_height, row_height); - const int icon_area_width = icon_area_size.width + icon_hsep; - width += icon_area_width; - - const int line_from = CLAMP(completion_index - row_count / 2, 0, completion_options_size - row_count); - - for (int i = 0; i < row_count; i++) { - int l = line_from + i; - ERR_CONTINUE(l < 0 || l >= completion_options_size); - if (completion_options[l].default_value.get_type() == Variant::COLOR) { - width += icon_area_size.width; - break; - } - } - - // Position completion panel - completion_rect.size.width = width + 2; - completion_rect.size.height = completion_rows_height; - - if (completion_options_size <= maxlines) { - scroll_rectangle_width = 0; - } - - const Point2 csb_offset = csb->get_offset(); - - const int total_width = completion_rect.size.width + csb->get_minimum_size().x + scroll_rectangle_width; - const int total_height = completion_rect.size.height + csb->get_minimum_size().y; - - const int rect_left_border_x = cursor_pos.x - completion_base_width - icon_area_width - csb_offset.x; - const int rect_right_border_x = rect_left_border_x + total_width; - - if (rect_left_border_x < 0) { - // Anchor the completion panel to the left - completion_rect.position.x = 0; - } else if (rect_right_border_x > get_size().width) { - // Anchor the completion panel to the right - completion_rect.position.x = get_size().width - total_width; - } else { - // Let the completion panel float with the cursor - completion_rect.position.x = rect_left_border_x; - } - - if (cursor_pos.y + row_height + total_height > get_size().height) { - // Completion panel above the cursor line - completion_rect.position.y = cursor_pos.y - total_height; - } else { - // Completion panel below the cursor line - completion_rect.position.y = cursor_pos.y + row_height; - completion_below = true; - } - - draw_style_box(csb, Rect2(completion_rect.position - csb_offset, completion_rect.size + csb->get_minimum_size() + Size2(scroll_rectangle_width, 0))); - - if (cache.completion_background_color.a > 0.01) { - RenderingServer::get_singleton()->canvas_item_add_rect(ci, Rect2(completion_rect.position, completion_rect.size + Size2(scroll_rectangle_width, 0)), cache.completion_background_color); - } - RenderingServer::get_singleton()->canvas_item_add_rect(ci, Rect2(Point2(completion_rect.position.x, completion_rect.position.y + (completion_index - line_from) * get_row_height()), Size2(completion_rect.size.width, get_row_height())), cache.completion_selected_color); - - draw_rect(Rect2(completion_rect.position + Vector2(icon_area_size.x + icon_hsep, 0), Size2(MIN(completion_base_width, completion_rect.size.width - (icon_area_size.x + icon_hsep)), completion_rect.size.height)), cache.completion_existing_color); - - for (int i = 0; i < row_count; i++) { - int l = line_from + i; - ERR_CONTINUE(l < 0 || l >= completion_options_size); - - Ref<TextLine> tl; - tl.instance(); - tl->add_string(completion_options[l].display, cache.font, cache.font_size); - - int yofs = (row_height - tl->get_size().y) / 2; - Point2 title_pos(completion_rect.position.x, completion_rect.position.y + i * row_height + yofs); - - // Draw completion icon if it is valid. - Ref<Texture2D> icon = completion_options[l].icon; - Rect2 icon_area(completion_rect.position.x, completion_rect.position.y + i * row_height, icon_area_size.width, icon_area_size.height); - if (icon.is_valid()) { - const real_t max_scale = 0.7f; - const real_t side = max_scale * icon_area.size.width; - real_t scale = MIN(side / icon->get_width(), side / icon->get_height()); - Size2 icon_size = icon->get_size() * scale; - draw_texture_rect(icon, Rect2(icon_area.position + (icon_area.size - icon_size) / 2, icon_size)); - } - - title_pos.x = icon_area.position.x + icon_area.size.width + icon_hsep; - - tl->set_width(completion_rect.size.width - (icon_area_size.x + icon_hsep)); - - if (rtl) { - if (completion_options[l].default_value.get_type() == Variant::COLOR) { - draw_rect(Rect2(Point2(completion_rect.position.x, icon_area.position.y), icon_area_size), (Color)completion_options[l].default_value); - } - tl->set_align(HALIGN_RIGHT); - } else { - if (completion_options[l].default_value.get_type() == Variant::COLOR) { - draw_rect(Rect2(Point2(completion_rect.position.x + completion_rect.size.width - icon_area_size.x, icon_area.position.y), icon_area_size), (Color)completion_options[l].default_value); - } - tl->set_align(HALIGN_LEFT); - } - if (cache.outline_size > 0 && cache.outline_color.a > 0) { - tl->draw_outline(ci, title_pos, cache.outline_size, cache.outline_color); - } - tl->draw(ci, title_pos, completion_options[l].font_color); - } - - if (scroll_rectangle_width) { - // Draw a small scroll rectangle to show a position in the options. - float r = (float)maxlines / completion_options_size; - float o = (float)line_from / completion_options_size; - draw_rect(Rect2(completion_rect.position.x + completion_rect.size.width, completion_rect.position.y + o * completion_rect.size.y, scroll_rectangle_width, completion_rect.size.y * r), scrollc); - } - - completion_line_ofs = line_from; - } - - // Check to see if the hint should be drawn. - bool show_hint = false; - if (is_cursor_line_visible && completion_hint != "") { - if (completion_active) { - if (completion_below && !callhint_below) { - show_hint = true; - } else if (!completion_below && callhint_below) { - show_hint = true; - } - } else { - show_hint = true; - } - } - - if (show_hint) { - Ref<StyleBox> sb = get_theme_stylebox("panel", "TooltipPanel"); - Ref<Font> font = cache.font; - Color font_color = get_theme_color("font_color", "TooltipLabel"); - - int max_w = 0; - int sc = completion_hint.get_slice_count("\n"); - int offset = 0; - int spacing = 0; - for (int i = 0; i < sc; i++) { - String l = completion_hint.get_slice("\n", i); - int len = font->get_string_size(l, cache.font_size).x; - max_w = MAX(len, max_w); - if (i == 0) { - offset = font->get_string_size(l.substr(0, l.find(String::chr(0xFFFF))), cache.font_size).x; - } else { - spacing += cache.line_spacing; - } - } - - Size2 size2 = Size2(max_w, sc * font->get_height(cache.font_size) + spacing); - Size2 minsize = size2 + sb->get_minimum_size(); - - if (completion_hint_offset == -0xFFFF) { - completion_hint_offset = cursor_pos.x - offset; - } - - Point2 hint_ofs = Vector2(completion_hint_offset, cursor_pos.y) + callhint_offset; - - if (callhint_below) { - hint_ofs.y += row_height + sb->get_offset().y; - } else { - hint_ofs.y -= minsize.y + sb->get_offset().y; - } - - draw_style_box(sb, Rect2(hint_ofs, minsize)); - - spacing = 0; - for (int i = 0; i < sc; i++) { - int begin = 0; - int end = 0; - String l = completion_hint.get_slice("\n", i); - - if (l.find(String::chr(0xFFFF)) != -1) { - begin = font->get_string_size(l.substr(0, l.find(String::chr(0xFFFF))), cache.font_size).x; - end = font->get_string_size(l.substr(0, l.rfind(String::chr(0xFFFF))), cache.font_size).x; - } - - Point2 round_ofs = hint_ofs + sb->get_offset() + Vector2(0, font->get_ascent(cache.font_size) + font->get_height(cache.font_size) * i + spacing); - round_ofs = round_ofs.round(); - draw_string(font, round_ofs, l.replace(String::chr(0xFFFF), ""), HALIGN_LEFT, -1, cache.font_size, font_color); - if (end > 0) { - Vector2 b = hint_ofs + sb->get_offset() + Vector2(begin, font->get_height(cache.font_size) + font->get_height(cache.font_size) * i + spacing - 1); - draw_line(b, b + Vector2(end - begin, 0), font_color); - } - spacing += cache.line_spacing; - } - } - if (has_focus()) { if (get_viewport()->get_window_id() != DisplayServer::INVALID_WINDOW_ID && DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_IME)) { DisplayServer::get_singleton()->window_set_ime_active(true, get_viewport()->get_window_id()); - DisplayServer::get_singleton()->window_set_ime_position(get_global_position() + cursor_pos, get_viewport()->get_window_id()); + DisplayServer::get_singleton()->window_set_ime_position(get_global_position() + cursor.draw_pos, get_viewport()->get_window_id()); } } } break; @@ -1973,7 +1765,7 @@ void TextEdit::backspace_at_cursor() { } } - cursor_set_line(prev_line, true, true); + cursor_set_line(prev_line, false, true); cursor_set_column(prev_column); } @@ -2054,6 +1846,7 @@ void TextEdit::indent_selected_lines_left() { if (is_selection_active() && get_selection_to_column() == 0) { end_line--; } + String first_line_text = get_line(start_line); String last_line_text = get_line(end_line); for (int i = start_line; i <= end_line; i++) { @@ -2078,10 +1871,17 @@ void TextEdit::indent_selected_lines_left() { } } - // Fix selection and cursor being off by one on the last line. - if (is_selection_active() && last_line_text != get_line(end_line)) { - select(selection.from_line, selection.from_column - removed_characters, - selection.to_line, initial_selection_end_column - removed_characters); + if (is_selection_active()) { + // Fix selection being off by one on the first line. + if (first_line_text != get_line(start_line)) { + select(selection.from_line, selection.from_column - removed_characters, + selection.to_line, initial_selection_end_column); + } + // Fix selection being off by one on the last line. + if (last_line_text != get_line(end_line)) { + select(selection.from_line, selection.from_column, + selection.to_line, initial_selection_end_column - removed_characters); + } } cursor_set_column(initial_cursor_column - removed_characters, false); end_complex_operation(); @@ -2207,7 +2007,7 @@ void TextEdit::_new_line(bool p_split_current_line, bool p_above) { if (!p_split_current_line) { if (p_above) { if (cursor.line > 0) { - cursor_set_line(cursor.line - 1); + cursor_set_line(cursor.line - 1, false); cursor_set_column(text[cursor.line].length()); } else { cursor_set_column(0); @@ -2223,7 +2023,7 @@ void TextEdit::_new_line(bool p_split_current_line, bool p_above) { if (first_line) { cursor_set_line(0); } else if (brace_indent) { - cursor_set_line(cursor.line - 1); + cursor_set_line(cursor.line - 1, false); cursor_set_column(text[cursor.line].length()); } end_complex_operation(); @@ -2411,8 +2211,6 @@ void TextEdit::_move_cursor_up(bool p_select) { if (p_select) { _post_shift_selection(); } - - _cancel_code_hint(); } void TextEdit::_move_cursor_down(bool p_select) { @@ -2435,8 +2233,6 @@ void TextEdit::_move_cursor_down(bool p_select) { if (p_select) { _post_shift_selection(); } - - _cancel_code_hint(); } void TextEdit::_move_cursor_to_line_start(bool p_select) { @@ -2476,9 +2272,6 @@ void TextEdit::_move_cursor_to_line_start(bool p_select) { if (p_select) { _post_shift_selection(); } - - _cancel_completion(); - completion_hint = ""; } void TextEdit::_move_cursor_to_line_end(bool p_select) { @@ -2504,8 +2297,6 @@ void TextEdit::_move_cursor_to_line_end(bool p_select) { if (p_select) { _post_shift_selection(); } - _cancel_completion(); - completion_hint = ""; } void TextEdit::_move_cursor_page_up(bool p_select) { @@ -2522,9 +2313,6 @@ void TextEdit::_move_cursor_page_up(bool p_select) { if (p_select) { _post_shift_selection(); } - - _cancel_completion(); - completion_hint = ""; } void TextEdit::_move_cursor_page_down(bool p_select) { @@ -2541,9 +2329,6 @@ void TextEdit::_move_cursor_page_down(bool p_select) { if (p_select) { _post_shift_selection(); } - - _cancel_completion(); - completion_hint = ""; } void TextEdit::_backspace(bool p_word, bool p_all_to_left) { @@ -2573,7 +2358,7 @@ void TextEdit::_backspace(bool p_word, bool p_all_to_left) { _remove_text(line, column, cursor.line, cursor.column); - cursor_set_line(line); + cursor_set_line(line, false); cursor_set_column(column); } else { // One character. @@ -2640,7 +2425,7 @@ void TextEdit::_delete_selection() { selection.active = false; update(); _remove_text(selection.from_line, selection.from_column, selection.to_line, selection.to_column); - cursor_set_line(selection.from_line, true, false); + cursor_set_line(selection.from_line, false, false); cursor_set_column(selection.from_column); update(); } @@ -2676,11 +2461,7 @@ void TextEdit::_move_cursor_document_end(bool p_select) { } } -void TextEdit::_handle_unicode_character(uint32_t unicode, bool p_had_selection, bool p_update_auto_complete) { - if (p_update_auto_complete) { - _reset_caret_blink_timer(); - } - +void TextEdit::_handle_unicode_character(uint32_t unicode, bool p_had_selection) { if (p_had_selection) { _delete_selection(); } @@ -2697,11 +2478,6 @@ void TextEdit::_handle_unicode_character(uint32_t unicode, bool p_had_selection, const char32_t chr[2] = { (char32_t)unicode, 0 }; - // Clear completion hint when function closed - if (completion_hint != "" && unicode == ')') { - completion_hint = ""; - } - if (auto_brace_completion_enabled && _is_pair_symbol(chr[0])) { _consume_pair_symbol(chr[0]); } else { @@ -2711,10 +2487,6 @@ void TextEdit::_handle_unicode_character(uint32_t unicode, bool p_had_selection, if ((insert_mode && !p_had_selection) || (selection.active != p_had_selection)) { end_complex_operation(); } - - if (p_update_auto_complete) { - _update_completion_candidates(); - } } void TextEdit::_get_mouse_pos(const Point2i &p_mouse, int &r_row, int &r_col) const { @@ -2854,6 +2626,8 @@ void TextEdit::_get_minimap_mouse_row(const Point2i &p_mouse, int &r_row) const } void TextEdit::_gui_input(const Ref<InputEvent> &p_gui_input) { + ERR_FAIL_COND(p_gui_input.is_null()); + double prev_v_scroll = v_scroll->get_value(); double prev_h_scroll = h_scroll->get_value(); @@ -2868,63 +2642,37 @@ void TextEdit::_gui_input(const Ref<InputEvent> &p_gui_input) { // Ignore mouse clicks in IME input mode. return; } - if (completion_active && completion_rect.has_point(mpos)) { - if (!mb->is_pressed()) { - return; - } - - if (mb->get_button_index() == BUTTON_WHEEL_UP) { - if (completion_index > 0) { - completion_index--; - completion_current = completion_options[completion_index]; - update(); - } - } - if (mb->get_button_index() == BUTTON_WHEEL_DOWN) { - if (completion_index < completion_options.size() - 1) { - completion_index++; - completion_current = completion_options[completion_index]; - update(); - } - } - - if (mb->get_button_index() == BUTTON_LEFT) { - completion_index = CLAMP(completion_line_ofs + (mpos.y - completion_rect.position.y) / get_row_height(), 0, completion_options.size() - 1); - - completion_current = completion_options[completion_index]; - update(); - if (mb->is_doubleclick()) { - _confirm_completion(); - } - } - return; - } else { - _cancel_completion(); - _cancel_code_hint(); - } if (mb->is_pressed()) { - if (mb->get_button_index() == BUTTON_WHEEL_UP && !mb->get_command()) { - if (mb->get_shift()) { + if (mb->get_button_index() == MOUSE_BUTTON_WHEEL_UP && !mb->is_command_pressed()) { + if (mb->is_shift_pressed()) { h_scroll->set_value(h_scroll->get_value() - (100 * mb->get_factor())); + } else if (mb->is_alt_pressed()) { + // Scroll 5 times as fast as normal (like in Visual Studio Code). + _scroll_up(15 * mb->get_factor()); } else if (v_scroll->is_visible()) { + // Scroll 3 lines. _scroll_up(3 * mb->get_factor()); } } - if (mb->get_button_index() == BUTTON_WHEEL_DOWN && !mb->get_command()) { - if (mb->get_shift()) { + if (mb->get_button_index() == MOUSE_BUTTON_WHEEL_DOWN && !mb->is_command_pressed()) { + if (mb->is_shift_pressed()) { h_scroll->set_value(h_scroll->get_value() + (100 * mb->get_factor())); + } else if (mb->is_alt_pressed()) { + // Scroll 5 times as fast as normal (like in Visual Studio Code). + _scroll_down(15 * mb->get_factor()); } else if (v_scroll->is_visible()) { + // Scroll 3 lines. _scroll_down(3 * mb->get_factor()); } } - if (mb->get_button_index() == BUTTON_WHEEL_LEFT) { + if (mb->get_button_index() == MOUSE_BUTTON_WHEEL_LEFT) { h_scroll->set_value(h_scroll->get_value() - (100 * mb->get_factor())); } - if (mb->get_button_index() == BUTTON_WHEEL_RIGHT) { + if (mb->get_button_index() == MOUSE_BUTTON_WHEEL_RIGHT) { h_scroll->set_value(h_scroll->get_value() + (100 * mb->get_factor())); } - if (mb->get_button_index() == BUTTON_LEFT) { + if (mb->get_button_index() == MOUSE_BUTTON_LEFT) { _reset_caret_blink_timer(); int row, col; @@ -2967,7 +2715,7 @@ void TextEdit::_gui_input(const Ref<InputEvent> &p_gui_input) { cursor_set_line(row, false, false); cursor_set_column(col); - if (mb->get_shift() && (cursor.column != prev_col || cursor.line != prev_line)) { + if (mb->is_shift_pressed() && (cursor.column != prev_col || cursor.line != prev_line)) { if (!selection.active) { selection.active = true; selection.selecting_mode = SelectionMode::SELECTION_MODE_POINTER; @@ -3016,12 +2764,12 @@ void TextEdit::_gui_input(const Ref<InputEvent> &p_gui_input) { selection.selecting_column = col; } - if (!mb->is_doubleclick() && (OS::get_singleton()->get_ticks_msec() - last_dblclk) < 600 && cursor.line == prev_line) { + if (!mb->is_double_click() && (OS::get_singleton()->get_ticks_msec() - last_dblclk) < 600 && cursor.line == prev_line) { // Triple-click select line. selection.selecting_mode = SelectionMode::SELECTION_MODE_LINE; _update_selection_mode_line(); last_dblclk = 0; - } else if (mb->is_doubleclick() && text[cursor.line].length()) { + } else if (mb->is_double_click() && text[cursor.line].length()) { // Double-click select word. selection.selecting_mode = SelectionMode::SELECTION_MODE_WORD; _update_selection_mode_word(); @@ -3031,7 +2779,7 @@ void TextEdit::_gui_input(const Ref<InputEvent> &p_gui_input) { update(); } - if (mb->get_button_index() == BUTTON_RIGHT && context_menu_enabled) { + if (mb->get_button_index() == MOUSE_BUTTON_RIGHT && context_menu_enabled) { _reset_caret_blink_timer(); int row, col; @@ -3062,8 +2810,8 @@ void TextEdit::_gui_input(const Ref<InputEvent> &p_gui_input) { grab_focus(); } } else { - if (mb->get_button_index() == BUTTON_LEFT) { - if (mb->get_command() && highlighted_word != String()) { + if (mb->get_button_index() == MOUSE_BUTTON_LEFT) { + if (mb->is_command_pressed() && highlighted_word != String()) { int row, col; _get_mouse_pos(Point2i(mpos.x, mpos.y), row, col); @@ -3106,7 +2854,7 @@ void TextEdit::_gui_input(const Ref<InputEvent> &p_gui_input) { mpos.x = get_size().x - mpos.x; } if (select_identifiers_enabled) { - if (!dragging_minimap && !dragging_selection && mm->get_command() && mm->get_button_mask() == 0) { + if (!dragging_minimap && !dragging_selection && mm->is_command_pressed() && mm->get_button_mask() == 0) { String new_word = get_word_at_pos(mpos); if (new_word != highlighted_word) { emit_signal("symbol_validate", new_word); @@ -3118,7 +2866,7 @@ void TextEdit::_gui_input(const Ref<InputEvent> &p_gui_input) { } } - if (mm->get_button_mask() & BUTTON_MASK_LEFT && get_viewport()->gui_get_drag_data() == Variant()) { // Ignore if dragging. + if (mm->get_button_mask() & MOUSE_BUTTON_MASK_LEFT && get_viewport()->gui_get_drag_data() == Variant()) { // Ignore if dragging. _reset_caret_blink_timer(); if (draw_minimap && !dragging_selection) { @@ -3155,7 +2903,7 @@ void TextEdit::_gui_input(const Ref<InputEvent> &p_gui_input) { #ifdef OSX_ENABLED if (k->get_keycode() == KEY_META) { #else - if (k->get_keycode() == KEY_CONTROL) { + if (k->get_keycode() == KEY_CTRL) { #endif if (select_identifiers_enabled) { if (k->is_pressed() && !dragging_minimap && !dragging_selection) { @@ -3173,7 +2921,7 @@ void TextEdit::_gui_input(const Ref<InputEvent> &p_gui_input) { } // If a modifier has been pressed, and nothing else, return. - if (k->get_keycode() == KEY_CONTROL || k->get_keycode() == KEY_ALT || k->get_keycode() == KEY_SHIFT || k->get_keycode() == KEY_META) { + if (k->get_keycode() == KEY_CTRL || k->get_keycode() == KEY_ALT || k->get_keycode() == KEY_SHIFT || k->get_keycode() == KEY_META) { return; } @@ -3181,7 +2929,7 @@ void TextEdit::_gui_input(const Ref<InputEvent> &p_gui_input) { // Allow unicode handling if: // * No Modifiers are pressed (except shift) - bool allow_unicode_handling = !(k->get_command() || k->get_control() || k->get_alt() || k->get_metakey()); + bool allow_unicode_handling = !(k->is_command_pressed() || k->is_ctrl_pressed() || k->is_alt_pressed() || k->is_meta_pressed()); // Save here for insert mode, just in case it is cleared in the following section. bool had_selection = selection.active; @@ -3190,96 +2938,6 @@ void TextEdit::_gui_input(const Ref<InputEvent> &p_gui_input) { // Check and handle all built in shortcuts. - // AUTO-COMPLETE - - if (k->is_action("ui_text_completion_query", true)) { - query_code_comple(); - accept_event(); - return; - } - - if (completion_active) { - if (k->is_action("ui_up", true)) { - if (completion_index > 0) { - completion_index--; - } else { - completion_index = completion_options.size() - 1; - } - completion_current = completion_options[completion_index]; - update(); - accept_event(); - return; - } - if (k->is_action("ui_down", true)) { - if (completion_index < completion_options.size() - 1) { - completion_index++; - } else { - completion_index = 0; - } - completion_current = completion_options[completion_index]; - update(); - accept_event(); - return; - } - if (k->is_action("ui_page_up", true)) { - completion_index -= get_theme_constant("completion_lines"); - if (completion_index < 0) { - completion_index = 0; - } - completion_current = completion_options[completion_index]; - update(); - accept_event(); - return; - } - if (k->is_action("ui_page_down", true)) { - completion_index += get_theme_constant("completion_lines"); - if (completion_index >= completion_options.size()) { - completion_index = completion_options.size() - 1; - } - completion_current = completion_options[completion_index]; - update(); - accept_event(); - return; - } - if (k->is_action("ui_home", true)) { - if (completion_index > 0) { - completion_index = 0; - completion_current = completion_options[completion_index]; - update(); - } - accept_event(); - return; - } - if (k->is_action("ui_end", true)) { - if (completion_index < completion_options.size() - 1) { - completion_index = completion_options.size() - 1; - completion_current = completion_options[completion_index]; - update(); - } - accept_event(); - return; - } - if (k->is_action("ui_accept", true) || k->is_action("ui_text_completion_accept", true)) { - _confirm_completion(); - accept_event(); - return; - } - if (k->is_action("ui_cancel", true)) { - _cancel_completion(); - accept_event(); - return; - } - - // Handle Unicode here (if no modifiers active) and update autocomplete. - if (k->get_unicode() >= 32) { - if (allow_unicode_handling && !readonly) { - _handle_unicode_character(k->get_unicode(), had_selection, true); - accept_event(); - return; - } - } - } - // NEWLINES. if (k->is_action("ui_text_newline_above", true)) { _new_line(false, true); @@ -3322,9 +2980,6 @@ void TextEdit::_gui_input(const Ref<InputEvent> &p_gui_input) { } if (k->is_action("ui_text_backspace", true)) { _backspace(); - if (completion_active) { - _update_completion_candidates(); - } accept_event(); return; } @@ -3356,13 +3011,18 @@ void TextEdit::_gui_input(const Ref<InputEvent> &p_gui_input) { return; } - // SELECT ALL, CUT, COPY, PASTE. + // SELECT ALL, SELECT WORD UNDER CARET, CUT, COPY, PASTE. if (k->is_action("ui_text_select_all", true)) { select_all(); accept_event(); return; } + if (k->is_action("ui_text_select_word_under_caret", true)) { + select_word_under_caret(); + accept_event(); + return; + } if (k->is_action("ui_cut", true)) { cut(); accept_event(); @@ -3392,7 +3052,6 @@ void TextEdit::_gui_input(const Ref<InputEvent> &p_gui_input) { } // MISC. - if (k->is_action("ui_menu", true)) { if (context_menu_enabled) { menu->set_position(get_screen_transform().xform(_get_cursor_pixel_pos())); @@ -3409,14 +3068,6 @@ void TextEdit::_gui_input(const Ref<InputEvent> &p_gui_input) { accept_event(); return; } - if (k->is_action("ui_cancel", true)) { - if (completion_hint != "") { - completion_hint = ""; - update(); - } - accept_event(); - return; - } if (k->is_action("ui_swap_input_direction", true)) { _swap_current_input_direction(); accept_event(); @@ -3426,9 +3077,9 @@ void TextEdit::_gui_input(const Ref<InputEvent> &p_gui_input) { // CURSOR MOVEMENT k = k->duplicate(); - bool shift_pressed = k->get_shift(); + bool shift_pressed = k->is_shift_pressed(); // Remove shift or else actions will not match. Use above variable for selection. - k->set_shift(false); + k->set_shift_pressed(false); // CURSOR MOVEMENT - LEFT, RIGHT. if (k->is_action("ui_text_caret_word_left", true)) { @@ -3502,7 +3153,7 @@ void TextEdit::_gui_input(const Ref<InputEvent> &p_gui_input) { if (allow_unicode_handling && !readonly && k->get_unicode() >= 32) { // Handle Unicode (if no modifiers active). - _handle_unicode_character(k->get_unicode(), had_selection, false); + _handle_unicode_character(k->get_unicode(), had_selection); accept_event(); return; } @@ -3849,7 +3500,7 @@ void TextEdit::_insert_text_at_cursor(const String &p_text) { int new_column, new_line; _insert_text(cursor.line, cursor.column, p_text, &new_line, &new_column); _update_scrollbars(); - cursor_set_line(new_line); + cursor_set_line(new_line, false); cursor_set_column(new_column); update(); @@ -4263,6 +3914,14 @@ void TextEdit::cursor_set_line(int p_row, bool p_adjust_viewport, bool p_can_be_ } } +Point2 TextEdit::get_caret_draw_pos() const { + return cursor.draw_pos; +} + +bool TextEdit::is_caret_visible() const { + return cursor.visible; +} + int TextEdit::cursor_get_column() const { return cursor.column; } @@ -4423,7 +4082,7 @@ int TextEdit::get_column_x_offset_for_line(int p_char, int p_line) const { void TextEdit::insert_text_at_cursor(const String &p_text) { if (selection.active) { - cursor_set_line(selection.from_line); + cursor_set_line(selection.from_line, false); cursor_set_column(selection.from_column); _remove_text(selection.from_line, selection.from_column, selection.to_line, selection.to_column); @@ -4440,10 +4099,6 @@ Control::CursorShape TextEdit::get_cursor_shape(const Point2 &p_pos) const { return CURSOR_POINTING_HAND; } - if ((completion_active && completion_rect.has_point(p_pos))) { - return CURSOR_ARROW; - } - int row, col; _get_mouse_pos(p_pos, row, col); @@ -4655,26 +4310,6 @@ String TextEdit::get_text_for_lookup_completion() { return longthing; } -String TextEdit::get_text_for_completion() { - String longthing; - int len = text.size(); - for (int i = 0; i < len; i++) { - if (i == cursor.line) { - longthing += text[i].substr(0, cursor.column); - longthing += String::chr(0xFFFF); // Not unicode, represents the cursor. - longthing += text[i].substr(cursor.column, text[i].size()); - } else { - longthing += text[i]; - } - - if (i != len - 1) { - longthing += "\n"; - } - } - - return longthing; -}; - String TextEdit::get_line(int line) const { if (line < 0 || line >= text.size()) { return ""; @@ -4757,10 +4392,6 @@ void TextEdit::_update_caches() { cache.style_normal = get_theme_stylebox("normal"); cache.style_focus = get_theme_stylebox("focus"); cache.style_readonly = get_theme_stylebox("read_only"); - cache.completion_background_color = get_theme_color("completion_background_color"); - cache.completion_selected_color = get_theme_color("completion_selected_color"); - cache.completion_existing_color = get_theme_color("completion_existing_color"); - cache.completion_font_color = get_theme_color("completion_font_color"); cache.font = get_theme_font("font"); cache.font_size = get_theme_font_size("font_size"); cache.outline_color = get_theme_color("font_outline_color"); @@ -4771,7 +4402,6 @@ void TextEdit::_update_caches() { cache.font_selected_color = get_theme_color("font_selected_color"); cache.font_readonly_color = get_theme_color("font_readonly_color"); cache.selection_color = get_theme_color("selection_color"); - cache.mark_color = get_theme_color("mark_color"); cache.current_line_color = get_theme_color("current_line_color"); cache.line_length_guideline_color = get_theme_color("line_length_guideline_color"); cache.code_folding_color = get_theme_color("code_folding_color"); @@ -5001,6 +4631,18 @@ bool TextEdit::is_line_gutter_clickable(int p_line, int p_gutter) const { return text.is_line_gutter_clickable(p_line, p_gutter); } +// Line style +void TextEdit::set_line_background_color(int p_line, const Color &p_color) { + ERR_FAIL_INDEX(p_line, text.size()); + text.set_line_background_color(p_line, p_color); + update(); +} + +Color TextEdit::get_line_background_color(int p_line) { + ERR_FAIL_INDEX_V(p_line, text.size(), Color()); + return text.get_line_background_color(p_line); +} + void TextEdit::add_keyword(const String &p_keyword) { keywords.insert(p_keyword); } @@ -5040,7 +4682,7 @@ void TextEdit::cut() { DisplayServer::get_singleton()->clipboard_set(clipboard); _remove_text(selection.from_line, selection.from_column, selection.to_line, selection.to_column); - cursor_set_line(selection.from_line); // Set afterwards else it causes the view to be offset. + cursor_set_line(selection.from_line, false); // Set afterwards else it causes the view to be offset. cursor_set_column(selection.from_column); selection.active = false; @@ -5076,7 +4718,7 @@ void TextEdit::paste() { selection.active = false; selection.selecting_mode = SelectionMode::SELECTION_MODE_NONE; _remove_text(selection.from_line, selection.from_column, selection.to_line, selection.to_column); - cursor_set_line(selection.from_line); + cursor_set_line(selection.from_line, false); cursor_set_column(selection.from_column); } else if (!cut_copy_line.is_empty() && cut_copy_line == clipboard) { @@ -5113,6 +4755,39 @@ void TextEdit::select_all() { update(); } +void TextEdit::select_word_under_caret() { + if (!selecting_enabled) { + return; + } + + if (text.size() == 1 && text[0].length() == 0) { + return; + } + + if (selection.active) { + // Allow toggling selection by pressing the shortcut a second time. + // This is also usable as a general-purpose "deselect" shortcut after + // selecting anything. + deselect(); + return; + } + + int begin = 0; + int end = 0; + const Vector<Vector2i> words = TS->shaped_text_get_word_breaks(text.get_line_data(cursor.line)->get_rid()); + for (int i = 0; i < words.size(); i++) { + if (words[i].x <= cursor.column && words[i].y >= cursor.column) { + begin = words[i].x; + end = words[i].y; + break; + } + } + + select(cursor.line, begin, cursor.line, end); + // Move the cursor to the end of the word for easier editing. + cursor_set_column(end, false); +} + void TextEdit::deselect() { selection.active = false; update(); @@ -5419,12 +5094,6 @@ void TextEdit::_text_changed_emit() { text_changed_dirty = false; } -void TextEdit::set_line_as_marked(int p_line, bool p_marked) { - ERR_FAIL_INDEX(p_line, text.size()); - text.set_marked(p_line, p_marked); - update(); -} - void TextEdit::set_line_as_hidden(int p_line, bool p_hidden) { ERR_FAIL_INDEX(p_line, text.size()); if (is_hiding_enabled() || !p_hidden) { @@ -5815,11 +5484,10 @@ void TextEdit::undo() { _update_scrollbars(); if (undo_stack_pos->get().type == TextOperation::TYPE_REMOVE) { - cursor_set_line(undo_stack_pos->get().to_line); + cursor_set_line(undo_stack_pos->get().to_line, false); cursor_set_column(undo_stack_pos->get().to_column); - _cancel_code_hint(); } else { - cursor_set_line(undo_stack_pos->get().from_line); + cursor_set_line(undo_stack_pos->get().from_line, false); cursor_set_column(undo_stack_pos->get().from_column); } update(); @@ -5854,7 +5522,7 @@ void TextEdit::redo() { } _update_scrollbars(); - cursor_set_line(undo_stack_pos->get().to_line); + cursor_set_line(undo_stack_pos->get().to_line, false); cursor_set_column(undo_stack_pos->get().to_column); undo_stack_pos = undo_stack_pos->next(); update(); @@ -6093,313 +5761,6 @@ float TextEdit::get_v_scroll_speed() const { return v_scroll_speed; } -void TextEdit::set_completion(bool p_enabled, const Vector<String> &p_prefixes) { - completion_prefixes.clear(); - completion_enabled = p_enabled; - for (int i = 0; i < p_prefixes.size(); i++) { - completion_prefixes.insert(p_prefixes[i]); - } -} - -void TextEdit::_confirm_completion() { - begin_complex_operation(); - - _remove_text(cursor.line, cursor.column - completion_base.length(), cursor.line, cursor.column); - cursor_set_column(cursor.column - completion_base.length(), false); - insert_text_at_cursor(completion_current.insert_text); - - // When inserted into the middle of an existing string/method, don't add an unnecessary quote/bracket. - String line = text[cursor.line]; - char32_t next_char = line[cursor.column]; - char32_t last_completion_char = completion_current.insert_text[completion_current.insert_text.length() - 1]; - char32_t last_completion_char_display = completion_current.display[completion_current.display.length() - 1]; - - if ((last_completion_char == '"' || last_completion_char == '\'') && (last_completion_char == next_char || last_completion_char_display == next_char)) { - _remove_text(cursor.line, cursor.column, cursor.line, cursor.column + 1); - } - - if (last_completion_char == '(') { - if (next_char == last_completion_char) { - _base_remove_text(cursor.line, cursor.column - 1, cursor.line, cursor.column); - } else if (auto_brace_completion_enabled) { - insert_text_at_cursor(")"); - cursor.column--; - } - } else if (last_completion_char == ')' && next_char == '(') { - _base_remove_text(cursor.line, cursor.column - 2, cursor.line, cursor.column); - if (line[cursor.column + 1] != ')') { - cursor.column--; - } - } - - end_complex_operation(); - - _cancel_completion(); - - if (last_completion_char == '(') { - query_code_comple(); - } -} - -void TextEdit::_cancel_code_hint() { - completion_hint = ""; - update(); -} - -void TextEdit::_cancel_completion() { - if (!completion_active) { - return; - } - - completion_active = false; - completion_forced = false; - update(); -} - -static bool _is_completable(char32_t c) { - return !_is_symbol(c) || c == '"' || c == '\''; -} - -void TextEdit::_update_completion_candidates() { - String l = text[cursor.line]; - int cofs = CLAMP(cursor.column, 0, l.length()); - - String s; - - // Look for keywords first. - - bool inquote = false; - int first_quote = -1; - int restore_quotes = -1; - - int c = cofs - 1; - while (c >= 0) { - if (l[c] == '"' || l[c] == '\'') { - inquote = !inquote; - if (first_quote == -1) { - first_quote = c; - } - restore_quotes = 0; - } else if (restore_quotes == 0 && l[c] == '$') { - restore_quotes = 1; - } else if (restore_quotes == 0 && !_is_whitespace(l[c])) { - restore_quotes = -1; - } - c--; - } - - bool pre_keyword = false; - bool cancel = false; - - if (!inquote && first_quote == cofs - 1) { - // No completion here. - cancel = true; - } else if (inquote && first_quote != -1) { - s = l.substr(first_quote, cofs - first_quote); - } else if (cofs > 0 && l[cofs - 1] == ' ') { - int kofs = cofs - 1; - String kw; - while (kofs >= 0 && l[kofs] == ' ') { - kofs--; - } - - while (kofs >= 0 && l[kofs] > 32 && _is_completable(l[kofs])) { - kw = String::chr(l[kofs]) + kw; - kofs--; - } - - pre_keyword = keywords.has(kw); - - } else { - while (cofs > 0 && l[cofs - 1] > 32 && (l[cofs - 1] == '/' || _is_completable(l[cofs - 1]))) { - s = String::chr(l[cofs - 1]) + s; - if (l[cofs - 1] == '\'' || l[cofs - 1] == '"' || l[cofs - 1] == '$') { - break; - } - - cofs--; - } - } - - if (cursor.column > 0 && l[cursor.column - 1] == '(' && !pre_keyword && !completion_forced) { - cancel = true; - } - - update(); - - bool prev_is_prefix = false; - if (cofs > 0 && completion_prefixes.has(String::chr(l[cofs - 1]))) { - prev_is_prefix = true; - } - // Check with one space before prefix, to allow indent. - if (cofs > 1 && l[cofs - 1] == ' ' && completion_prefixes.has(String::chr(l[cofs - 2]))) { - prev_is_prefix = true; - } - - if (cancel || (!pre_keyword && s == "" && (cofs == 0 || !prev_is_prefix))) { - // None to complete, cancel. - _cancel_completion(); - return; - } - - completion_options.clear(); - completion_index = 0; - completion_base = s; - Vector<float> sim_cache; - bool single_quote = s.begins_with("'"); - Vector<ScriptCodeCompletionOption> completion_options_casei; - Vector<ScriptCodeCompletionOption> completion_options_subseq; - Vector<ScriptCodeCompletionOption> completion_options_subseq_casei; - - String s_lower = s.to_lower(); - - for (List<ScriptCodeCompletionOption>::Element *E = completion_sources.front(); E; E = E->next()) { - ScriptCodeCompletionOption &option = E->get(); - - if (single_quote && option.display.is_quoted()) { - option.display = option.display.unquote().quote("'"); - } - - if (inquote && restore_quotes == 1 && !option.display.is_quoted()) { - String quote = single_quote ? "'" : "\""; - option.display = option.display.quote(quote); - option.insert_text = option.insert_text.quote(quote); - } - - if (option.display.length() == 0) { - continue; - } else if (s.length() == 0) { - completion_options.push_back(option); - } else { - // This code works the same as: - /* - if (option.display.begins_with(s)) { - completion_options.push_back(option); - } else if (option.display.to_lower().begins_with(s.to_lower())) { - completion_options_casei.push_back(option); - } else if (s.is_subsequence_of(option.display)) { - completion_options_subseq.push_back(option); - } else if (s.is_subsequence_ofi(option.display)) { - completion_options_subseq_casei.push_back(option); - } - */ - // But is more performant due to being inlined and looping over the characters only once - - String display_lower = option.display.to_lower(); - - const char32_t *ssq = &s[0]; - const char32_t *ssq_lower = &s_lower[0]; - - const char32_t *tgt = &option.display[0]; - const char32_t *tgt_lower = &display_lower[0]; - - const char32_t *ssq_last_tgt = nullptr; - const char32_t *ssq_lower_last_tgt = nullptr; - - for (; *tgt; tgt++, tgt_lower++) { - if (*ssq == *tgt) { - ssq++; - ssq_last_tgt = tgt; - } - if (*ssq_lower == *tgt_lower) { - ssq_lower++; - ssq_lower_last_tgt = tgt; - } - } - - if (!*ssq) { // Matched the whole subsequence in s - if (ssq_last_tgt == &option.display[s.length() - 1]) { // Finished matching in the first s.length() characters - completion_options.push_back(option); - } else { - completion_options_subseq.push_back(option); - } - } else if (!*ssq_lower) { // Matched the whole subsequence in s_lower - if (ssq_lower_last_tgt == &option.display[s.length() - 1]) { // Finished matching in the first s.length() characters - completion_options_casei.push_back(option); - } else { - completion_options_subseq_casei.push_back(option); - } - } - } - } - - completion_options.append_array(completion_options_casei); - completion_options.append_array(completion_options_subseq); - completion_options.append_array(completion_options_subseq_casei); - - if (completion_options.size() == 0) { - // No options to complete, cancel. - _cancel_completion(); - return; - } - - if (completion_options.size() == 1 && s == completion_options[0].display) { - // A perfect match, stop completion. - _cancel_completion(); - return; - } - - // The top of the list is the best match. - completion_current = completion_options[0]; - completion_enabled = true; -} - -void TextEdit::query_code_comple() { - String l = text[cursor.line]; - int ofs = CLAMP(cursor.column, 0, l.length()); - - bool inquote = false; - - int c = ofs - 1; - while (c >= 0) { - if (l[c] == '"' || l[c] == '\'') { - inquote = !inquote; - } - c--; - } - - bool ignored = completion_active && !completion_options.is_empty(); - if (ignored) { - ScriptCodeCompletionOption::Kind kind = ScriptCodeCompletionOption::KIND_PLAIN_TEXT; - const ScriptCodeCompletionOption *previous_option = nullptr; - for (int i = 0; i < completion_options.size(); i++) { - const ScriptCodeCompletionOption ¤t_option = completion_options[i]; - if (!previous_option) { - previous_option = ¤t_option; - kind = current_option.kind; - } - if (previous_option->kind != current_option.kind) { - ignored = false; - break; - } - } - ignored = ignored && (kind == ScriptCodeCompletionOption::KIND_FILE_PATH || kind == ScriptCodeCompletionOption::KIND_NODE_PATH || kind == ScriptCodeCompletionOption::KIND_SIGNAL); - } - - if (!ignored) { - if (ofs > 0 && (inquote || _is_completable(l[ofs - 1]) || completion_prefixes.has(String::chr(l[ofs - 1])))) { - emit_signal("request_completion"); - } else if (ofs > 1 && l[ofs - 1] == ' ' && completion_prefixes.has(String::chr(l[ofs - 2]))) { // Make it work with a space too, it's good enough. - emit_signal("request_completion"); - } - } -} - -void TextEdit::set_code_hint(const String &p_hint) { - completion_hint = p_hint; - completion_hint_offset = -0xFFFF; - update(); -} - -void TextEdit::code_complete(const List<ScriptCodeCompletionOption> &p_strings, bool p_forced) { - completion_sources = p_strings; - completion_active = true; - completion_forced = p_forced; - completion_current = ScriptCodeCompletionOption(); - completion_index = 0; - _update_completion_candidates(); -} - String TextEdit::get_word_at_pos(const Vector2 &p_pos) const { int row, col; _get_mouse_pos(p_pos, row, col); @@ -6835,6 +6196,7 @@ void TextEdit::_bind_methods() { ClassDB::bind_method(D_METHOD("get_line_count"), &TextEdit::get_line_count); ClassDB::bind_method(D_METHOD("get_text"), &TextEdit::get_text); ClassDB::bind_method(D_METHOD("get_line", "line"), &TextEdit::get_line); + ClassDB::bind_method(D_METHOD("get_visible_line_count"), &TextEdit::get_total_visible_rows); ClassDB::bind_method(D_METHOD("set_line", "line", "new_text"), &TextEdit::set_line); ClassDB::bind_method(D_METHOD("set_structured_text_bidi_override", "parser"), &TextEdit::set_structured_text_bidi_override); @@ -6846,6 +6208,8 @@ void TextEdit::_bind_methods() { ClassDB::bind_method(D_METHOD("cursor_set_column", "column", "adjust_viewport"), &TextEdit::cursor_set_column, DEFVAL(true)); ClassDB::bind_method(D_METHOD("cursor_set_line", "line", "adjust_viewport", "can_be_hidden", "wrap_index"), &TextEdit::cursor_set_line, DEFVAL(true), DEFVAL(true), DEFVAL(0)); + ClassDB::bind_method(D_METHOD("get_caret_draw_pos"), &TextEdit::get_caret_draw_pos); + ClassDB::bind_method(D_METHOD("is_caret_visible"), &TextEdit::is_caret_visible); ClassDB::bind_method(D_METHOD("cursor_get_column"), &TextEdit::cursor_get_column); ClassDB::bind_method(D_METHOD("cursor_get_line"), &TextEdit::cursor_get_line); ClassDB::bind_method(D_METHOD("cursor_set_blink_enabled", "enable"), &TextEdit::cursor_set_blink_enabled); @@ -6961,6 +6325,10 @@ void TextEdit::_bind_methods() { ClassDB::bind_method(D_METHOD("set_line_gutter_clickable", "line", "gutter", "clickable"), &TextEdit::set_line_gutter_clickable); ClassDB::bind_method(D_METHOD("is_line_gutter_clickable", "line", "gutter"), &TextEdit::is_line_gutter_clickable); + // Line style + ClassDB::bind_method(D_METHOD("set_line_background_color", "line", "color"), &TextEdit::set_line_background_color); + ClassDB::bind_method(D_METHOD("get_line_background_color", "line"), &TextEdit::get_line_background_color); + ClassDB::bind_method(D_METHOD("set_highlight_current_line", "enabled"), &TextEdit::set_highlight_current_line); ClassDB::bind_method(D_METHOD("is_highlight_current_line_enabled"), &TextEdit::is_highlight_current_line_enabled); @@ -6982,7 +6350,7 @@ void TextEdit::_bind_methods() { ClassDB::bind_method(D_METHOD("get_minimap_width"), &TextEdit::get_minimap_width); ADD_PROPERTY(PropertyInfo(Variant::STRING, "text", PROPERTY_HINT_MULTILINE_TEXT), "set_text", "get_text"); - ADD_PROPERTY(PropertyInfo(Variant::INT, "text_direction", PROPERTY_HINT_ENUM, "Auto,LTR,RTL,Inherited"), "set_text_direction", "get_text_direction"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "text_direction", PROPERTY_HINT_ENUM, "Auto,Left-to-Right,Right-to-Left,Inherited"), "set_text_direction", "get_text_direction"); ADD_PROPERTY(PropertyInfo(Variant::STRING, "language"), "set_language", "get_language"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "draw_control_chars"), "set_draw_control_chars", "get_draw_control_chars"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "readonly"), "set_readonly", "is_readonly"); @@ -7022,7 +6390,6 @@ void TextEdit::_bind_methods() { ADD_SIGNAL(MethodInfo("cursor_changed")); ADD_SIGNAL(MethodInfo("text_changed")); ADD_SIGNAL(MethodInfo("lines_edited_from", PropertyInfo(Variant::INT, "from_line"), PropertyInfo(Variant::INT, "to_line"))); - ADD_SIGNAL(MethodInfo("request_completion")); ADD_SIGNAL(MethodInfo("gutter_clicked", PropertyInfo(Variant::INT, "line"), PropertyInfo(Variant::INT, "gutter"))); ADD_SIGNAL(MethodInfo("gutter_added")); ADD_SIGNAL(MethodInfo("gutter_removed")); diff --git a/scene/gui/text_edit.h b/scene/gui/text_edit.h index b0c7314c65..f963e664d1 100644 --- a/scene/gui/text_edit.h +++ b/scene/gui/text_edit.h @@ -92,7 +92,7 @@ private: Vector<Vector2i> bidi_override; Ref<TextParagraph> data_buf; - bool marked = false; + Color background_color = Color(0, 0, 0, 0); bool hidden = false; Line() { @@ -129,12 +129,11 @@ private: void set_width(float p_width); int get_line_wrap_amount(int p_line) const; + Vector<Vector2i> get_line_wrap_ranges(int p_line) const; const Ref<TextParagraph> get_line_data(int p_line) const; void set(int p_line, const String &p_text, const Vector<Vector2i> &p_bidi_override); - void set_marked(int p_line, bool p_marked) { text.write[p_line].marked = p_marked; } - bool is_marked(int p_line) const { return text[p_line].marked; } void set_hidden(int p_line, bool p_hidden) { text.write[p_line].hidden = p_hidden; } bool is_hidden(int p_line) const { return text[p_line].hidden; } void insert(int p_at, const String &p_text, const Vector<Vector2i> &p_bidi_override); @@ -167,9 +166,15 @@ private: void set_line_gutter_clickable(int p_line, int p_gutter, bool p_clickable) { text.write[p_line].gutters.write[p_gutter].clickable = p_clickable; } bool is_line_gutter_clickable(int p_line, int p_gutter) const { return text[p_line].gutters[p_gutter].clickable; } + + /* Line style. */ + void set_line_background_color(int p_line, const Color &p_color) { text.write[p_line].background_color = p_color; } + const Color get_line_background_color(int p_line) const { return text[p_line].background_color; } }; struct Cursor { + Point2 draw_pos; + bool visible = false; int last_fit_x = 0; int line = 0; int column = 0; ///< cursor @@ -236,20 +241,6 @@ private: Dictionary _get_line_syntax_highlighting(int p_line); - Set<String> completion_prefixes; - bool completion_enabled = false; - List<ScriptCodeCompletionOption> completion_sources; - Vector<ScriptCodeCompletionOption> completion_options; - bool completion_active = false; - bool completion_forced = false; - ScriptCodeCompletionOption completion_current; - String completion_base; - int completion_index = 0; - Rect2i completion_rect; - int completion_line_ofs = 0; - String completion_hint; - int completion_hint_offset = 0; - bool setting_text = false; // data @@ -303,10 +294,10 @@ private: bool highlight_all_occurrences = false; bool scroll_past_end_of_file_enabled = false; - bool auto_brace_completion_enabled = false; bool brace_matching_enabled = false; bool highlight_current_line = false; bool auto_indent = false; + String cut_copy_line; bool insert_mode = false; bool select_identifiers_enabled = false; @@ -338,9 +329,6 @@ private: bool next_operation_is_complex = false; - bool callhint_below = false; - Vector2 callhint_offset; - String search_text; uint32_t search_flags = 0; int search_result_line = 0; @@ -434,10 +422,6 @@ private: PopupMenu *menu_ctl; void _clear(); - void _cancel_completion(); - void _cancel_code_hint(); - void _confirm_completion(); - void _update_completion_candidates(); int _calculate_spaces_till_next_left_indent(int column); int _calculate_spaces_till_next_right_indent(int column); @@ -460,9 +444,11 @@ private: void _delete_selection(); void _move_cursor_document_start(bool p_select); void _move_cursor_document_end(bool p_select); - void _handle_unicode_character(uint32_t unicode, bool p_had_selection, bool p_update_auto_complete); + void _handle_unicode_character(uint32_t unicode, bool p_had_selection); protected: + bool auto_brace_completion_enabled = false; + struct Cache { Ref<Texture2D> tab_icon; Ref<Texture2D> space_icon; @@ -474,17 +460,12 @@ protected: int font_size = 16; int outline_size = 0; Color outline_color; - Color completion_background_color; - Color completion_selected_color; - Color completion_existing_color; - Color completion_font_color; Color caret_color; Color caret_background_color; Color font_color; Color font_selected_color; Color font_readonly_color; Color selection_color; - Color mark_color; Color code_folding_color; Color current_line_color; Color line_length_guideline_color; @@ -503,7 +484,7 @@ protected: void _insert_text(int p_line, int p_char, const String &p_text, int *r_end_line = nullptr, int *r_end_char = nullptr); void _remove_text(int p_from_line, int p_from_column, int p_to_line, int p_to_column); void _insert_text_at_cursor(const String &p_text); - void _gui_input(const Ref<InputEvent> &p_gui_input); + virtual void _gui_input(const Ref<InputEvent> &p_gui_input); void _notification(int p_what); void _consume_pair_symbol(char32_t ch); @@ -561,6 +542,10 @@ public: void set_line_gutter_clickable(int p_line, int p_gutter, bool p_clickable); bool is_line_gutter_clickable(int p_line, int p_gutter) const; + // Line style + void set_line_background_color(int p_line, const Color &p_color); + Color get_line_background_color(int p_line); + enum MenuItems { MENU_CUT, MENU_COPY, @@ -637,7 +622,6 @@ public: void insert_text_at_cursor(const String &p_text); void insert_at(const String &p_text, int at); int get_line_count() const; - void set_line_as_marked(int p_line, bool p_marked); void set_line_as_hidden(int p_line, bool p_hidden); bool is_line_hidden(int p_line) const; @@ -676,10 +660,6 @@ public: brace_matching_enabled = p_enabled; update(); } - inline void set_callhint_settings(bool below, Vector2 offset) { - callhint_below = below; - callhint_offset = offset; - } void set_auto_indent(bool p_auto_indent); void center_viewport_to_cursor(); @@ -690,6 +670,8 @@ public: void cursor_set_column(int p_col, bool p_adjust_viewport = true); void cursor_set_line(int p_row, bool p_adjust_viewport = true, bool p_can_be_hidden = true, int p_wrap_index = 0); + Point2 get_caret_draw_pos() const; + bool is_caret_visible() const; int cursor_get_column() const; int cursor_get_line() const; Vector2i _get_cursor_pixel_pos(bool p_adjust_viewport = true); @@ -726,6 +708,7 @@ public: void copy(); void paste(); void select_all(); + void select_word_under_caret(); void select(int p_from_line, int p_from_column, int p_to_line, int p_to_column); void deselect(); void swap_lines(int line1, int line2); @@ -805,11 +788,6 @@ public: void set_tooltip_request_func(Object *p_obj, const StringName &p_function, const Variant &p_udata); - void set_completion(bool p_enabled, const Vector<String> &p_prefixes); - void code_complete(const List<ScriptCodeCompletionOption> &p_strings, bool p_forced = false); - void set_code_hint(const String &p_hint); - void query_code_comple(); - void set_select_identifiers_on_hover(bool p_enable); bool is_selecting_identifiers_on_hover_enabled() const; @@ -827,7 +805,6 @@ public: PopupMenu *get_menu() const; - String get_text_for_completion(); String get_text_for_lookup_completion(); virtual bool is_text_field() const override; diff --git a/scene/gui/tree.cpp b/scene/gui/tree.cpp index 6f51a61329..f66cc13af5 100644 --- a/scene/gui/tree.cpp +++ b/scene/gui/tree.cpp @@ -47,36 +47,6 @@ #include <limits.h> -void TreeItem::move_to_top() { - if (!parent || parent->children == this) { - return; //already on top - } - TreeItem *prev = get_prev(); - prev->next = next; - next = parent->children; - parent->children = this; -} - -void TreeItem::move_to_bottom() { - if (!parent || !next) { - return; - } - - TreeItem *prev = get_prev(); - TreeItem *last = next; - while (last->next) { - last = last->next; - } - - if (prev) { - prev->next = next; - } else { - parent->children = next; - } - last->next = this; - next = nullptr; -} - Size2 TreeItem::Cell::get_icon_size() const { if (icon.is_null()) { return Size2(); @@ -118,6 +88,54 @@ void TreeItem::_cell_deselected(int p_cell) { tree->item_deselected(p_cell, this); } +void TreeItem::_change_tree(Tree *p_tree) { + if (p_tree == tree) { + return; + } + + TreeItem *c = first_child; + while (c) { + c->_change_tree(p_tree); + c = c->next; + } + + if (tree && tree->root == this) { + tree->root = nullptr; + } + + if (tree && tree->popup_edited_item == this) { + tree->popup_edited_item = nullptr; + tree->pressing_for_editor = false; + } + + if (tree && tree->cache.hover_item == this) { + tree->cache.hover_item = nullptr; + } + + if (tree && tree->selected_item == this) { + tree->selected_item = nullptr; + } + + if (tree && tree->drop_mode_over == this) { + tree->drop_mode_over = nullptr; + } + + if (tree && tree->single_select_defer == this) { + tree->single_select_defer = nullptr; + } + + if (tree && tree->edited_item == this) { + tree->edited_item = nullptr; + tree->pressing_for_editor = false; + } + + tree = p_tree; + + if (tree) { + cells.resize(tree->columns.size()); + } +} + /* cell mode */ void TreeItem::set_cell_mode(int p_column, TreeCellMode p_mode) { ERR_FAIL_INDEX(p_column, cells.size()); @@ -410,6 +428,14 @@ bool TreeItem::is_collapsed() { return collapsed; } +void TreeItem::uncollapse_tree() { + TreeItem *t = this; + while (t) { + t->set_collapsed(false); + t = t->parent; + } +} + void TreeItem::set_custom_minimum_height(int p_height) { custom_min_height = p_height; _changed_notify(); @@ -419,20 +445,74 @@ int TreeItem::get_custom_minimum_height() const { return custom_min_height; } +/* Item manipulation */ + +TreeItem *TreeItem::create_child(int p_idx) { + TreeItem *ti = memnew(TreeItem(tree)); + if (tree) { + ti->cells.resize(tree->columns.size()); + } + + TreeItem *l_prev = nullptr; + TreeItem *c = first_child; + int idx = 0; + + while (c) { + if (idx++ == p_idx) { + c->prev = ti; + ti->next = c; + break; + } + l_prev = c; + c = c->next; + } + + if (l_prev) { + l_prev->next = ti; + ti->prev = l_prev; + if (!children_cache.is_empty()) { + if (ti->next) { + children_cache.insert(p_idx, ti); + } else { + children_cache.append(ti); + } + } + } else { + first_child = ti; + if (!children_cache.is_empty()) { + children_cache.insert(0, ti); + } + } + + ti->parent = this; + + return ti; +} + +Tree *TreeItem::get_tree() { + return tree; +} + TreeItem *TreeItem::get_next() { return next; } TreeItem *TreeItem::get_prev() { - if (!parent || parent->children == this) { - return nullptr; + if (prev) { + return prev; } - TreeItem *prev = parent->children; - while (prev && prev->next != this) { - prev = prev->next; + if (!parent || parent->first_child == this) { + return nullptr; + } + // This is an edge case + TreeItem *l_prev = parent->first_child; + while (l_prev && l_prev->next != this) { + l_prev = l_prev->next; } + prev = l_prev; + return prev; } @@ -440,8 +520,8 @@ TreeItem *TreeItem::get_parent() { return parent; } -TreeItem *TreeItem::get_children() { - return children; +TreeItem *TreeItem::get_first_child() { + return first_child; } TreeItem *TreeItem::get_prev_visible(bool p_wrap) { @@ -467,10 +547,10 @@ TreeItem *TreeItem::get_prev_visible(bool p_wrap) { } } else { current = prev; - while (!current->collapsed && current->children) { + while (!current->collapsed && current->first_child) { //go to the very end - current = current->children; + current = current->first_child; while (current->next) { current = current->next; } @@ -483,8 +563,8 @@ TreeItem *TreeItem::get_prev_visible(bool p_wrap) { TreeItem *TreeItem::get_next_visible(bool p_wrap) { TreeItem *current = this; - if (!current->collapsed && current->children) { - current = current->children; + if (!current->collapsed && current->first_child) { + current = current->first_child; } else if (current->next) { current = current->next; @@ -507,24 +587,136 @@ TreeItem *TreeItem::get_next_visible(bool p_wrap) { return current; } -void TreeItem::remove_child(TreeItem *p_item) { +TreeItem *TreeItem::get_child(int p_idx) { + _create_children_cache(); + ERR_FAIL_INDEX_V(p_idx, children_cache.size(), nullptr); + return children_cache.get(p_idx); +} + +int TreeItem::get_child_count() { + _create_children_cache(); + return children_cache.size(); +} + +Array TreeItem::get_children() { + int size = get_child_count(); + Array arr; + arr.resize(size); + for (int i = 0; i < size; i++) { + arr[i] = children_cache[i]; + } + + return arr; +} + +int TreeItem::get_index() { + int idx = 0; + TreeItem *c = this; + + while (c) { + c = c->get_prev(); + idx++; + } + return idx - 1; +} + +void TreeItem::move_before(TreeItem *p_item) { + ERR_FAIL_NULL(p_item); + ERR_FAIL_COND(is_root); + ERR_FAIL_COND(!p_item->parent); + + if (p_item == this) { + return; + } + + TreeItem *p = p_item->parent; + while (p) { + ERR_FAIL_COND_MSG(p == this, "Can't move to a descendant"); + p = p->parent; + } + + Tree *old_tree = tree; + _unlink_from_tree(); + _change_tree(p_item->tree); + + parent = p_item->parent; + + TreeItem *item_prev = p_item->get_prev(); + if (item_prev) { + item_prev->next = this; + parent->children_cache.clear(); + } else { + parent->first_child = this; + parent->children_cache.insert(0, this); + } + + prev = item_prev; + next = p_item; + p_item->prev = this; + + if (old_tree && old_tree != tree) { + old_tree->update(); + } + + if (tree) { + tree->update(); + } +} + +void TreeItem::move_after(TreeItem *p_item) { ERR_FAIL_NULL(p_item); - TreeItem **c = &children; + ERR_FAIL_COND(is_root); + ERR_FAIL_COND(!p_item->parent); + + if (p_item == this) { + return; + } - while (*c) { - if ((*c) == p_item) { - TreeItem *aux = *c; + TreeItem *p = p_item->parent; + while (p) { + ERR_FAIL_COND_MSG(p == this, "Can't move to a descendant"); + p = p->parent; + } - *c = (*c)->next; + Tree *old_tree = tree; + _unlink_from_tree(); + _change_tree(p_item->tree); - aux->parent = nullptr; - return; - } + if (p_item->next) { + p_item->next->prev = this; + } + parent = p_item->parent; + prev = p_item; + next = p_item->next; + p_item->next = this; + + if (next) { + parent->children_cache.clear(); + } else { + parent->children_cache.append(this); + } - c = &(*c)->next; + if (old_tree && old_tree != tree) { + old_tree->update(); } - ERR_FAIL(); + if (tree) { + tree->update(); + } +} + +void TreeItem::remove_child(TreeItem *p_item) { + ERR_FAIL_NULL(p_item); + ERR_FAIL_COND(p_item->parent != this); + + p_item->_unlink_from_tree(); + p_item->prev = nullptr; + p_item->next = nullptr; + p_item->parent = nullptr; + + if (tree) { + tree->update(); + } } void TreeItem::set_selectable(int p_column, bool p_selectable) { @@ -678,6 +870,15 @@ void TreeItem::clear_custom_color(int p_column) { _changed_notify(p_column); } +void TreeItem::set_custom_font(int p_column, const Ref<Font> &p_font) { + ERR_FAIL_INDEX(p_column, cells.size()); + cells.write[p_column].custom_font = p_font; +} +Ref<Font> TreeItem::get_custom_font(int p_column) const { + ERR_FAIL_INDEX_V(p_column, cells.size(), Ref<Font>()); + return cells[p_column].custom_font; +} + void TreeItem::set_tooltip(int p_column, const String &p_tooltip) { ERR_FAIL_INDEX(p_column, cells.size()); cells.write[p_column].tooltip = p_tooltip; @@ -777,7 +978,7 @@ void recursive_call_aux(TreeItem *p_item, const StringName &p_method, const Vari return; } p_item->call(p_method, p_args, p_argcount, r_error); - TreeItem *c = p_item->get_children(); + TreeItem *c = p_item->get_first_child(); while (c) { recursive_call_aux(c, p_method, p_args, p_argcount, r_error); c = c->get_next(); @@ -842,19 +1043,11 @@ void TreeItem::_bind_methods() { ClassDB::bind_method(D_METHOD("set_collapsed", "enable"), &TreeItem::set_collapsed); ClassDB::bind_method(D_METHOD("is_collapsed"), &TreeItem::is_collapsed); + ClassDB::bind_method(D_METHOD("uncollapse_tree"), &TreeItem::uncollapse_tree); + ClassDB::bind_method(D_METHOD("set_custom_minimum_height", "height"), &TreeItem::set_custom_minimum_height); ClassDB::bind_method(D_METHOD("get_custom_minimum_height"), &TreeItem::get_custom_minimum_height); - ClassDB::bind_method(D_METHOD("get_next"), &TreeItem::get_next); - ClassDB::bind_method(D_METHOD("get_prev"), &TreeItem::get_prev); - ClassDB::bind_method(D_METHOD("get_parent"), &TreeItem::get_parent); - ClassDB::bind_method(D_METHOD("get_children"), &TreeItem::get_children); - - ClassDB::bind_method(D_METHOD("get_next_visible", "wrap"), &TreeItem::get_next_visible, DEFVAL(false)); - ClassDB::bind_method(D_METHOD("get_prev_visible", "wrap"), &TreeItem::get_prev_visible, DEFVAL(false)); - - ClassDB::bind_method(D_METHOD("remove_child", "child"), &TreeItem::_remove_child); - ClassDB::bind_method(D_METHOD("set_selectable", "column", "selectable"), &TreeItem::set_selectable); ClassDB::bind_method(D_METHOD("is_selectable", "column"), &TreeItem::is_selectable); @@ -866,8 +1059,11 @@ void TreeItem::_bind_methods() { ClassDB::bind_method(D_METHOD("is_editable", "column"), &TreeItem::is_editable); ClassDB::bind_method(D_METHOD("set_custom_color", "column", "color"), &TreeItem::set_custom_color); - ClassDB::bind_method(D_METHOD("clear_custom_color", "column"), &TreeItem::clear_custom_color); ClassDB::bind_method(D_METHOD("get_custom_color", "column"), &TreeItem::get_custom_color); + ClassDB::bind_method(D_METHOD("clear_custom_color", "column"), &TreeItem::clear_custom_color); + + ClassDB::bind_method(D_METHOD("set_custom_font", "column", "font"), &TreeItem::set_custom_font); + ClassDB::bind_method(D_METHOD("get_custom_font", "column"), &TreeItem::get_custom_font); ClassDB::bind_method(D_METHOD("set_custom_bg_color", "column", "color", "just_outline"), &TreeItem::set_custom_bg_color, DEFVAL(false)); ClassDB::bind_method(D_METHOD("clear_custom_bg_color", "column"), &TreeItem::clear_custom_bg_color); @@ -885,19 +1081,38 @@ void TreeItem::_bind_methods() { ClassDB::bind_method(D_METHOD("set_button_disabled", "column", "button_idx", "disabled"), &TreeItem::set_button_disabled); ClassDB::bind_method(D_METHOD("is_button_disabled", "column", "button_idx"), &TreeItem::is_button_disabled); - ClassDB::bind_method(D_METHOD("set_expand_right", "column", "enable"), &TreeItem::set_expand_right); - ClassDB::bind_method(D_METHOD("get_expand_right", "column"), &TreeItem::get_expand_right); - ClassDB::bind_method(D_METHOD("set_tooltip", "column", "tooltip"), &TreeItem::set_tooltip); ClassDB::bind_method(D_METHOD("get_tooltip", "column"), &TreeItem::get_tooltip); ClassDB::bind_method(D_METHOD("set_text_align", "column", "text_align"), &TreeItem::set_text_align); ClassDB::bind_method(D_METHOD("get_text_align", "column"), &TreeItem::get_text_align); - ClassDB::bind_method(D_METHOD("move_to_top"), &TreeItem::move_to_top); - ClassDB::bind_method(D_METHOD("move_to_bottom"), &TreeItem::move_to_bottom); + + ClassDB::bind_method(D_METHOD("set_expand_right", "column", "enable"), &TreeItem::set_expand_right); + ClassDB::bind_method(D_METHOD("get_expand_right", "column"), &TreeItem::get_expand_right); ClassDB::bind_method(D_METHOD("set_disable_folding", "disable"), &TreeItem::set_disable_folding); ClassDB::bind_method(D_METHOD("is_folding_disabled"), &TreeItem::is_folding_disabled); + ClassDB::bind_method(D_METHOD("create_child", "idx"), &TreeItem::create_child, DEFVAL(-1)); + ClassDB::bind_method(D_METHOD("get_tree"), &TreeItem::get_tree); + + ClassDB::bind_method(D_METHOD("get_next"), &TreeItem::get_next); + ClassDB::bind_method(D_METHOD("get_prev"), &TreeItem::get_prev); + ClassDB::bind_method(D_METHOD("get_parent"), &TreeItem::get_parent); + ClassDB::bind_method(D_METHOD("get_first_child"), &TreeItem::get_first_child); + + ClassDB::bind_method(D_METHOD("get_next_visible", "wrap"), &TreeItem::get_next_visible, DEFVAL(false)); + ClassDB::bind_method(D_METHOD("get_prev_visible", "wrap"), &TreeItem::get_prev_visible, DEFVAL(false)); + + ClassDB::bind_method(D_METHOD("get_child", "idx"), &TreeItem::get_child); + ClassDB::bind_method(D_METHOD("get_child_count"), &TreeItem::get_child_count); + ClassDB::bind_method(D_METHOD("get_children"), &TreeItem::get_children); + ClassDB::bind_method(D_METHOD("get_index"), &TreeItem::get_index); + + ClassDB::bind_method(D_METHOD("move_before", "item"), &TreeItem::_move_before); + ClassDB::bind_method(D_METHOD("move_after", "item"), &TreeItem::_move_after); + + ClassDB::bind_method(D_METHOD("remove_child", "child"), &TreeItem::_remove_child); + { MethodInfo mi; mi.name = "call_recursive"; @@ -922,7 +1137,7 @@ void TreeItem::_bind_methods() { } void TreeItem::clear_children() { - TreeItem *c = children; + TreeItem *c = first_child; while (c) { TreeItem *aux = c; c = c->get_next(); @@ -930,56 +1145,18 @@ void TreeItem::clear_children() { memdelete(aux); } - children = nullptr; + first_child = nullptr; }; TreeItem::TreeItem(Tree *p_tree) { tree = p_tree; - collapsed = false; - disable_folding = false; - custom_min_height = 0; - - parent = nullptr; // parent item - next = nullptr; // next in list - children = nullptr; //child items } TreeItem::~TreeItem() { + _unlink_from_tree(); + prev = nullptr; clear_children(); - - if (parent) { - parent->remove_child(this); - } - - if (tree && tree->root == this) { - tree->root = nullptr; - } - - if (tree && tree->popup_edited_item == this) { - tree->popup_edited_item = nullptr; - tree->pressing_for_editor = false; - } - - if (tree && tree->cache.hover_item == this) { - tree->cache.hover_item = nullptr; - } - - if (tree && tree->selected_item == this) { - tree->selected_item = nullptr; - } - - if (tree && tree->drop_mode_over == this) { - tree->drop_mode_over = nullptr; - } - - if (tree && tree->single_select_defer == this) { - tree->single_select_defer = nullptr; - } - - if (tree && tree->edited_item == this) { - tree->edited_item = nullptr; - tree->pressing_for_editor = false; - } + _change_tree(nullptr); } /**********************************************/ @@ -1019,15 +1196,23 @@ void Tree::update_cache() { cache.font_color = get_theme_color("font_color"); cache.font_selected_color = get_theme_color("font_selected_color"); - cache.guide_color = get_theme_color("guide_color"); cache.drop_position_color = get_theme_color("drop_position_color"); cache.hseparation = get_theme_constant("hseparation"); cache.vseparation = get_theme_constant("vseparation"); cache.item_margin = get_theme_constant("item_margin"); cache.button_margin = get_theme_constant("button_margin"); + cache.draw_guides = get_theme_constant("draw_guides"); + cache.guide_color = get_theme_color("guide_color"); cache.draw_relationship_lines = get_theme_constant("draw_relationship_lines"); + cache.relationship_line_width = get_theme_constant("relationship_line_width"); + cache.parent_hl_line_width = get_theme_constant("parent_hl_line_width"); + cache.children_hl_line_width = get_theme_constant("children_hl_line_width"); + cache.parent_hl_line_margin = get_theme_constant("parent_hl_line_margin"); cache.relationship_line_color = get_theme_color("relationship_line_color"); + cache.parent_hl_line_color = get_theme_color("parent_hl_line_color"); + cache.children_hl_line_color = get_theme_color("children_hl_line_color"); + cache.scroll_border = get_theme_constant("scroll_border"); cache.scroll_speed = get_theme_constant("scroll_speed"); @@ -1106,7 +1291,7 @@ int Tree::get_item_height(TreeItem *p_item) const { if (!p_item->collapsed) { /* if not collapsed, check the children */ - TreeItem *c = p_item->children; + TreeItem *c = p_item->first_child; while (c) { height += get_item_height(c); @@ -1199,6 +1384,7 @@ void Tree::update_column(int p_col) { } else { columns.write[p_col].text_buf->set_direction((TextServer::Direction)columns[p_col].text_direction); } + columns.write[p_col].text_buf->add_string(columns[p_col].title, cache.font, cache.font_size, columns[p_col].opentype_features, (columns[p_col].language != "") ? columns[p_col].language : TranslationServer::get_singleton()->get_tool_locale()); } @@ -1243,7 +1429,14 @@ void Tree::update_item_cell(TreeItem *p_item, int p_col) { } else { p_item->cells.write[p_col].text_buf->set_direction((TextServer::Direction)p_item->cells[p_col].text_direction); } - p_item->cells.write[p_col].text_buf->add_string(valtext, cache.font, cache.font_size, p_item->cells[p_col].opentype_features, (p_item->cells[p_col].language != "") ? p_item->cells[p_col].language : TranslationServer::get_singleton()->get_tool_locale()); + + Ref<Font> font; + if (p_item->cells[p_col].custom_font.is_valid()) { + font = p_item->cells[p_col].custom_font; + } else { + font = cache.font; + } + p_item->cells.write[p_col].text_buf->add_string(valtext, font, cache.font_size, p_item->cells[p_col].opentype_features, (p_item->cells[p_col].language != "") ? p_item->cells[p_col].language : TranslationServer::get_singleton()->get_tool_locale()); TS->shaped_text_set_bidi_override(p_item->cells[p_col].text_buf->get_rid(), structured_text_parser(p_item->cells[p_col].st_parser, p_item->cells[p_col].st_args, valtext)); p_item->cells.write[p_col].dirty = false; } @@ -1253,7 +1446,7 @@ void Tree::update_item_cache(TreeItem *p_item) { update_item_cell(p_item, i); } - TreeItem *c = p_item->children; + TreeItem *c = p_item->first_child; while (c) { update_item_cache(c); c = c->next; @@ -1378,7 +1571,7 @@ int Tree::draw_item(const Point2i &p_pos, const Point2 &p_draw_ofs, const Size2 } } - if ((select_mode == SELECT_ROW && selected_item == p_item) || p_item->cells[i].selected) { + if ((select_mode == SELECT_ROW && selected_item == p_item) || p_item->cells[i].selected || !p_item->has_meta("__focus_rect")) { Rect2i r(cell_rect.position, cell_rect.size); p_item->set_meta("__focus_rect", Rect2(r.position, r.size)); @@ -1420,7 +1613,7 @@ int Tree::draw_item(const Point2i &p_pos, const Point2 &p_draw_ofs, const Size2 if (drop_mode_flags && drop_mode_over == p_item) { Rect2 r = cell_rect; - bool has_parent = p_item->get_children() != nullptr; + bool has_parent = p_item->get_first_child() != nullptr; if (rtl) { r.position.x = get_size().width - r.position.x - r.size.x; } @@ -1578,7 +1771,7 @@ int Tree::draw_item(const Point2i &p_pos, const Point2 &p_draw_ofs, const Size2 if (p_item->cells[i].custom_button) { if (cache.hover_item == p_item && cache.hover_cell == i) { - if (Input::get_singleton()->is_mouse_button_pressed(BUTTON_LEFT)) { + if (Input::get_singleton()->is_mouse_button_pressed(MOUSE_BUTTON_LEFT)) { draw_style_box(cache.custom_button_pressed, ir); } else { draw_style_box(cache.custom_button_hover, ir); @@ -1616,7 +1809,7 @@ int Tree::draw_item(const Point2i &p_pos, const Point2 &p_draw_ofs, const Size2 } } - if (!p_item->disable_folding && !hide_folding && p_item->children) { //has children, draw the guide box + if (!p_item->disable_folding && !hide_folding && p_item->first_child) { //has children, draw the guide box Ref<Texture2D> arrow; @@ -1646,40 +1839,81 @@ int Tree::draw_item(const Point2i &p_pos, const Point2 &p_draw_ofs, const Size2 if (!p_item->collapsed) { /* if not collapsed, check the children */ - TreeItem *c = p_item->children; + TreeItem *c = p_item->first_child; - int prev_ofs = children_pos.y - cache.offset.y + p_draw_ofs.y; + int base_ofs = children_pos.y - cache.offset.y + p_draw_ofs.y; + int prev_ofs = base_ofs; + int prev_hl_ofs = base_ofs; while (c) { if (cache.draw_relationship_lines > 0 && (!hide_root || c->parent != root)) { int root_ofs = children_pos.x + ((p_item->disable_folding || hide_folding) ? cache.hseparation : cache.item_margin); - int parent_ofs = p_pos.x + ((p_item->disable_folding || hide_folding) ? cache.hseparation : cache.item_margin); + int parent_ofs = p_pos.x + cache.item_margin; Point2i root_pos = Point2i(root_ofs, children_pos.y + label_h / 2) - cache.offset + p_draw_ofs; - if (c->get_children() != nullptr) { + if (c->get_first_child() != nullptr) { root_pos -= Point2i(cache.arrow->get_width(), 0); } - float line_width = 1.0; + float line_width = cache.relationship_line_width; + float parent_line_width = cache.parent_hl_line_width; + float children_line_width = cache.children_hl_line_width; + #ifdef TOOLS_ENABLED - line_width *= EDSCALE; + line_width *= Math::round(EDSCALE); + parent_line_width *= Math::round(EDSCALE); + children_line_width *= Math::round(EDSCALE); #endif Point2i parent_pos = Point2i(parent_ofs - cache.arrow->get_width() / 2, p_pos.y + label_h / 2 + cache.arrow->get_height() / 2) - cache.offset + p_draw_ofs; + int more_prev_ofs = 0; + if (root_pos.y + line_width >= 0) { if (rtl) { root_pos.x = get_size().width - root_pos.x; parent_pos.x = get_size().width - parent_pos.x; } - RenderingServer::get_singleton()->canvas_item_add_line(ci, root_pos, Point2i(parent_pos.x - Math::floor(line_width / 2), root_pos.y), cache.relationship_line_color, line_width); - RenderingServer::get_singleton()->canvas_item_add_line(ci, Point2i(parent_pos.x, root_pos.y), Point2i(parent_pos.x, prev_ofs), cache.relationship_line_color, line_width); + + // Order of parts on this bend: the horizontal line first, then the vertical line. + if (_is_branch_selected(c)) { + // If this item or one of its children is selected, we draw the line using parent highlight style. + RenderingServer::get_singleton()->canvas_item_add_line(ci, root_pos, Point2i(parent_pos.x + Math::floor(parent_line_width / 2), root_pos.y), cache.parent_hl_line_color, parent_line_width); + RenderingServer::get_singleton()->canvas_item_add_line(ci, Point2i(parent_pos.x, root_pos.y + Math::floor(parent_line_width / 2)), Point2i(parent_pos.x, prev_hl_ofs), cache.parent_hl_line_color, parent_line_width); + + more_prev_ofs = cache.parent_hl_line_margin; + prev_hl_ofs = root_pos.y + Math::floor(parent_line_width / 2); + } else if (p_item->is_selected(0)) { + // If parent item is selected (but this item is not), we draw the line using children highlight style. + // Siblings of the selected branch can be drawn with a slight offset and their vertical line must appear as highlighted. + if (_is_sibling_branch_selected(c)) { + RenderingServer::get_singleton()->canvas_item_add_line(ci, root_pos, Point2i(parent_pos.x + Math::floor(parent_line_width / 2), root_pos.y), cache.children_hl_line_color, children_line_width); + RenderingServer::get_singleton()->canvas_item_add_line(ci, Point2i(parent_pos.x, root_pos.y + Math::floor(parent_line_width / 2)), Point2i(parent_pos.x, prev_hl_ofs), cache.parent_hl_line_color, parent_line_width); + + prev_hl_ofs = root_pos.y + Math::floor(parent_line_width / 2); + } else { + RenderingServer::get_singleton()->canvas_item_add_line(ci, root_pos, Point2i(parent_pos.x + Math::floor(children_line_width / 2), root_pos.y), cache.children_hl_line_color, children_line_width); + RenderingServer::get_singleton()->canvas_item_add_line(ci, Point2i(parent_pos.x, root_pos.y + Math::floor(children_line_width / 2)), Point2i(parent_pos.x, prev_ofs + Math::floor(children_line_width / 2)), cache.children_hl_line_color, children_line_width); + } + } else { + // If nothing of the above is true, we draw the line using normal style. + // Siblings of the selected branch can be drawn with a slight offset and their vertical line must appear as highlighted. + if (_is_sibling_branch_selected(c)) { + RenderingServer::get_singleton()->canvas_item_add_line(ci, root_pos, Point2i(parent_pos.x + cache.parent_hl_line_margin, root_pos.y), cache.relationship_line_color, line_width); + RenderingServer::get_singleton()->canvas_item_add_line(ci, Point2i(parent_pos.x, root_pos.y + Math::floor(parent_line_width / 2)), Point2i(parent_pos.x, prev_hl_ofs), cache.parent_hl_line_color, parent_line_width); + + prev_hl_ofs = root_pos.y + Math::floor(parent_line_width / 2); + } else { + RenderingServer::get_singleton()->canvas_item_add_line(ci, root_pos, Point2i(parent_pos.x + Math::floor(line_width / 2), root_pos.y), cache.relationship_line_color, line_width); + RenderingServer::get_singleton()->canvas_item_add_line(ci, Point2i(parent_pos.x, root_pos.y + Math::floor(line_width / 2)), Point2i(parent_pos.x, prev_ofs + Math::floor(line_width / 2)), cache.relationship_line_color, line_width); + } + } } if (htotal < 0) { return -1; } - prev_ofs = root_pos.y; + prev_ofs = root_pos.y + more_prev_ofs; } if (htotal >= 0) { @@ -1688,10 +1922,10 @@ int Tree::draw_item(const Point2i &p_pos, const Point2 &p_draw_ofs, const Size2 if (child_h < 0) { if (cache.draw_relationship_lines == 0) { return -1; // break, stop drawing, no need to anymore - } else { - htotal = -1; - children_pos.y = cache.offset.y + p_draw_size.height; } + + htotal = -1; + children_pos.y = cache.offset.y + p_draw_size.height; } else { htotal += child_h; children_pos.y += child_h; @@ -1713,8 +1947,8 @@ int Tree::_count_selected_items(TreeItem *p_from) const { } } - if (p_from->get_children()) { - count += _count_selected_items(p_from->get_children()); + if (p_from->get_first_child()) { + count += _count_selected_items(p_from->get_first_child()); } if (p_from->get_next()) { @@ -1724,6 +1958,36 @@ int Tree::_count_selected_items(TreeItem *p_from) const { return count; } +bool Tree::_is_branch_selected(TreeItem *p_from) const { + for (int i = 0; i < columns.size(); i++) { + if (p_from->is_selected(i)) { + return true; + } + } + + TreeItem *child_item = p_from->get_first_child(); + while (child_item) { + if (_is_branch_selected(child_item)) { + return true; + } + child_item = child_item->get_next(); + } + + return false; +} + +bool Tree::_is_sibling_branch_selected(TreeItem *p_from) const { + TreeItem *sibling_item = p_from->get_next(); + while (sibling_item) { + if (_is_branch_selected(sibling_item)) { + return true; + } + sibling_item = sibling_item->get_next(); + } + + return false; +} + void Tree::select_single_item(TreeItem *p_selected, TreeItem *p_current, int p_col, TreeItem *p_prev, bool *r_in_range, bool p_force_deselect) { TreeItem::Cell &selected_cell = p_selected->cells.write[p_col]; @@ -1802,7 +2066,7 @@ void Tree::select_single_item(TreeItem *p_selected, TreeItem *p_current, int p_c *r_in_range = false; } - TreeItem *c = p_current->children; + TreeItem *c = p_current->first_child; while (c) { select_single_item(p_selected, c, p_col, p_prev, r_in_range, p_current->is_collapsed() || p_force_deselect); @@ -1815,7 +2079,7 @@ Rect2 Tree::search_item_rect(TreeItem *p_from, TreeItem *p_item) { } void Tree::_range_click_timeout() { - if (range_item_last && !range_drag_enabled && Input::get_singleton()->is_mouse_button_pressed(BUTTON_LEFT)) { + if (range_item_last && !range_drag_enabled && Input::get_singleton()->is_mouse_button_pressed(MOUSE_BUTTON_LEFT)) { Point2 pos = get_local_mouse_position() - cache.bg->get_offset(); if (show_column_titles) { pos.y -= _get_title_button_height(); @@ -1829,11 +2093,10 @@ void Tree::_range_click_timeout() { click_handled = false; Ref<InputEventMouseButton> mb; mb.instance(); - ; propagate_mouse_activated = false; // done from outside, so signal handler can't clear the tree in the middle of emit (which is a common case) blocked++; - propagate_mouse_event(pos + cache.offset, 0, 0, false, root, BUTTON_LEFT, mb); + propagate_mouse_event(pos + cache.offset, 0, 0, false, root, MOUSE_BUTTON_LEFT, mb); blocked--; if (range_click_timer->is_one_shot()) { @@ -1856,7 +2119,7 @@ void Tree::_range_click_timeout() { } } -int Tree::propagate_mouse_event(const Point2i &p_pos, int x_ofs, int y_ofs, bool p_doubleclick, TreeItem *p_item, int p_button, const Ref<InputEventWithModifiers> &p_mod) { +int Tree::propagate_mouse_event(const Point2i &p_pos, int x_ofs, int y_ofs, bool p_double_click, TreeItem *p_item, int p_button, const Ref<InputEventWithModifiers> &p_mod) { int item_h = compute_item_height(p_item) + cache.vseparation; bool skip = (p_item == root && hide_root); @@ -1869,7 +2132,7 @@ int Tree::propagate_mouse_event(const Point2i &p_pos, int x_ofs, int y_ofs, bool } if (!p_item->disable_folding && !hide_folding && (p_pos.x >= x_ofs && p_pos.x < (x_ofs + cache.item_margin))) { - if (p_item->children) { + if (p_item->first_child) { p_item->set_collapsed(!p_item->is_collapsed()); } @@ -1916,7 +2179,7 @@ int Tree::propagate_mouse_event(const Point2i &p_pos, int x_ofs, int y_ofs, bool x -= cache.hseparation; } - if (!p_item->disable_folding && !hide_folding && !p_item->cells[col].editable && !p_item->cells[col].selectable && p_item->get_children()) { + if (!p_item->disable_folding && !hide_folding && !p_item->cells[col].editable && !p_item->cells[col].selectable && p_item->get_first_child()) { p_item->set_collapsed(!p_item->is_collapsed()); return -1; //collapse/uncollapse because nothing can be done with item } @@ -1950,10 +2213,10 @@ int Tree::propagate_mouse_event(const Point2i &p_pos, int x_ofs, int y_ofs, bool col_width -= w + cache.button_margin; } - if (p_button == BUTTON_LEFT || (p_button == BUTTON_RIGHT && allow_rmb_select)) { + if (p_button == MOUSE_BUTTON_LEFT || (p_button == MOUSE_BUTTON_RIGHT && allow_rmb_select)) { /* process selection */ - if (p_doubleclick && (!c.editable || c.mode == TreeItem::CELL_MODE_CUSTOM || c.mode == TreeItem::CELL_MODE_ICON /*|| c.mode==TreeItem::CELL_MODE_CHECK*/)) { //it's confusing for check + if (p_double_click && (!c.editable || c.mode == TreeItem::CELL_MODE_CUSTOM || c.mode == TreeItem::CELL_MODE_ICON /*|| c.mode==TreeItem::CELL_MODE_CHECK*/)) { //it's confusing for check propagate_mouse_activated = true; @@ -1961,11 +2224,11 @@ int Tree::propagate_mouse_event(const Point2i &p_pos, int x_ofs, int y_ofs, bool return -1; } - if (select_mode == SELECT_MULTI && p_mod->get_command() && c.selectable) { - if (!c.selected || p_button == BUTTON_RIGHT) { + if (select_mode == SELECT_MULTI && p_mod->is_command_pressed() && c.selectable) { + if (!c.selected || p_button == MOUSE_BUTTON_RIGHT) { p_item->select(col); emit_signal("multi_selected", p_item, col, true); - if (p_button == BUTTON_RIGHT) { + if (p_button == MOUSE_BUTTON_RIGHT) { emit_signal("item_rmb_selected", get_local_mouse_position()); } @@ -1978,25 +2241,25 @@ int Tree::propagate_mouse_event(const Point2i &p_pos, int x_ofs, int y_ofs, bool } else { if (c.selectable) { - if (select_mode == SELECT_MULTI && p_mod->get_shift() && selected_item && selected_item != p_item) { + if (select_mode == SELECT_MULTI && p_mod->is_shift_pressed() && selected_item && selected_item != p_item) { bool inrange = false; select_single_item(p_item, root, col, selected_item, &inrange); - if (p_button == BUTTON_RIGHT) { + if (p_button == MOUSE_BUTTON_RIGHT) { emit_signal("item_rmb_selected", get_local_mouse_position()); } } else { int icount = _count_selected_items(root); - if (select_mode == SELECT_MULTI && icount > 1 && p_button != BUTTON_RIGHT) { + if (select_mode == SELECT_MULTI && icount > 1 && p_button != MOUSE_BUTTON_RIGHT) { single_select_defer = p_item; single_select_defer_column = col; } else { - if (p_button != BUTTON_RIGHT || !c.selected) { + if (p_button != MOUSE_BUTTON_RIGHT || !c.selected) { select_single_item(p_item, root, col); } - if (p_button == BUTTON_RIGHT) { + if (p_button == MOUSE_BUTTON_RIGHT) { emit_signal("item_rmb_selected", get_local_mouse_position()); } } @@ -2066,7 +2329,7 @@ int Tree::propagate_mouse_event(const Point2i &p_pos, int x_ofs, int y_ofs, bool /* touching the combo */ bool up = p_pos.y < (item_h / 2); - if (p_button == BUTTON_LEFT) { + if (p_button == MOUSE_BUTTON_LEFT) { if (range_click_timer->get_time_left() == 0) { range_item_last = p_item; range_up_last = up; @@ -2083,13 +2346,13 @@ int Tree::propagate_mouse_event(const Point2i &p_pos, int x_ofs, int y_ofs, bool item_edited(col, p_item); - } else if (p_button == BUTTON_RIGHT) { + } else if (p_button == MOUSE_BUTTON_RIGHT) { p_item->set_range(col, (up ? c.max : c.min)); item_edited(col, p_item); - } else if (p_button == BUTTON_WHEEL_UP) { + } else if (p_button == MOUSE_BUTTON_WHEEL_UP) { p_item->set_range(col, c.val + c.step); item_edited(col, p_item); - } else if (p_button == BUTTON_WHEEL_DOWN) { + } else if (p_button == MOUSE_BUTTON_WHEEL_DOWN) { p_item->set_range(col, c.val - c.step); item_edited(col, p_item); } @@ -2122,14 +2385,14 @@ int Tree::propagate_mouse_event(const Point2i &p_pos, int x_ofs, int y_ofs, bool } if (!p_item->cells[col].custom_button || !on_arrow) { - item_edited(col, p_item, p_button == BUTTON_LEFT); + item_edited(col, p_item, p_button == MOUSE_BUTTON_LEFT); } click_handled = true; return -1; } break; }; - if (!bring_up_editor || p_button != BUTTON_LEFT) { + if (!bring_up_editor || p_button != MOUSE_BUTTON_LEFT) { return -1; } @@ -2154,10 +2417,10 @@ int Tree::propagate_mouse_event(const Point2i &p_pos, int x_ofs, int y_ofs, bool if (!p_item->collapsed) { /* if not collapsed, check the children */ - TreeItem *c = p_item->children; + TreeItem *c = p_item->first_child; while (c) { - int child_h = propagate_mouse_event(new_pos, x_ofs, y_ofs, p_doubleclick, c, p_button, p_mod); + int child_h = propagate_mouse_event(new_pos, x_ofs, y_ofs, p_double_click, c, p_button, p_mod); if (child_h < 0) { return -1; // break, stop propagating, no need to anymore @@ -2169,7 +2432,7 @@ int Tree::propagate_mouse_event(const Point2i &p_pos, int x_ofs, int y_ofs, bool item_h += child_h; } } - if (p_item == root && p_button == BUTTON_RIGHT) { + if (p_item == root && p_button == MOUSE_BUTTON_RIGHT) { emit_signal("empty_rmb", get_local_mouse_position()); } } @@ -2260,7 +2523,7 @@ void Tree::popup_select(int p_option) { void Tree::_go_left() { if (selected_col == 0) { - if (selected_item->get_children() != nullptr && !selected_item->is_collapsed()) { + if (selected_item->get_first_child() != nullptr && !selected_item->is_collapsed()) { selected_item->set_collapsed(true); } else { if (columns.size() == 1) { // goto parent with one column @@ -2288,7 +2551,7 @@ void Tree::_go_left() { void Tree::_go_right() { if (selected_col == (columns.size() - 1)) { - if (selected_item->get_children() != nullptr && selected_item->is_collapsed()) { + if (selected_item->get_first_child() != nullptr && selected_item->is_collapsed()) { selected_item->set_collapsed(false); } else if (selected_item->get_next_visible()) { selected_col = 0; @@ -2392,9 +2655,11 @@ void Tree::_go_down() { } void Tree::_gui_input(Ref<InputEvent> p_event) { + ERR_FAIL_COND(p_event.is_null()); + Ref<InputEventKey> k = p_event; - bool is_command = k.is_valid() && k->get_command(); + bool is_command = k.is_valid() && k->is_command_pressed(); if (p_event->is_action("ui_right") && p_event->is_pressed()) { if (!cursor_can_exit_tree) { accept_event(); @@ -2403,9 +2668,9 @@ void Tree::_gui_input(Ref<InputEvent> p_event) { if (!selected_item || select_mode == SELECT_ROW || selected_col > (columns.size() - 1)) { return; } - if (k.is_valid() && k->get_alt()) { + if (k.is_valid() && k->is_alt_pressed()) { selected_item->set_collapsed(false); - TreeItem *next = selected_item->get_children(); + TreeItem *next = selected_item->get_first_child(); while (next && next != selected_item->next) { next->set_collapsed(false); next = next->get_next_visible(); @@ -2422,9 +2687,9 @@ void Tree::_gui_input(Ref<InputEvent> p_event) { return; } - if (k.is_valid() && k->get_alt()) { + if (k.is_valid() && k->is_alt_pressed()) { selected_item->set_collapsed(true); - TreeItem *next = selected_item->get_children(); + TreeItem *next = selected_item->get_first_child(); while (next && next != selected_item->next) { next->set_collapsed(true); next = next->get_next_visible(); @@ -2552,7 +2817,7 @@ void Tree::_gui_input(Ref<InputEvent> p_event) { if (!k->is_pressed()) { return; } - if (k->get_command() || (k->get_shift() && k->get_unicode() == 0) || k->get_metakey()) { + if (k->is_command_pressed() || (k->is_shift_pressed() && k->get_unicode() == 0) || k->is_meta_pressed()) { return; } if (!root) { @@ -2710,7 +2975,7 @@ void Tree::_gui_input(Ref<InputEvent> p_event) { bool rtl = is_layout_rtl(); if (!b->is_pressed()) { - if (b->get_button_index() == BUTTON_LEFT) { + if (b->get_button_index() == MOUSE_BUTTON_LEFT) { Point2 pos = b->get_position(); if (rtl) { pos.x = get_size().width - pos.x; @@ -2791,8 +3056,8 @@ void Tree::_gui_input(Ref<InputEvent> p_event) { } switch (b->get_button_index()) { - case BUTTON_RIGHT: - case BUTTON_LEFT: { + case MOUSE_BUTTON_RIGHT: + case MOUSE_BUTTON_LEFT: { Ref<StyleBox> bg = cache.bg; Point2 pos = b->get_position(); @@ -2805,7 +3070,7 @@ void Tree::_gui_input(Ref<InputEvent> p_event) { pos.y -= _get_title_button_height(); if (pos.y < 0) { - if (b->get_button_index() == BUTTON_LEFT) { + if (b->get_button_index() == MOUSE_BUTTON_LEFT) { pos.x += cache.offset.x; int len = 0; for (int i = 0; i < columns.size(); i++) { @@ -2822,8 +3087,8 @@ void Tree::_gui_input(Ref<InputEvent> p_event) { break; } } - if (!root || (!root->get_children() && hide_root)) { - if (b->get_button_index() == BUTTON_RIGHT && allow_rmb_select) { + if (!root || (!root->get_first_child() && hide_root)) { + if (b->get_button_index() == MOUSE_BUTTON_RIGHT && allow_rmb_select) { emit_signal("empty_tree_rmb_selected", get_local_mouse_position()); } break; @@ -2834,7 +3099,7 @@ void Tree::_gui_input(Ref<InputEvent> p_event) { propagate_mouse_activated = false; blocked++; - propagate_mouse_event(pos + cache.offset, 0, 0, b->is_doubleclick(), root, b->get_button_index(), b); + propagate_mouse_event(pos + cache.offset, 0, 0, b->is_double_click(), root, b->get_button_index(), b); blocked--; if (pressing_for_editor) { @@ -2844,7 +3109,7 @@ void Tree::_gui_input(Ref<InputEvent> p_event) { } } - if (b->get_button_index() == BUTTON_RIGHT) { + if (b->get_button_index() == MOUSE_BUTTON_RIGHT) { break; } @@ -2861,14 +3126,14 @@ void Tree::_gui_input(Ref<InputEvent> p_event) { drag_accum = 0; //last_drag_accum=0; drag_from = v_scroll->get_value(); - drag_touching = !DisplayServer::get_singleton()->screen_is_touchscreen(DisplayServer::get_singleton()->window_get_current_screen(get_viewport()->get_window_id())); + drag_touching = DisplayServer::get_singleton()->screen_is_touchscreen(DisplayServer::get_singleton()->window_get_current_screen(get_viewport()->get_window_id())); drag_touching_deaccel = false; if (drag_touching) { set_physics_process_internal(true); } - if (b->get_button_index() == BUTTON_LEFT) { - if (get_item_at_position(b->get_position()) == nullptr && !b->get_shift() && !b->get_control() && !b->get_command()) { + if (b->get_button_index() == MOUSE_BUTTON_LEFT) { + if (get_item_at_position(b->get_position()) == nullptr && !b->is_shift_pressed() && !b->is_ctrl_pressed() && !b->is_command_pressed()) { emit_signal("nothing_selected"); } } @@ -2880,7 +3145,7 @@ void Tree::_gui_input(Ref<InputEvent> p_event) { } } break; - case BUTTON_WHEEL_UP: { + case MOUSE_BUTTON_WHEEL_UP: { double prev_value = v_scroll->get_value(); v_scroll->set_value(v_scroll->get_value() - v_scroll->get_page() * b->get_factor() / 8); if (v_scroll->get_value() != prev_value) { @@ -2888,7 +3153,7 @@ void Tree::_gui_input(Ref<InputEvent> p_event) { } } break; - case BUTTON_WHEEL_DOWN: { + case MOUSE_BUTTON_WHEEL_DOWN: { double prev_value = v_scroll->get_value(); v_scroll->set_value(v_scroll->get_value() + v_scroll->get_page() * b->get_factor() / 8); if (v_scroll->get_value() != prev_value) { @@ -3001,6 +3266,10 @@ bool Tree::edit_selected() { return false; } +bool Tree::is_editing() { + return popup_editor->is_visible(); +} + Size2 Tree::get_internal_min_size() const { Size2i size = cache.bg->get_offset(); if (root) { @@ -3162,7 +3431,6 @@ void Tree::_notification(int p_what) { RID ci = get_canvas_item(); Ref<StyleBox> bg = cache.bg; - Ref<StyleBox> bg_focus = get_theme_stylebox("bg_focus"); Color font_outline_color = get_theme_color("font_outline_color"); int outline_size = get_theme_constant("outline_size"); @@ -3171,11 +3439,6 @@ void Tree::_notification(int p_what) { Size2 draw_size = get_size() - bg->get_minimum_size(); bg->draw(ci, Rect2(Point2(), get_size())); - if (has_focus()) { - RenderingServer::get_singleton()->canvas_item_add_clip_ignore(ci, true); - bg_focus->draw(ci, Rect2(Point2(), get_size())); - RenderingServer::get_singleton()->canvas_item_add_clip_ignore(ci, false); - } int tbh = _get_title_button_height(); @@ -3209,6 +3472,15 @@ void Tree::_notification(int p_what) { columns[i].text_buf->draw(ci, text_pos, cache.title_button_color); } } + + // Draw the background focus outline last, so that it is drawn in front of the section headings. + // Otherwise, section heading backgrounds can appear to be in front of the focus outline when scrolling. + if (has_focus()) { + RenderingServer::get_singleton()->canvas_item_add_clip_ignore(ci, true); + const Ref<StyleBox> bg_focus = get_theme_stylebox("bg_focus"); + bg_focus->draw(ci, Rect2(Point2(), get_size())); + RenderingServer::get_singleton()->canvas_item_add_clip_ignore(ci, false); + } } if (p_what == NOTIFICATION_THEME_CHANGED || p_what == NOTIFICATION_LAYOUT_DIRECTION_CHANGED || p_what == NOTIFICATION_TRANSLATION_CHANGED) { @@ -3250,38 +3522,15 @@ TreeItem *Tree::create_item(TreeItem *p_parent, int p_idx) { TreeItem *ti = nullptr; if (p_parent) { - // Append or insert a new item to the given parent. - ti = memnew(TreeItem(this)); - ERR_FAIL_COND_V(!ti, nullptr); - ti->cells.resize(columns.size()); - - TreeItem *prev = nullptr; - TreeItem *c = p_parent->children; - int idx = 0; - - while (c) { - if (idx++ == p_idx) { - ti->next = c; - break; - } - prev = c; - c = c->next; - } - - if (prev) { - prev->next = ti; - } else { - p_parent->children = ti; - } - ti->parent = p_parent; - + ERR_FAIL_COND_V_MSG(p_parent->tree != this, nullptr, "A different tree owns the given parent"); + ti = p_parent->create_child(p_idx); } else { if (!root) { // No root exists, make the given item the new root. ti = memnew(TreeItem(this)); ERR_FAIL_COND_V(!ti, nullptr); ti->cells.resize(columns.size()); - + ti->is_root = true; root = ti; } else { // Root exists, append or insert to root. @@ -3302,8 +3551,8 @@ TreeItem *Tree::get_last_item() { while (last) { if (last->next) { last = last->next; - } else if (last->children) { - last = last->children; + } else if (last->first_child) { + last = last->first_child; } else { break; } @@ -3472,8 +3721,8 @@ TreeItem *Tree::get_next_selected(TreeItem *p_item) { if (!p_item) { p_item = root; } else { - if (p_item->children) { - p_item = p_item->children; + if (p_item->first_child) { + p_item = p_item->first_child; } else if (p_item->next) { p_item = p_item->next; @@ -3542,7 +3791,7 @@ int Tree::get_column_width(int p_column) const { void Tree::propagate_set_columns(TreeItem *p_item) { p_item->cells.resize(columns.size()); - TreeItem *c = p_item->get_children(); + TreeItem *c = p_item->get_first_child(); while (c) { propagate_set_columns(c); c = c->next; @@ -3592,8 +3841,8 @@ int Tree::get_item_offset(TreeItem *p_item) const { ofs += cache.vseparation; } - if (it->children && !it->collapsed) { - it = it->children; + if (it->first_child && !it->collapsed) { + it = it->first_child; } else if (it->next) { it = it->next; @@ -3916,7 +4165,7 @@ TreeItem *Tree::_find_item_at_pos(TreeItem *p_item, const Point2 &p_pos, int &r_ return nullptr; // do not try children, it's collapsed } - TreeItem *n = p_item->get_children(); + TreeItem *n = p_item->get_first_child(); while (n) { int ch; TreeItem *r = _find_item_at_pos(n, pos, r_column, ch, section); @@ -4236,7 +4485,7 @@ void Tree::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::BOOL, "allow_rmb_select"), "set_allow_rmb_select", "get_allow_rmb_select"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "hide_folding"), "set_hide_folding", "is_folding_hidden"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "hide_root"), "set_hide_root", "is_root_hidden"); - ADD_PROPERTY(PropertyInfo(Variant::INT, "drop_mode_flags", PROPERTY_HINT_FLAGS, "On Item,In between"), "set_drop_mode_flags", "get_drop_mode_flags"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "drop_mode_flags", PROPERTY_HINT_FLAGS, "On Item,In Between"), "set_drop_mode_flags", "get_drop_mode_flags"); ADD_PROPERTY(PropertyInfo(Variant::INT, "select_mode", PROPERTY_HINT_ENUM, "Single,Row,Multi"), "set_select_mode", "get_select_mode"); ADD_SIGNAL(MethodInfo("item_selected")); @@ -4250,7 +4499,7 @@ void Tree::_bind_methods() { ADD_SIGNAL(MethodInfo("item_custom_button_pressed")); ADD_SIGNAL(MethodInfo("item_double_clicked")); ADD_SIGNAL(MethodInfo("item_collapsed", PropertyInfo(Variant::OBJECT, "item", PROPERTY_HINT_RESOURCE_TYPE, "TreeItem"))); - //ADD_SIGNAL( MethodInfo("item_doubleclicked" ) ); + //ADD_SIGNAL( MethodInfo("item_double_clicked" ) ); ADD_SIGNAL(MethodInfo("button_pressed", PropertyInfo(Variant::OBJECT, "item", PROPERTY_HINT_RESOURCE_TYPE, "TreeItem"), PropertyInfo(Variant::INT, "column"), PropertyInfo(Variant::INT, "id"))); ADD_SIGNAL(MethodInfo("custom_popup_edited", PropertyInfo(Variant::BOOL, "arrow_clicked"))); ADD_SIGNAL(MethodInfo("item_activated")); @@ -4315,6 +4564,8 @@ Tree::Tree() { set_mouse_filter(MOUSE_FILTER_STOP); set_clip_contents(true); + + update_cache(); } Tree::~Tree() { diff --git a/scene/gui/tree.h b/scene/gui/tree.h index 1be21cb4a4..5176d01497 100644 --- a/scene/gui/tree.h +++ b/scene/gui/tree.h @@ -112,6 +112,8 @@ private: Vector<Button> buttons; + Ref<Font> custom_font; + Cell() { text_buf.instance(); } @@ -122,14 +124,18 @@ private: Vector<Cell> cells; - bool collapsed; // won't show children - bool disable_folding; - int custom_min_height; + bool collapsed = false; // won't show children + bool disable_folding = false; + int custom_min_height = 0; + + TreeItem *parent = nullptr; // parent item + TreeItem *prev = nullptr; // previous in list + TreeItem *next = nullptr; // next in list + TreeItem *first_child = nullptr; - TreeItem *parent; // parent item - TreeItem *next; // next in list - TreeItem *children; //child items - Tree *tree; //tree (for reference) + Vector<TreeItem *> children_cache; + bool is_root = false; // for tree root + Tree *tree; // tree (for reference) TreeItem(Tree *p_tree); @@ -138,9 +144,40 @@ private: void _cell_selected(int p_cell); void _cell_deselected(int p_cell); + void _change_tree(Tree *p_tree); + + _FORCE_INLINE_ void _create_children_cache() { + if (children_cache.is_empty()) { + TreeItem *c = first_child; + while (c) { + children_cache.append(c); + c = c->next; + } + } + } + + _FORCE_INLINE_ void _unlink_from_tree() { + TreeItem *p = get_prev(); + if (p) { + p->next = next; + } + if (next) { + next->prev = p; + } + if (parent) { + if (!parent->children_cache.is_empty()) { + parent->children_cache.remove(get_index()); + } + if (parent->first_child == this) { + parent->first_child = next; + } + } + } + protected: static void _bind_methods(); - //bind helpers + + // Bind helpers Dictionary _get_range_config(int p_column) { Dictionary d; double min = 0.0, max = 0.0, step = 0.0; @@ -156,6 +193,13 @@ protected: remove_child(Object::cast_to<TreeItem>(p_child)); } + void _move_before(Object *p_item) { + move_before(Object::cast_to<TreeItem>(p_item)); + } + void _move_after(Object *p_item) { + move_after(Object::cast_to<TreeItem>(p_item)); + } + Variant _call_recursive_bind(const Variant **p_args, int p_argcount, Callable::CallError &r_error); public: @@ -229,19 +273,11 @@ public: void set_collapsed(bool p_collapsed); bool is_collapsed(); + void uncollapse_tree(); + void set_custom_minimum_height(int p_height); int get_custom_minimum_height() const; - TreeItem *get_prev(); - TreeItem *get_next(); - TreeItem *get_parent(); - TreeItem *get_children(); - - TreeItem *get_prev_visible(bool p_wrap = false); - TreeItem *get_next_visible(bool p_wrap = false); - - void remove_child(TreeItem *p_item); - void set_selectable(int p_column, bool p_selectable); bool is_selectable(int p_column) const; @@ -257,6 +293,9 @@ public: Color get_custom_color(int p_column) const; void clear_custom_color(int p_column); + void set_custom_font(int p_column, const Ref<Font> &p_font); + Ref<Font> get_custom_font(int p_column) const; + void set_custom_bg_color(int p_column, const Color &p_color, bool p_bg_outline = false); void clear_custom_bg_color(int p_column); Color get_custom_bg_color(int p_column) const; @@ -267,22 +306,43 @@ public: void set_tooltip(int p_column, const String &p_tooltip); String get_tooltip(int p_column) const; - void clear_children(); - void set_text_align(int p_column, TextAlign p_align); TextAlign get_text_align(int p_column) const; void set_expand_right(int p_column, bool p_enable); bool get_expand_right(int p_column) const; - void move_to_top(); - void move_to_bottom(); - void set_disable_folding(bool p_disable); bool is_folding_disabled() const; + /* Item manipulation */ + + TreeItem *create_child(int p_idx = -1); + + Tree *get_tree(); + + TreeItem *get_prev(); + TreeItem *get_next(); + TreeItem *get_parent(); + TreeItem *get_first_child(); + + TreeItem *get_prev_visible(bool p_wrap = false); + TreeItem *get_next_visible(bool p_wrap = false); + + TreeItem *get_child(int p_idx); + int get_child_count(); + Array get_children(); + int get_index(); + + void move_before(TreeItem *p_item); + void move_after(TreeItem *p_item); + + void remove_child(TreeItem *p_item); + void call_recursive(const StringName &p_method, const Variant **p_args, int p_argcount, Callable::CallError &r_error); + void clear_children(); + ~TreeItem(); }; @@ -388,7 +448,7 @@ private: void draw_item_rect(TreeItem::Cell &p_cell, const Rect2i &p_rect, const Color &p_color, const Color &p_icon_color, int p_ol_size, const Color &p_ol_color); int draw_item(const Point2i &p_pos, const Point2 &p_draw_ofs, const Size2 &p_draw_size, TreeItem *p_item); void select_single_item(TreeItem *p_selected, TreeItem *p_current, int p_col, TreeItem *p_prev = nullptr, bool *r_in_range = nullptr, bool p_force_deselect = false); - int propagate_mouse_event(const Point2i &p_pos, int x_ofs, int y_ofs, bool p_doubleclick, TreeItem *p_item, int p_button, const Ref<InputEventWithModifiers> &p_mod); + int propagate_mouse_event(const Point2i &p_pos, int x_ofs, int y_ofs, bool p_double_click, TreeItem *p_item, int p_button, const Ref<InputEventWithModifiers> &p_mod); void _text_editor_enter(String p_text); void _text_editor_modal_close(); void value_editor_changed(double p_value); @@ -439,6 +499,8 @@ private: Color guide_color; Color drop_position_color; Color relationship_line_color; + Color parent_hl_line_color; + Color children_hl_line_color; Color custom_button_font_highlight; int hseparation = 0; @@ -447,6 +509,10 @@ private: int button_margin = 0; Point2 offset; int draw_relationship_lines = 0; + int relationship_line_width = 0; + int parent_hl_line_width = 0; + int children_hl_line_width = 0; + int parent_hl_line_margin = 0; int draw_guides = 0; int scroll_border = 0; int scroll_speed = 0; @@ -519,6 +585,8 @@ private: bool hide_folding = false; int _count_selected_items(TreeItem *p_from) const; + bool _is_branch_selected(TreeItem *p_from) const; + bool _is_sibling_branch_selected(TreeItem *p_from) const; void _go_left(); void _go_right(); void _go_down(); @@ -602,6 +670,7 @@ public: int get_item_offset(TreeItem *p_item) const; Rect2 get_item_rect(TreeItem *p_item, int p_column = -1) const; bool edit_selected(); + bool is_editing(); // First item that starts with the text, from the current focused item down and wraps around. TreeItem *search_item_text(const String &p_find, int *r_col = nullptr, bool p_selectable = false); |