diff options
Diffstat (limited to 'scene/gui')
66 files changed, 3154 insertions, 1727 deletions
diff --git a/scene/gui/base_button.cpp b/scene/gui/base_button.cpp index 806c8afa5b..4f71481280 100644 --- a/scene/gui/base_button.cpp +++ b/scene/gui/base_button.cpp @@ -56,185 +56,23 @@ void BaseButton::_gui_input(Ref<InputEvent> p_event) { if (status.disabled) // no interaction with disabled button return; - Ref<InputEventMouseButton> b = p_event; + Ref<InputEventMouseButton> mouse_button = p_event; + bool ui_accept = p_event->is_action("ui_accept") && !p_event->is_echo(); - if (b.is_valid()) { - if (status.disabled || ((1 << (b->get_button_index() - 1)) & button_mask) == 0) - return; - - if (status.pressing_button) - return; - - if (action_mode == ACTION_MODE_BUTTON_PRESS) { - - if (b->is_pressed()) { - - emit_signal("button_down"); - - if (!toggle_mode) { //mouse press attempt - - status.press_attempt = true; - status.pressing_inside = true; - - pressed(); - if (get_script_instance()) { - Variant::CallError ce; - get_script_instance()->call(SceneStringNames::get_singleton()->_pressed, NULL, 0, ce); - } - - _unpress_group(); - emit_signal("pressed"); - - } else { - - status.pressed = !status.pressed; - pressed(); - - _unpress_group(); - emit_signal("pressed"); - - toggled(status.pressed); - if (get_script_instance()) { - get_script_instance()->call(SceneStringNames::get_singleton()->_toggled, status.pressed); - } - emit_signal("toggled", status.pressed); - } - - } else { - - emit_signal("button_up"); - - /* this is pointless if (status.press_attempt && status.pressing_inside) { - //released(); - emit_signal("released"); - } -*/ - status.press_attempt = false; - } - update(); - return; - } - - if (b->is_pressed()) { - - status.press_attempt = true; - status.pressing_inside = true; - emit_signal("button_down"); - - } else { - - emit_signal("button_up"); - - if (status.press_attempt && status.pressing_inside) { - - if (!toggle_mode) { //mouse press attempt - - pressed(); - if (get_script_instance()) { - Variant::CallError ce; - get_script_instance()->call(SceneStringNames::get_singleton()->_pressed, NULL, 0, ce); - } - - _unpress_group(); - emit_signal("pressed"); - - } else { - - status.pressed = !status.pressed; - - pressed(); - _unpress_group(); - emit_signal("pressed"); - - toggled(status.pressed); - if (get_script_instance()) { - get_script_instance()->call(SceneStringNames::get_singleton()->_toggled, status.pressed); - } - emit_signal("toggled", status.pressed); - } - } - - status.press_attempt = false; - } - - update(); + bool button_masked = mouse_button.is_valid() && ((1 << (mouse_button->get_button_index() - 1)) & button_mask) > 0; + if (button_masked || ui_accept) { + on_action_event(p_event); + return; } - Ref<InputEventMouseMotion> mm = p_event; - - if (mm.is_valid()) { - if (status.press_attempt && status.pressing_button == 0) { + Ref<InputEventMouseMotion> mouse_motion = p_event; + if (mouse_motion.is_valid()) { + if (status.press_attempt) { bool last_press_inside = status.pressing_inside; - status.pressing_inside = has_point(mm->get_position()); - if (last_press_inside != status.pressing_inside) + status.pressing_inside = has_point(mouse_motion->get_position()); + if (last_press_inside != status.pressing_inside) { update(); - } - } - - if (!mm.is_valid() && !b.is_valid()) { - - if (p_event->is_echo()) { - return; - } - - if (status.disabled) { - return; - } - - if (status.press_attempt && status.pressing_button == 0) { - return; - } - - if (p_event->is_action("ui_accept")) { - - if (p_event->is_pressed()) { - - status.pressing_button++; - status.press_attempt = true; - status.pressing_inside = true; - emit_signal("button_down"); - - } else if (status.press_attempt) { - - if (status.pressing_button) - status.pressing_button--; - - if (status.pressing_button) - return; - - status.press_attempt = false; - status.pressing_inside = false; - - emit_signal("button_up"); - - if (!toggle_mode) { //mouse press attempt - - pressed(); - if (get_script_instance()) { - Variant::CallError ce; - get_script_instance()->call(SceneStringNames::get_singleton()->_pressed, NULL, 0, ce); - } - - _unpress_group(); - emit_signal("pressed"); - } else { - - status.pressed = !status.pressed; - - pressed(); - _unpress_group(); - emit_signal("pressed"); - - toggled(status.pressed); - if (get_script_instance()) { - get_script_instance()->call(SceneStringNames::get_singleton()->_toggled, status.pressed); - } - emit_signal("toggled", status.pressed); - } } - - accept_event(); - update(); } } } @@ -255,7 +93,6 @@ void BaseButton::_notification(int p_what) { if (status.press_attempt) { status.press_attempt = false; - status.pressing_button = 0; update(); } } @@ -268,9 +105,8 @@ void BaseButton::_notification(int p_what) { if (p_what == NOTIFICATION_FOCUS_EXIT) { - if (status.pressing_button && status.press_attempt) { + if (status.press_attempt) { status.press_attempt = false; - status.pressing_button = 0; status.hovering = false; update(); } else if (status.hovering) { @@ -279,13 +115,7 @@ void BaseButton::_notification(int p_what) { } } - if (p_what == NOTIFICATION_ENTER_TREE) { - } - - if (p_what == NOTIFICATION_EXIT_TREE) { - } - - if (p_what == NOTIFICATION_VISIBILITY_CHANGED && !is_visible_in_tree()) { + if (p_what == NOTIFICATION_EXIT_TREE || (p_what == NOTIFICATION_VISIBILITY_CHANGED && !is_visible_in_tree())) { if (!toggle_mode) { status.pressed = false; @@ -293,21 +123,66 @@ void BaseButton::_notification(int p_what) { status.hovering = false; status.press_attempt = false; status.pressing_inside = false; - status.pressing_button = 0; } } -void BaseButton::pressed() { +void BaseButton::_pressed() { - if (get_script_instance()) - get_script_instance()->call("pressed"); + if (get_script_instance()) { + get_script_instance()->call(SceneStringNames::get_singleton()->_pressed); + } + pressed(); + emit_signal("pressed"); } -void BaseButton::toggled(bool p_pressed) { +void BaseButton::_toggled(bool p_pressed) { if (get_script_instance()) { - get_script_instance()->call("toggled", p_pressed); + get_script_instance()->call(SceneStringNames::get_singleton()->_toggled, p_pressed); } + toggled(p_pressed); + emit_signal("toggled", p_pressed); +} + +void BaseButton::on_action_event(Ref<InputEvent> p_event) { + + if (p_event->is_pressed()) { + status.press_attempt = true; + status.pressing_inside = true; + emit_signal("button_down"); + } + + if (status.press_attempt && status.pressing_inside) { + if (toggle_mode) { + if ((p_event->is_pressed() && action_mode == ACTION_MODE_BUTTON_PRESS) || (!p_event->is_pressed() && action_mode == ACTION_MODE_BUTTON_RELEASE)) { + if (action_mode == ACTION_MODE_BUTTON_PRESS) { + status.press_attempt = false; + status.pressing_inside = false; + } + status.pressed = !status.pressed; + _unpress_group(); + _toggled(status.pressed); + _pressed(); + } + } else { + if (!p_event->is_pressed()) { + _pressed(); + } + } + } + + if (!p_event->is_pressed()) { // pressed state should be correct with button_up signal + emit_signal("button_up"); + status.press_attempt = false; + } + + update(); +} + +void BaseButton::pressed() { +} + +void BaseButton::toggled(bool p_pressed) { } void BaseButton::set_disabled(bool p_disabled) { @@ -321,7 +196,6 @@ void BaseButton::set_disabled(bool p_disabled) { } status.press_attempt = false; status.pressing_inside = false; - status.pressing_button = 0; } update(); _change_notify("disabled"); @@ -344,6 +218,8 @@ void BaseButton::set_pressed(bool p_pressed) { if (p_pressed) { _unpress_group(); } + _toggled(status.pressed); + update(); } @@ -379,7 +255,7 @@ BaseButton::DrawMode BaseButton::get_draw_mode() const { bool pressing; if (status.press_attempt) { - pressing = status.pressing_inside; + pressing = (status.pressing_inside || keep_pressed_outside); if (status.pressed) pressing = !pressing; } else { @@ -449,10 +325,17 @@ Control::FocusMode BaseButton::get_enabled_focus_mode() const { return enabled_focus_mode; } -void BaseButton::set_shortcut(const Ref<ShortCut> &p_shortcut) { +void BaseButton::set_keep_pressed_outside(bool p_on) { - if (shortcut.is_null() == p_shortcut.is_null()) - return; + keep_pressed_outside = p_on; +} + +bool BaseButton::is_keep_pressed_outside() const { + + return keep_pressed_outside; +} + +void BaseButton::set_shortcut(const Ref<ShortCut> &p_shortcut) { shortcut = p_shortcut; set_process_unhandled_input(shortcut.is_valid()); @@ -464,17 +347,12 @@ Ref<ShortCut> BaseButton::get_shortcut() const { void BaseButton::_unhandled_input(Ref<InputEvent> p_event) { - if (!is_disabled() && is_visible_in_tree() && p_event->is_pressed() && !p_event->is_echo() && shortcut.is_valid() && shortcut->is_shortcut(p_event)) { + if (!is_disabled() && is_visible_in_tree() && !p_event->is_echo() && shortcut.is_valid() && shortcut->is_shortcut(p_event)) { if (get_viewport()->get_modal_stack_top() && !get_viewport()->get_modal_stack_top()->is_a_parent_of(this)) return; //ignore because of modal window - if (is_toggle_mode()) { - set_pressed(!is_pressed()); - emit_signal("toggled", is_pressed()); - } - - emit_signal("pressed"); + on_action_event(p_event); } } @@ -531,6 +409,8 @@ void BaseButton::_bind_methods() { ClassDB::bind_method(D_METHOD("get_draw_mode"), &BaseButton::get_draw_mode); ClassDB::bind_method(D_METHOD("set_enabled_focus_mode", "mode"), &BaseButton::set_enabled_focus_mode); ClassDB::bind_method(D_METHOD("get_enabled_focus_mode"), &BaseButton::get_enabled_focus_mode); + ClassDB::bind_method(D_METHOD("set_keep_pressed_outside", "enabled"), &BaseButton::set_keep_pressed_outside); + ClassDB::bind_method(D_METHOD("is_keep_pressed_outside"), &BaseButton::is_keep_pressed_outside); ClassDB::bind_method(D_METHOD("set_shortcut", "shortcut"), &BaseButton::set_shortcut); ClassDB::bind_method(D_METHOD("get_shortcut"), &BaseButton::get_shortcut); @@ -552,6 +432,7 @@ void BaseButton::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::INT, "action_mode", PROPERTY_HINT_ENUM, "Button Press,Button Release"), "set_action_mode", "get_action_mode"); ADD_PROPERTY(PropertyInfo(Variant::INT, "button_mask", PROPERTY_HINT_FLAGS, "Mouse Left, Mouse Right, Mouse Middle"), "set_button_mask", "get_button_mask"); ADD_PROPERTY(PropertyInfo(Variant::INT, "enabled_focus_mode", PROPERTY_HINT_ENUM, "None,Click,All"), "set_enabled_focus_mode", "get_enabled_focus_mode"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "keep_pressed_outside"), "set_keep_pressed_outside", "is_keep_pressed_outside"); ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "shortcut", PROPERTY_HINT_RESOURCE_TYPE, "ShortCut"), "set_shortcut", "get_shortcut"); ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "group", PROPERTY_HINT_RESOURCE_TYPE, "ButtonGroup"), "set_button_group", "get_button_group"); @@ -569,12 +450,12 @@ BaseButton::BaseButton() { toggle_mode = false; shortcut_in_tooltip = true; + keep_pressed_outside = false; status.pressed = false; status.press_attempt = false; status.hovering = false; status.pressing_inside = false; status.disabled = false; - status.pressing_button = 0; set_focus_mode(FOCUS_ALL); enabled_focus_mode = FOCUS_ALL; action_mode = ACTION_MODE_BUTTON_RELEASE; diff --git a/scene/gui/base_button.h b/scene/gui/base_button.h index 9a00cc79f2..2773f024df 100644 --- a/scene/gui/base_button.h +++ b/scene/gui/base_button.h @@ -32,9 +32,6 @@ #define BASE_BUTTON_H #include "scene/gui/control.h" -/** - @author Juan Linietsky <reduzio@gmail.com> -*/ class ButtonGroup; @@ -52,6 +49,7 @@ private: int button_mask; bool toggle_mode; bool shortcut_in_tooltip; + bool keep_pressed_outside; FocusMode enabled_focus_mode; Ref<ShortCut> shortcut; @@ -64,13 +62,16 @@ private: bool pressing_inside; bool disabled; - int pressing_button; } status; Ref<ButtonGroup> button_group; void _unpress_group(); + void _pressed(); + void _toggled(bool p_pressed); + + void on_action_event(Ref<InputEvent> p_event); protected: virtual void pressed(); @@ -110,6 +111,9 @@ public: void set_action_mode(ActionMode p_mode); ActionMode get_action_mode() const; + void set_keep_pressed_outside(bool p_on); + bool is_keep_pressed_outside() const; + void set_button_mask(int p_mask); int get_button_mask() const; @@ -133,7 +137,7 @@ VARIANT_ENUM_CAST(BaseButton::ActionMode) class ButtonGroup : public Resource { - GDCLASS(ButtonGroup, Resource) + GDCLASS(ButtonGroup, Resource); friend class BaseButton; Set<BaseButton *> buttons; diff --git a/scene/gui/box_container.cpp b/scene/gui/box_container.cpp index cc37d4cf7d..b7d2131ee9 100644 --- a/scene/gui/box_container.cpp +++ b/scene/gui/box_container.cpp @@ -91,7 +91,6 @@ void BoxContainer::_resort() { int stretch_diff = stretch_max - stretch_min; if (stretch_diff < 0) { //avoid negative stretch space - stretch_max = stretch_min; stretch_diff = 0; } diff --git a/scene/gui/button.cpp b/scene/gui/button.cpp index b77b57ddd4..4ce3f18505 100644 --- a/scene/gui/button.cpp +++ b/scene/gui/button.cpp @@ -29,6 +29,7 @@ /*************************************************************************/ #include "button.h" + #include "core/translation.h" #include "servers/visual_server.h" @@ -102,6 +103,7 @@ void Button::_notification(int p_what) { break; } + FALLTHROUGH; } case DRAW_PRESSED: { @@ -140,8 +142,8 @@ void Button::_notification(int p_what) { if (has_focus()) { - Ref<StyleBox> style = get_stylebox("focus"); - style->draw(ci, Rect2(Point2(), size)); + Ref<StyleBox> style2 = get_stylebox("focus"); + style2->draw(ci, Rect2(Point2(), size)); } Ref<Font> font = get_font("font"); @@ -157,7 +159,11 @@ void Button::_notification(int p_what) { switch (align) { case ALIGN_LEFT: { - text_ofs.x = style->get_margin(MARGIN_LEFT) + icon_ofs.x + _internal_margin[MARGIN_LEFT] + get_constant("hseparation"); + if (_internal_margin[MARGIN_LEFT] > 0) { + text_ofs.x = style->get_margin(MARGIN_LEFT) + icon_ofs.x + _internal_margin[MARGIN_LEFT] + get_constant("hseparation"); + } else { + text_ofs.x = style->get_margin(MARGIN_LEFT) + icon_ofs.x; + } text_ofs.y += style->get_offset().y; } break; case ALIGN_CENTER: { diff --git a/scene/gui/button.h b/scene/gui/button.h index 6ba3475e5a..370809060e 100644 --- a/scene/gui/button.h +++ b/scene/gui/button.h @@ -32,9 +32,6 @@ #define BUTTON_H #include "scene/gui/base_button.h" -/** - @author Juan Linietsky <reduzio@gmail.com> -*/ class Button : public BaseButton { diff --git a/scene/gui/check_box.cpp b/scene/gui/check_box.cpp index 1d8b74d9db..8744407763 100644 --- a/scene/gui/check_box.cpp +++ b/scene/gui/check_box.cpp @@ -79,7 +79,7 @@ void CheckBox::_notification(int p_what) { Vector2 ofs; ofs.x = sb->get_margin(MARGIN_LEFT); - ofs.y = int((get_size().height - get_icon_size().height) / 2); + ofs.y = int((get_size().height - get_icon_size().height) / 2) + get_constant("check_vadjust"); if (is_pressed()) on->draw(ci, ofs); diff --git a/scene/gui/check_button.cpp b/scene/gui/check_button.cpp index 35e3119473..f47547f2cc 100644 --- a/scene/gui/check_button.cpp +++ b/scene/gui/check_button.cpp @@ -34,13 +34,15 @@ #include "servers/visual_server.h" Size2 CheckButton::get_icon_size() const { - Ref<Texture> on = Control::get_icon("on"); - Ref<Texture> off = Control::get_icon("off"); + + Ref<Texture> on = Control::get_icon(is_disabled() ? "on_disabled" : "on"); + Ref<Texture> off = Control::get_icon(is_disabled() ? "off_disabled" : "off"); Size2 tex_size = Size2(0, 0); if (!on.is_null()) tex_size = Size2(on->get_width(), on->get_height()); if (!off.is_null()) tex_size = Size2(MAX(tex_size.width, off->get_width()), MAX(tex_size.height, off->get_height())); + return tex_size; } @@ -49,9 +51,8 @@ Size2 CheckButton::get_minimum_size() const { Size2 minsize = Button::get_minimum_size(); Size2 tex_size = get_icon_size(); minsize.width += tex_size.width; - if (get_text().length() > 0) { + if (get_text().length() > 0) minsize.width += get_constant("hseparation"); - } Ref<StyleBox> sb = get_stylebox("normal"); minsize.height = MAX(minsize.height, tex_size.height + sb->get_margin(MARGIN_TOP) + sb->get_margin(MARGIN_BOTTOM)); @@ -67,15 +68,15 @@ void CheckButton::_notification(int p_what) { RID ci = get_canvas_item(); - Ref<Texture> on = Control::get_icon("on"); - Ref<Texture> off = Control::get_icon("off"); + Ref<Texture> on = Control::get_icon(is_disabled() ? "on_disabled" : "on"); + Ref<Texture> off = Control::get_icon(is_disabled() ? "off_disabled" : "off"); Ref<StyleBox> sb = get_stylebox("normal"); Vector2 ofs; Size2 tex_size = get_icon_size(); ofs.x = get_size().width - (tex_size.width + sb->get_margin(MARGIN_RIGHT)); - ofs.y = (get_size().height - tex_size.height) / 2; + ofs.y = (get_size().height - tex_size.height) / 2 + get_constant("check_vadjust"); if (is_pressed()) on->draw(ci, ofs); diff --git a/scene/gui/color_picker.cpp b/scene/gui/color_picker.cpp index 6ed465562e..1d529f4e72 100644 --- a/scene/gui/color_picker.cpp +++ b/scene/gui/color_picker.cpp @@ -35,10 +35,9 @@ #include "core/os/os.h" #ifdef TOOLS_ENABLED +#include "editor_scale.h" #include "editor_settings.h" #endif - -#include "scene/gui/separator.h" #include "scene/main/viewport.h" void ColorPicker::_notification(int p_what) { @@ -81,11 +80,8 @@ void ColorPicker::_notification(int p_what) { } break; case MainLoop::NOTIFICATION_WM_QUIT_REQUEST: { - if (screen != NULL) { - if (screen->is_visible()) { - screen->hide(); - } - } + if (screen != NULL && screen->is_visible()) + screen->hide(); } break; } } @@ -97,6 +93,28 @@ void ColorPicker::set_focus_on_line_edit() { void ColorPicker::_update_controls() { + const char *rgb[3] = { "R", "G", "B" }; + const char *hsv[3] = { "H", "S", "V" }; + + if (hsv_mode_enabled) { + for (int i = 0; i < 3; i++) + labels[i]->set_text(hsv[i]); + } else { + for (int i = 0; i < 3; i++) + labels[i]->set_text(rgb[i]); + } + + if (hsv_mode_enabled) { + set_raw_mode(false); + btn_raw->set_disabled(true); + } else if (raw_mode_enabled) { + set_hsv_mode(false); + btn_hsv->set_disabled(true); + } else { + btn_raw->set_disabled(false); + btn_hsv->set_disabled(false); + } + if (edit_alpha) { values[3]->show(); scroll[3]->show(); @@ -108,7 +126,7 @@ void ColorPicker::_update_controls() { } } -void ColorPicker::set_pick_color(const Color &p_color) { +void ColorPicker::_set_pick_color(const Color &p_color, bool p_update_sliders) { color = p_color; if (color != last_hsv) { @@ -121,7 +139,12 @@ void ColorPicker::set_pick_color(const Color &p_color) { if (!is_inside_tree()) return; - _update_color(); + _update_color(p_update_sliders); +} + +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_edit_alpha(bool p_show) { @@ -146,17 +169,24 @@ void ColorPicker::_value_changed(double) { if (updating) return; - for (int i = 0; i < 4; i++) { - color.components[i] = scroll[i]->get_value() / (raw_mode_enabled ? 1.0 : 255.0); + if (hsv_mode_enabled) { + color.set_hsv(scroll[0]->get_value() / 360.0, + scroll[1]->get_value() / 100.0, + scroll[2]->get_value() / 100.0, + scroll[3]->get_value() / 255.0); + } else { + for (int i = 0; i < 4; i++) { + color.components[i] = scroll[i]->get_value() / (raw_mode_enabled ? 1.0 : 255.0); + } } - set_pick_color(color); + _set_pick_color(color, false); emit_signal("color_changed", color); } void ColorPicker::_html_entered(const String &p_html) { - if (updating) + if (updating || text_is_constructor || !c_text->is_visible()) return; float last_alpha = color.a; @@ -171,22 +201,40 @@ void ColorPicker::_html_entered(const String &p_html) { emit_signal("color_changed", color); } -void ColorPicker::_update_color() { +void ColorPicker::_update_color(bool p_update_sliders) { updating = true; - for (int i = 0; i < 4; i++) { - if (raw_mode_enabled) { - scroll[i]->set_step(0.01); - scroll[i]->set_max(100); - if (i == 3) - scroll[i]->set_max(1); - scroll[i]->set_value(color.components[i]); + if (p_update_sliders) { + + if (hsv_mode_enabled) { + for (int i = 0; i < 4; i++) { + scroll[i]->set_step(1.0); + } + + scroll[0]->set_max(359); + scroll[0]->set_value(h * 360.0); + scroll[1]->set_max(100); + scroll[1]->set_value(s * 100.0); + scroll[2]->set_max(100); + scroll[2]->set_value(v * 100.0); + scroll[3]->set_max(255); + scroll[3]->set_value(color.components[3] * 255.0); } else { - scroll[i]->set_step(1); - const float byte_value = color.components[i] * 255.0; - scroll[i]->set_max(next_power_of_2(MAX(255, byte_value)) - 1); - scroll[i]->set_value(byte_value); + for (int i = 0; i < 4; i++) { + if (raw_mode_enabled) { + scroll[i]->set_step(0.01); + scroll[i]->set_max(100); + if (i == 3) + scroll[i]->set_max(1); + scroll[i]->set_value(color.components[i]); + } else { + scroll[i]->set_step(1); + const float byte_value = color.components[i] * 255.0; + scroll[i]->set_max(next_power_of_2(MAX(255, byte_value)) - 1); + scroll[i]->set_value(byte_value); + } + } } } @@ -199,27 +247,34 @@ void ColorPicker::_update_color() { } void ColorPicker::_update_presets() { + presets_per_row = 10; Size2 size = bt_add_preset->get_size(); - Size2 preset_size = Size2(size.width * presets.size(), size.height); + Size2 preset_size = Size2(MIN(size.width * presets.size(), presets_per_row * size.width), size.height * (Math::ceil((float)presets.size() / presets_per_row))); preset->set_custom_minimum_size(preset_size); - - preset->draw_texture_rect(get_icon("preset_bg", "ColorPicker"), Rect2(Point2(), preset_size), true); + preset_container->set_custom_minimum_size(preset_size); + preset->draw_rect(Rect2(Point2(), preset_size), Color(1, 1, 1, 0)); for (int i = 0; i < presets.size(); i++) { - preset->draw_rect(Rect2(Point2(size.width * i, 0), size), presets[i]); + int x = (i % presets_per_row) * size.width; + int y = (Math::floor((float)i / presets_per_row)) * size.height; + preset->draw_rect(Rect2(Point2(x, y), size), presets[i]); } + _notification(NOTIFICATION_VISIBILITY_CHANGED); } void ColorPicker::_text_type_toggled() { - if (!Engine::get_singleton()->is_editor_hint()) - return; + text_is_constructor = !text_is_constructor; if (text_is_constructor) { text_type->set_text(""); text_type->set_icon(get_icon("Script", "EditorIcons")); + + c_text->set_editable(false); } else { text_type->set_text("#"); text_type->set_icon(NULL); + + c_text->set_editable(true); } _update_color(); } @@ -237,8 +292,6 @@ void ColorPicker::add_preset(const Color &p_color) { presets.push_back(p_color); } preset->update(); - if (presets.size() == 10) - bt_add_preset->hide(); #ifdef TOOLS_ENABLED if (Engine::get_singleton()->is_editor_hint()) { @@ -273,13 +326,33 @@ PoolColorArray ColorPicker::get_presets() const { return arr; } +void ColorPicker::set_hsv_mode(bool p_enabled) { + + if (hsv_mode_enabled == p_enabled || raw_mode_enabled) + return; + hsv_mode_enabled = p_enabled; + if (btn_hsv->is_pressed() != p_enabled) + btn_hsv->set_pressed(p_enabled); + + if (!is_inside_tree()) + return; + + _update_controls(); + _update_color(); +} + +bool ColorPicker::is_hsv_mode() const { + + return hsv_mode_enabled; +} + void ColorPicker::set_raw_mode(bool p_enabled) { - if (raw_mode_enabled == p_enabled) + if (raw_mode_enabled == p_enabled || hsv_mode_enabled) return; raw_mode_enabled = p_enabled; - if (btn_mode->is_pressed() != p_enabled) - btn_mode->set_pressed(p_enabled); + if (btn_raw->is_pressed() != p_enabled) + btn_raw->set_pressed(p_enabled); if (!is_inside_tree()) return; @@ -304,19 +377,21 @@ bool ColorPicker::is_deferred_mode() const { void ColorPicker::_update_text_value() { bool visible = true; if (text_is_constructor) { - String t = "Color(" + String::num(color.r) + "," + String::num(color.g) + "," + String::num(color.b); + String t = "Color(" + String::num(color.r) + ", " + String::num(color.g) + ", " + String::num(color.b); if (edit_alpha && color.a < 1) - t += ("," + String::num(color.a) + ")"); + t += ", " + String::num(color.a) + ")"; else t += ")"; c_text->set_text(t); - } else { - if (color.r > 1 || color.g > 1 || color.b > 1 || color.r < 0 || color.g < 0 || color.b < 0) { - visible = false; - } else { - c_text->set_text(color.to_html(edit_alpha && color.a < 1)); - } } + + if (color.r > 1 || color.g > 1 || color.b > 1 || color.r < 0 || color.g < 0 || color.b < 0) { + visible = false; + } else if (!text_is_constructor) { + c_text->set_text(color.to_html(edit_alpha && color.a < 1)); + } + + text_type->set_visible(visible); c_text->set_visible(visible); } @@ -460,14 +535,20 @@ void ColorPicker::_preset_input(const Ref<InputEvent> &p_event) { Ref<InputEventMouseButton> bev = p_event; if (bev.is_valid()) { - + int index = 0; if (bev->is_pressed() && bev->get_button_index() == BUTTON_LEFT) { - int index = bev->get_position().x / (preset->get_size().x / presets.size()); + 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; + if (bev->get_position().x > x && bev->get_position().x < x + preset->get_size().x && bev->get_position().y > y && bev->get_position().y < y + preset->get_size().y) { + index = i; + } + } set_pick_color(presets[index]); _update_color(); emit_signal("color_changed", color); - } else if (bev->is_pressed() && bev->get_button_index() == BUTTON_RIGHT) { - int index = bev->get_position().x / (preset->get_size().x / presets.size()); + } else if (bev->is_pressed() && bev->get_button_index() == BUTTON_RIGHT && presets_enabled) { + index = bev->get_position().x / (preset->get_size().x / presets.size()); Color clicked_preset = presets[index]; erase_preset(clicked_preset); emit_signal("preset_removed", clicked_preset); @@ -494,21 +575,17 @@ void ColorPicker::_preset_input(const Ref<InputEvent> &p_event) { void ColorPicker::_screen_input(const Ref<InputEvent> &p_event) { Ref<InputEventMouseButton> bev = p_event; - - if (bev.is_valid()) { - - if (bev->get_button_index() == BUTTON_LEFT && !bev->is_pressed()) { - emit_signal("color_changed", color); - screen->hide(); - } + if (bev.is_valid() && bev->get_button_index() == BUTTON_LEFT && !bev->is_pressed()) { + emit_signal("color_changed", color); + screen->hide(); } Ref<InputEventMouseMotion> mev = p_event; - if (mev.is_valid()) { Viewport *r = get_tree()->get_root(); if (!r->get_visible_rect().has_point(Point2(mev->get_global_position().x, mev->get_global_position().y))) return; + Ref<Image> img = r->get_texture()->get_data(); if (img.is_valid() && !img->empty()) { img->lock(); @@ -534,6 +611,8 @@ void ColorPicker::_screen_pick_pressed() { screen->set_anchors_and_margins_preset(Control::PRESET_WIDE); screen->set_default_cursor_shape(CURSOR_POINTING_HAND); screen->connect("gui_input", this, "_screen_input"); + // It immediately toggles off in the first press otherwise. + screen->call_deferred("connect", "hide", btn_pick, "set_pressed", varray(false)); } screen->raise(); screen->show_modal(); @@ -564,16 +643,47 @@ void ColorPicker::_html_focus_exit() { _focus_exit(); } +void ColorPicker::set_presets_enabled(bool p_enabled) { + presets_enabled = p_enabled; + if (!p_enabled) { + bt_add_preset->set_disabled(true); + bt_add_preset->set_focus_mode(FOCUS_NONE); + } else { + bt_add_preset->set_disabled(false); + bt_add_preset->set_focus_mode(FOCUS_ALL); + } +} + +bool ColorPicker::are_presets_enabled() const { + return presets_enabled; +} + +void ColorPicker::set_presets_visible(bool p_visible) { + presets_visible = p_visible; + preset_separator->set_visible(p_visible); + preset_container->set_visible(p_visible); +} + +bool ColorPicker::are_presets_visible() const { + return presets_visible; +} + void ColorPicker::_bind_methods() { ClassDB::bind_method(D_METHOD("set_pick_color", "color"), &ColorPicker::set_pick_color); ClassDB::bind_method(D_METHOD("get_pick_color"), &ColorPicker::get_pick_color); + ClassDB::bind_method(D_METHOD("set_hsv_mode", "mode"), &ColorPicker::set_hsv_mode); + ClassDB::bind_method(D_METHOD("is_hsv_mode"), &ColorPicker::is_hsv_mode); ClassDB::bind_method(D_METHOD("set_raw_mode", "mode"), &ColorPicker::set_raw_mode); ClassDB::bind_method(D_METHOD("is_raw_mode"), &ColorPicker::is_raw_mode); ClassDB::bind_method(D_METHOD("set_deferred_mode", "mode"), &ColorPicker::set_deferred_mode); ClassDB::bind_method(D_METHOD("is_deferred_mode"), &ColorPicker::is_deferred_mode); ClassDB::bind_method(D_METHOD("set_edit_alpha", "show"), &ColorPicker::set_edit_alpha); ClassDB::bind_method(D_METHOD("is_editing_alpha"), &ColorPicker::is_editing_alpha); + ClassDB::bind_method(D_METHOD("set_presets_enabled", "enabled"), &ColorPicker::set_presets_enabled); + ClassDB::bind_method(D_METHOD("are_presets_enabled"), &ColorPicker::are_presets_enabled); + ClassDB::bind_method(D_METHOD("set_presets_visible", "visible"), &ColorPicker::set_presets_visible); + ClassDB::bind_method(D_METHOD("are_presets_visible"), &ColorPicker::are_presets_visible); 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); @@ -595,8 +705,11 @@ void ColorPicker::_bind_methods() { 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::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"))); @@ -609,28 +722,34 @@ ColorPicker::ColorPicker() : updating = true; edit_alpha = true; text_is_constructor = false; + hsv_mode_enabled = false; raw_mode_enabled = false; deferred_mode_enabled = false; changing_color = false; + presets_enabled = true; + presets_visible = true; screen = NULL; HBoxContainer *hb_smpl = memnew(HBoxContainer); - btn_pick = memnew(ToolButton); - btn_pick->connect("pressed", this, "_screen_pick_pressed"); + add_child(hb_smpl); sample = memnew(TextureRect); + hb_smpl->add_child(sample); sample->set_h_size_flags(SIZE_EXPAND_FILL); sample->connect("draw", this, "_sample_draw"); - hb_smpl->add_child(sample); + btn_pick = memnew(ToolButton); hb_smpl->add_child(btn_pick); - add_child(hb_smpl); + btn_pick->set_toggle_mode(true); + btn_pick->set_tooltip(TTR("Pick a color from the screen.")); + btn_pick->connect("pressed", this, "_screen_pick_pressed"); HBoxContainer *hb_edit = memnew(HBoxContainer); + 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", this, "_uv_input"); uv_edit->set_mouse_filter(MOUSE_FILTER_PASS); uv_edit->set_h_size_flags(SIZE_EXPAND_FILL); @@ -638,19 +757,14 @@ ColorPicker::ColorPicker() : uv_edit->set_custom_minimum_size(Size2(get_constant("sv_width"), get_constant("sv_height"))); uv_edit->connect("draw", this, "_hsv_draw", make_binds(0, uv_edit)); - add_child(hb_edit); - w_edit = memnew(Control); + hb_edit->add_child(w_edit); w_edit->set_custom_minimum_size(Size2(get_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", this, "_w_input"); w_edit->connect("draw", this, "_hsv_draw", make_binds(1, w_edit)); - hb_edit->add_child(uv_edit); - hb_edit->add_child(memnew(VSeparator)); - hb_edit->add_child(w_edit); - VBoxContainer *vbl = memnew(VBoxContainer); add_child(vbl); @@ -659,13 +773,12 @@ ColorPicker::ColorPicker() : VBoxContainer *vbr = memnew(VBoxContainer); add_child(vbr); vbr->set_h_size_flags(SIZE_EXPAND_FILL); - const char *lt[4] = { "R", "G", "B", "A" }; for (int i = 0; i < 4; i++) { HBoxContainer *hbc = memnew(HBoxContainer); - labels[i] = memnew(Label(lt[i])); + labels[i] = memnew(Label()); labels[i]->set_custom_minimum_size(Size2(get_constant("label_width"), 0)); labels[i]->set_v_size_flags(SIZE_SHRINK_CENTER); hbc->add_child(labels[i]); @@ -689,45 +802,68 @@ ColorPicker::ColorPicker() : vbr->add_child(hbc); } + labels[3]->set_text("A"); HBoxContainer *hhb = memnew(HBoxContainer); - - btn_mode = memnew(CheckButton); - btn_mode->set_text(TTR("Raw Mode")); - btn_mode->connect("toggled", this, "set_raw_mode"); - hhb->add_child(btn_mode); vbr->add_child(hhb); + + btn_hsv = memnew(CheckButton); + hhb->add_child(btn_hsv); + btn_hsv->set_text(TTR("HSV")); + btn_hsv->connect("toggled", this, "set_hsv_mode"); + + btn_raw = memnew(CheckButton); + hhb->add_child(btn_raw); + btn_raw->set_text(TTR("Raw")); + btn_raw->connect("toggled", this, "set_raw_mode"); + text_type = memnew(Button); - text_type->set_flat(true); - text_type->connect("pressed", this, "_text_type_toggled"); hhb->add_child(text_type); + text_type->set_text("#"); + text_type->set_tooltip(TTR("Switch between hexadecimal and code values.")); + if (Engine::get_singleton()->is_editor_hint()) { + +#ifdef TOOLS_ENABLED + text_type->set_custom_minimum_size(Size2(28 * EDSCALE, 0)); // Adjust for the width of the "Script" icon. +#endif + text_type->connect("pressed", this, "_text_type_toggled"); + } else { + + text_type->set_flat(true); + 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", this, "_html_entered"); c_text->connect("focus_entered", this, "_focus_enter"); c_text->connect("focus_exited", this, "_html_focus_exit"); - text_type->set_text("#"); - c_text->set_h_size_flags(SIZE_EXPAND_FILL); - _update_controls(); updating = false; set_pick_color(Color(1, 1, 1)); - HBoxContainer *bbc = memnew(HBoxContainer); - add_child(bbc); + 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); - bbc->add_child(preset); + preset_container->add_child(preset); preset->connect("gui_input", this, "_preset_input"); preset->connect("draw", this, "_update_presets"); + preset_container2 = memnew(HBoxContainer); + preset_container2->set_h_size_flags(SIZE_EXPAND_FILL); + add_child(preset_container2); bt_add_preset = memnew(Button); - bt_add_preset->set_tooltip(TTR("Add current color as a preset")); + preset_container2->add_child(bt_add_preset); + bt_add_preset->set_tooltip(TTR("Add current color as a preset.")); bt_add_preset->connect("pressed", this, "_add_preset_pressed"); - bbc->add_child(bt_add_preset); } ///////////////// @@ -747,23 +883,33 @@ void ColorPickerButton::_modal_closed() { void ColorPickerButton::pressed() { _update_picker(); - popup->set_position(get_global_position() - picker->get_combined_minimum_size()); + popup->set_position(get_global_position() - picker->get_combined_minimum_size() * get_global_transform().get_scale()); + popup->set_scale(get_global_transform().get_scale()); popup->popup(); picker->set_focus_on_line_edit(); } void ColorPickerButton::_notification(int p_what) { - if (p_what == NOTIFICATION_DRAW) { + switch (p_what) { + case NOTIFICATION_DRAW: { + + Ref<StyleBox> normal = get_stylebox("normal"); + Rect2 r = Rect2(normal->get_offset(), get_size() - normal->get_minimum_size()); + draw_texture_rect(Control::get_icon("bg", "ColorPickerButton"), r, true); + draw_rect(r, color); + } break; + case MainLoop::NOTIFICATION_WM_QUIT_REQUEST: { - Ref<StyleBox> normal = get_stylebox("normal"); - Rect2 r = Rect2(normal->get_offset(), get_size() - normal->get_minimum_size()); - draw_texture_rect(Control::get_icon("bg", "ColorPickerButton"), r, true); - draw_rect(r, color); + if (popup) + popup->hide(); + } break; } - if (p_what == MainLoop::NOTIFICATION_WM_QUIT_REQUEST && popup) { - popup->hide(); + if (p_what == NOTIFICATION_VISIBILITY_CHANGED) { + if (popup && !is_visible_in_tree()) { + popup->hide(); + } } } @@ -814,6 +960,8 @@ void ColorPickerButton::_update_picker() { add_child(popup); picker->connect("color_changed", this, "_color_changed"); popup->connect("modal_closed", this, "_modal_closed"); + popup->connect("about_to_show", this, "set_pressed", varray(true)); + popup->connect("popup_hide", this, "set_pressed", varray(false)); picker->set_pick_color(color); picker->set_edit_alpha(edit_alpha); } @@ -838,10 +986,12 @@ void ColorPickerButton::_bind_methods() { ColorPickerButton::ColorPickerButton() { - //Initialization is now done deferred - //this improves performance in the inspector as the color picker - //can be expensive to initialize + // Initialization is now done deferred, + // this improves performance in the inspector as the color picker + // can be expensive to initialize. picker = NULL; popup = NULL; edit_alpha = true; + + set_toggle_mode(true); } diff --git a/scene/gui/color_picker.h b/scene/gui/color_picker.h index b78844839a..167f7b33b3 100644 --- a/scene/gui/color_picker.h +++ b/scene/gui/color_picker.h @@ -37,6 +37,7 @@ #include "scene/gui/label.h" #include "scene/gui/line_edit.h" #include "scene/gui/popup.h" +#include "scene/gui/separator.h" #include "scene/gui/slider.h" #include "scene/gui/spin_box.h" #include "scene/gui/texture_rect.h" @@ -52,10 +53,14 @@ private: Control *w_edit; TextureRect *sample; TextureRect *preset; + HBoxContainer *preset_container; + HBoxContainer *preset_container2; + HSeparator *preset_separator; Button *bt_add_preset; List<Color> presets; ToolButton *btn_pick; - CheckButton *btn_mode; + CheckButton *btn_hsv; + CheckButton *btn_raw; HSlider *scroll[4]; SpinBox *values[4]; Label *labels[4]; @@ -64,19 +69,23 @@ private: bool edit_alpha; Size2i ms; bool text_is_constructor; + int presets_per_row; Color color; bool raw_mode_enabled; + bool hsv_mode_enabled; bool deferred_mode_enabled; bool updating; bool changing_color; + bool presets_enabled; + bool presets_visible; float h, s, v; Color last_hsv; void _html_entered(const String &p_html); void _value_changed(double); void _update_controls(); - void _update_color(); + void _update_color(bool p_update_sliders = true); void _update_presets(); void _update_text_value(); void _text_type_toggled(); @@ -101,6 +110,7 @@ public: 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; @@ -108,12 +118,21 @@ public: void erase_preset(const Color &p_color); PoolColorArray get_presets() const; + void set_hsv_mode(bool p_enabled); + bool is_hsv_mode() const; + void set_raw_mode(bool p_enabled); bool is_raw_mode() const; void set_deferred_mode(bool p_enabled); bool is_deferred_mode() const; + void set_presets_enabled(bool p_enabled); + bool are_presets_enabled() const; + + void set_presets_visible(bool p_visible); + bool are_presets_visible() const; + void set_focus_on_line_edit(); ColorPicker(); diff --git a/scene/gui/color_rect.h b/scene/gui/color_rect.h index d7f9ef275d..7a7bbe1029 100644 --- a/scene/gui/color_rect.h +++ b/scene/gui/color_rect.h @@ -34,7 +34,7 @@ #include "scene/gui/control.h" class ColorRect : public Control { - GDCLASS(ColorRect, Control) + GDCLASS(ColorRect, Control); Color color; diff --git a/scene/gui/container.cpp b/scene/gui/container.cpp index 2579321773..449076f863 100644 --- a/scene/gui/container.cpp +++ b/scene/gui/container.cpp @@ -169,6 +169,19 @@ void Container::_notification(int p_what) { } } +String Container::get_configuration_warning() const { + + String warning = Control::get_configuration_warning(); + + if (get_class() == "Container" && get_script().is_null()) { + if (warning != String()) { + 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."); + } + return warning; +} + void Container::_bind_methods() { ClassDB::bind_method(D_METHOD("_sort_children"), &Container::_sort_children); diff --git a/scene/gui/container.h b/scene/gui/container.h index 0b014137f4..80d3f6ee5d 100644 --- a/scene/gui/container.h +++ b/scene/gui/container.h @@ -57,6 +57,8 @@ public: void fit_child_in_rect(Control *p_child, const Rect2 &p_rect); + virtual String get_configuration_warning() const; + Container(); }; diff --git a/scene/gui/control.cpp b/scene/gui/control.cpp index 513befb8f3..034e9266f6 100644 --- a/scene/gui/control.cpp +++ b/scene/gui/control.cpp @@ -29,22 +29,23 @@ /*************************************************************************/ #include "control.h" -#include "core/project_settings.h" -#include "scene/main/canvas_layer.h" -#include "scene/main/viewport.h" -#include "servers/visual_server.h" #include "core/message_queue.h" #include "core/os/keyboard.h" #include "core/os/os.h" #include "core/print_string.h" +#include "core/project_settings.h" #include "scene/gui/label.h" #include "scene/gui/panel.h" +#include "scene/main/canvas_layer.h" +#include "scene/main/viewport.h" #include "scene/scene_string_names.h" +#include "servers/visual_server.h" + #ifdef TOOLS_ENABLED #include "editor/editor_settings.h" +#include "editor/plugins/canvas_item_editor_plugin.h" #endif -#include <stdio.h> Dictionary Control::_edit_get_state() const { @@ -66,6 +67,7 @@ Dictionary Control::_edit_get_state() const { s["margins"] = margins; return s; } + void Control::_edit_set_state(const Dictionary &p_state) { Dictionary state = p_state; @@ -87,7 +89,12 @@ void Control::_edit_set_state(const Dictionary &p_state) { } void Control::_edit_set_position(const Point2 &p_position) { +#ifdef TOOLS_ENABLED + set_position(p_position, CanvasItemEditor::get_singleton()->is_anchors_mode_enabled()); +#else + // Unlikely to happen. TODO: enclose all _edit_ functions into TOOLS_ENABLED set_position(p_position); +#endif }; Point2 Control::_edit_get_position() const { @@ -103,8 +110,14 @@ Size2 Control::_edit_get_scale() const { } void Control::_edit_set_rect(const Rect2 &p_edit_rect) { +#ifdef TOOLS_ENABLED + set_position((get_position() + get_transform().basis_xform(p_edit_rect.position)).snapped(Vector2(1, 1)), CanvasItemEditor::get_singleton()->is_anchors_mode_enabled()); + set_size(p_edit_rect.size.snapped(Vector2(1, 1)), CanvasItemEditor::get_singleton()->is_anchors_mode_enabled()); +#else + // Unlikely to happen. TODO: enclose all _edit_ functions into TOOLS_ENABLED set_position((get_position() + get_transform().basis_xform(p_edit_rect.position)).snapped(Vector2(1, 1))); set_size(p_edit_rect.size.snapped(Vector2(1, 1))); +#endif } Rect2 Control::_edit_get_rect() const { @@ -206,65 +219,62 @@ bool Control::_set(const StringName &p_name, const Variant &p_value) { if (name.begins_with("custom_icons/")) { String dname = name.get_slicec('/', 1); + if (data.icon_override.has(dname)) { + data.icon_override[dname]->disconnect("changed", this, "_override_changed"); + } data.icon_override.erase(dname); notification(NOTIFICATION_THEME_CHANGED); - update(); } else if (name.begins_with("custom_shaders/")) { String dname = name.get_slicec('/', 1); + if (data.shader_override.has(dname)) { + data.shader_override[dname]->disconnect("changed", this, "_override_changed"); + } data.shader_override.erase(dname); notification(NOTIFICATION_THEME_CHANGED); - update(); } else if (name.begins_with("custom_styles/")) { String dname = name.get_slicec('/', 1); + if (data.style_override.has(dname)) { + data.style_override[dname]->disconnect("changed", this, "_override_changed"); + } data.style_override.erase(dname); notification(NOTIFICATION_THEME_CHANGED); - update(); } else if (name.begins_with("custom_fonts/")) { String dname = name.get_slicec('/', 1); if (data.font_override.has(dname)) { - _unref_font(data.font_override[dname]); + data.font_override[dname]->disconnect("changed", this, "_override_changed"); } data.font_override.erase(dname); notification(NOTIFICATION_THEME_CHANGED); - update(); } else if (name.begins_with("custom_colors/")) { String dname = name.get_slicec('/', 1); data.color_override.erase(dname); notification(NOTIFICATION_THEME_CHANGED); - update(); } else if (name.begins_with("custom_constants/")) { String dname = name.get_slicec('/', 1); data.constant_override.erase(dname); notification(NOTIFICATION_THEME_CHANGED); - update(); } else return false; } else { if (name.begins_with("custom_icons/")) { String dname = name.get_slicec('/', 1); - notification(NOTIFICATION_THEME_CHANGED); add_icon_override(dname, p_value); } else if (name.begins_with("custom_shaders/")) { String dname = name.get_slicec('/', 1); add_shader_override(dname, p_value); - notification(NOTIFICATION_THEME_CHANGED); } else if (name.begins_with("custom_styles/")) { String dname = name.get_slicec('/', 1); add_style_override(dname, p_value); - notification(NOTIFICATION_THEME_CHANGED); } else if (name.begins_with("custom_fonts/")) { String dname = name.get_slicec('/', 1); add_font_override(dname, p_value); - notification(NOTIFICATION_THEME_CHANGED); } else if (name.begins_with("custom_colors/")) { String dname = name.get_slicec('/', 1); add_color_override(dname, p_value); - notification(NOTIFICATION_THEME_CHANGED); } else if (name.begins_with("custom_constants/")) { String dname = name.get_slicec('/', 1); add_constant_override(dname, p_value); - notification(NOTIFICATION_THEME_CHANGED); } else return false; } @@ -840,6 +850,12 @@ Ref<Texture> Control::get_icon(const StringName &p_name, const StringName &p_typ theme_owner = NULL; } + if (Theme::get_project_default().is_valid()) { + if (Theme::get_project_default()->has_icon(p_name, type)) { + return Theme::get_project_default()->get_icon(p_name, type); + } + } + return Theme::get_default()->get_icon(p_name, type); } @@ -876,6 +892,12 @@ Ref<Shader> Control::get_shader(const StringName &p_name, const StringName &p_ty theme_owner = NULL; } + if (Theme::get_project_default().is_valid()) { + if (Theme::get_project_default()->has_shader(p_name, type)) { + return Theme::get_project_default()->get_shader(p_name, type); + } + } + return Theme::get_default()->get_shader(p_name, type); } @@ -915,6 +937,9 @@ Ref<StyleBox> Control::get_stylebox(const StringName &p_name, const StringName & } while (class_name != StringName()) { + if (Theme::get_project_default().is_valid() && Theme::get_project_default()->has_stylebox(p_name, type)) + return Theme::get_project_default()->get_stylebox(p_name, type); + if (Theme::get_default()->has_stylebox(p_name, class_name)) return Theme::get_default()->get_stylebox(p_name, class_name); @@ -991,6 +1016,11 @@ Color Control::get_color(const StringName &p_name, const StringName &p_type) con theme_owner = NULL; } + if (Theme::get_project_default().is_valid()) { + if (Theme::get_project_default()->has_color(p_name, type)) { + return Theme::get_project_default()->get_color(p_name, type); + } + } return Theme::get_default()->get_color(p_name, type); } @@ -1026,61 +1056,48 @@ int Control::get_constant(const StringName &p_name, const StringName &p_type) co theme_owner = NULL; } + if (Theme::get_project_default().is_valid()) { + if (Theme::get_project_default()->has_constant(p_name, type)) { + return Theme::get_project_default()->get_constant(p_name, type); + } + } return Theme::get_default()->get_constant(p_name, type); } bool Control::has_icon_override(const StringName &p_name) const { const Ref<Texture> *tex = data.icon_override.getptr(p_name); - if (tex) - return true; - else - return false; + return tex != NULL; } bool Control::has_shader_override(const StringName &p_name) const { const Ref<Shader> *sdr = data.shader_override.getptr(p_name); - if (sdr) - return true; - else - return false; + return sdr != NULL; } bool Control::has_stylebox_override(const StringName &p_name) const { const Ref<StyleBox> *style = data.style_override.getptr(p_name); - if (style) - return true; - else - return false; + return style != NULL; } bool Control::has_font_override(const StringName &p_name) const { const Ref<Font> *font = data.font_override.getptr(p_name); - if (font) - return true; - else - return false; + return font != NULL; } bool Control::has_color_override(const StringName &p_name) const { const Color *color = data.color_override.getptr(p_name); - if (color) - return true; - else - return false; + return color != NULL; } bool Control::has_constant_override(const StringName &p_name) const { const int *constant = data.constant_override.getptr(p_name); - if (constant) - return true; - else - return false; + return constant != NULL; } bool Control::has_icon(const StringName &p_name, const StringName &p_type) const { @@ -1114,6 +1131,11 @@ bool Control::has_icon(const StringName &p_name, const StringName &p_type) const theme_owner = NULL; } + if (Theme::get_project_default().is_valid()) { + if (Theme::get_project_default()->has_color(p_name, type)) { + return true; + } + } return Theme::get_default()->has_icon(p_name, type); } @@ -1148,6 +1170,11 @@ bool Control::has_shader(const StringName &p_name, const StringName &p_type) con theme_owner = NULL; } + if (Theme::get_project_default().is_valid()) { + if (Theme::get_project_default()->has_shader(p_name, type)) { + return true; + } + } return Theme::get_default()->has_shader(p_name, type); } bool Control::has_stylebox(const StringName &p_name, const StringName &p_type) const { @@ -1181,6 +1208,11 @@ bool Control::has_stylebox(const StringName &p_name, const StringName &p_type) c theme_owner = NULL; } + if (Theme::get_project_default().is_valid()) { + if (Theme::get_project_default()->has_stylebox(p_name, type)) { + return true; + } + } return Theme::get_default()->has_stylebox(p_name, type); } bool Control::has_font(const StringName &p_name, const StringName &p_type) const { @@ -1214,6 +1246,11 @@ bool Control::has_font(const StringName &p_name, const StringName &p_type) const theme_owner = NULL; } + if (Theme::get_project_default().is_valid()) { + if (Theme::get_project_default()->has_font(p_name, type)) { + return true; + } + } return Theme::get_default()->has_font(p_name, type); } @@ -1248,6 +1285,11 @@ bool Control::has_color(const StringName &p_name, const StringName &p_type) cons theme_owner = NULL; } + if (Theme::get_project_default().is_valid()) { + if (Theme::get_project_default()->has_color(p_name, type)) { + return true; + } + } return Theme::get_default()->has_color(p_name, type); } @@ -1282,6 +1324,11 @@ bool Control::has_constant(const StringName &p_name, const StringName &p_type) c theme_owner = NULL; } + if (Theme::get_project_default().is_valid()) { + if (Theme::get_project_default()->has_constant(p_name, type)) { + return true; + } + } return Theme::get_default()->has_constant(p_name, type); } @@ -1369,7 +1416,7 @@ void Control::set_anchor(Margin p_margin, float p_anchor, bool p_keep_margin, bo float previous_margin_pos = data.margin[p_margin] + data.anchor[p_margin] * parent_range; float previous_opposite_margin_pos = data.margin[(p_margin + 2) % 4] + data.anchor[(p_margin + 2) % 4] * parent_range; - data.anchor[p_margin] = CLAMP(p_anchor, 0.0, 1.0); + data.anchor[p_margin] = p_anchor; if (((p_margin == MARGIN_LEFT || p_margin == MARGIN_TOP) && data.anchor[p_margin] > data.anchor[(p_margin + 2) % 4]) || ((p_margin == MARGIN_RIGHT || p_margin == MARGIN_BOTTOM) && data.anchor[p_margin] < data.anchor[(p_margin + 2) % 4])) { @@ -1391,19 +1438,14 @@ void Control::set_anchor(Margin p_margin, float p_anchor, bool p_keep_margin, bo } update(); - _change_notify("anchor"); + _change_notify("anchor_left"); + _change_notify("anchor_right"); + _change_notify("anchor_top"); + _change_notify("anchor_bottom"); } void Control::_set_anchor(Margin p_margin, float p_anchor) { -#ifdef TOOLS_ENABLED - if (is_inside_tree() && Engine::get_singleton()->is_editor_hint()) { - set_anchor(p_margin, p_anchor, EDITOR_DEF("editors/2d/keep_margins_when_changing_anchors", false)); - } else { - set_anchor(p_margin, p_anchor, false); - } -#else - set_anchor(p_margin, p_anchor, false); -#endif + set_anchor(p_margin, p_anchor); } void Control::set_anchor_and_margin(Margin p_margin, float p_anchor, float p_pos, bool p_push_opposite_anchor) { @@ -1412,7 +1454,7 @@ void Control::set_anchor_and_margin(Margin p_margin, float p_anchor, float p_pos set_margin(p_margin, p_pos); } -void Control::set_anchors_preset(LayoutPreset p_preset, bool p_keep_margin) { +void Control::set_anchors_preset(LayoutPreset p_preset, bool p_keep_margins) { //Left switch (p_preset) { case PRESET_TOP_LEFT: @@ -1423,21 +1465,21 @@ void Control::set_anchors_preset(LayoutPreset p_preset, bool p_keep_margin) { case PRESET_LEFT_WIDE: case PRESET_HCENTER_WIDE: case PRESET_WIDE: - set_anchor(MARGIN_LEFT, ANCHOR_BEGIN, p_keep_margin); + set_anchor(MARGIN_LEFT, ANCHOR_BEGIN, p_keep_margins); break; case PRESET_CENTER_TOP: case PRESET_CENTER_BOTTOM: case PRESET_CENTER: case PRESET_VCENTER_WIDE: - set_anchor(MARGIN_LEFT, 0.5, p_keep_margin); + set_anchor(MARGIN_LEFT, 0.5, p_keep_margins); break; case PRESET_TOP_RIGHT: case PRESET_BOTTOM_RIGHT: case PRESET_CENTER_RIGHT: case PRESET_RIGHT_WIDE: - set_anchor(MARGIN_LEFT, ANCHOR_END, p_keep_margin); + set_anchor(MARGIN_LEFT, ANCHOR_END, p_keep_margins); break; } @@ -1451,21 +1493,21 @@ void Control::set_anchors_preset(LayoutPreset p_preset, bool p_keep_margin) { case PRESET_TOP_WIDE: case PRESET_VCENTER_WIDE: case PRESET_WIDE: - set_anchor(MARGIN_TOP, ANCHOR_BEGIN, p_keep_margin); + set_anchor(MARGIN_TOP, ANCHOR_BEGIN, p_keep_margins); break; case PRESET_CENTER_LEFT: case PRESET_CENTER_RIGHT: case PRESET_CENTER: case PRESET_HCENTER_WIDE: - set_anchor(MARGIN_TOP, 0.5, p_keep_margin); + set_anchor(MARGIN_TOP, 0.5, p_keep_margins); break; case PRESET_BOTTOM_LEFT: case PRESET_BOTTOM_RIGHT: case PRESET_CENTER_BOTTOM: case PRESET_BOTTOM_WIDE: - set_anchor(MARGIN_TOP, ANCHOR_END, p_keep_margin); + set_anchor(MARGIN_TOP, ANCHOR_END, p_keep_margins); break; } @@ -1475,14 +1517,14 @@ void Control::set_anchors_preset(LayoutPreset p_preset, bool p_keep_margin) { case PRESET_BOTTOM_LEFT: case PRESET_CENTER_LEFT: case PRESET_LEFT_WIDE: - set_anchor(MARGIN_RIGHT, ANCHOR_BEGIN, p_keep_margin); + set_anchor(MARGIN_RIGHT, ANCHOR_BEGIN, p_keep_margins); break; case PRESET_CENTER_TOP: case PRESET_CENTER_BOTTOM: case PRESET_CENTER: case PRESET_VCENTER_WIDE: - set_anchor(MARGIN_RIGHT, 0.5, p_keep_margin); + set_anchor(MARGIN_RIGHT, 0.5, p_keep_margins); break; case PRESET_TOP_RIGHT: @@ -1493,7 +1535,7 @@ void Control::set_anchors_preset(LayoutPreset p_preset, bool p_keep_margin) { case PRESET_BOTTOM_WIDE: case PRESET_HCENTER_WIDE: case PRESET_WIDE: - set_anchor(MARGIN_RIGHT, ANCHOR_END, p_keep_margin); + set_anchor(MARGIN_RIGHT, ANCHOR_END, p_keep_margins); break; } @@ -1503,14 +1545,14 @@ void Control::set_anchors_preset(LayoutPreset p_preset, bool p_keep_margin) { case PRESET_TOP_RIGHT: case PRESET_CENTER_TOP: case PRESET_TOP_WIDE: - set_anchor(MARGIN_BOTTOM, ANCHOR_BEGIN, p_keep_margin); + set_anchor(MARGIN_BOTTOM, ANCHOR_BEGIN, p_keep_margins); break; case PRESET_CENTER_LEFT: case PRESET_CENTER_RIGHT: case PRESET_CENTER: case PRESET_HCENTER_WIDE: - set_anchor(MARGIN_BOTTOM, 0.5, p_keep_margin); + set_anchor(MARGIN_BOTTOM, 0.5, p_keep_margins); break; case PRESET_BOTTOM_LEFT: @@ -1521,7 +1563,7 @@ void Control::set_anchors_preset(LayoutPreset p_preset, bool p_keep_margin) { case PRESET_BOTTOM_WIDE: case PRESET_VCENTER_WIDE: case PRESET_WIDE: - set_anchor(MARGIN_BOTTOM, ANCHOR_END, p_keep_margin); + set_anchor(MARGIN_BOTTOM, ANCHOR_END, p_keep_margins); break; } } @@ -1714,7 +1756,11 @@ Point2 Control::get_global_position() const { return get_global_transform().get_origin(); } -void Control::set_global_position(const Point2 &p_point) { +void Control::_set_global_position(const Point2 &p_point) { + set_global_position(p_point); +} + +void Control::set_global_position(const Point2 &p_point, bool p_keep_margins) { Transform2D inv; @@ -1723,18 +1769,19 @@ void Control::set_global_position(const Point2 &p_point) { inv = data.parent_canvas_item->get_global_transform().affine_inverse(); } - set_position(inv.xform(p_point)); + set_position(inv.xform(p_point), p_keep_margins); } -Rect2 Control::_compute_child_rect(const float p_anchors[4], const float p_margins[4]) const { +void Control::_compute_anchors(Rect2 p_rect, const float p_margins[4], float (&r_anchors)[4]) { - Rect2 anchorable = get_parent_anchorable_rect(); - Rect2 result = anchorable; - for (int i = 0; i < 4; i++) { - result.grow_margin((Margin)i, p_anchors[i] * anchorable.get_size()[i % 2] + p_margins[i]); - } + 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); - return result; + r_anchors[0] = (p_rect.position.x - p_margins[0]) / parent_rect_size.x; + r_anchors[1] = (p_rect.position.y - p_margins[1]) / parent_rect_size.y; + r_anchors[2] = (p_rect.position.x + p_rect.size.x - p_margins[2]) / parent_rect_size.x; + r_anchors[3] = (p_rect.position.y + p_rect.size.y - p_margins[3]) / parent_rect_size.y; } void Control::_compute_margins(Rect2 p_rect, const float p_anchors[4], float (&r_margins)[4]) { @@ -1746,13 +1793,28 @@ void Control::_compute_margins(Rect2 p_rect, const float p_anchors[4], float (&r r_margins[3] = p_rect.position.y + p_rect.size.y - (p_anchors[3] * parent_rect_size.y); } -void Control::set_position(const Size2 &p_point) { +void Control::_set_position(const Size2 &p_point) { + set_position(p_point); +} - _compute_margins(Rect2(p_point, data.size_cache), data.anchor, data.margin); +void Control::set_position(const Size2 &p_point, bool p_keep_margins) { + if (p_keep_margins) { + _compute_anchors(Rect2(p_point, data.size_cache), data.margin, data.anchor); + _change_notify("anchor_left"); + _change_notify("anchor_right"); + _change_notify("anchor_top"); + _change_notify("anchor_bottom"); + } else { + _compute_margins(Rect2(p_point, data.size_cache), data.anchor, data.margin); + } _size_changed(); } -void Control::set_size(const Size2 &p_size) { +void Control::_set_size(const Size2 &p_size) { + set_size(p_size); +} + +void Control::set_size(const Size2 &p_size, bool p_keep_margins) { Size2 new_size = p_size; Size2 min = get_combined_minimum_size(); @@ -1761,7 +1823,15 @@ void Control::set_size(const Size2 &p_size) { if (new_size.y < min.y) new_size.y = min.y; - _compute_margins(Rect2(data.pos_cache, new_size), data.anchor, data.margin); + if (p_keep_margins) { + _compute_anchors(Rect2(data.pos_cache, new_size), data.margin, data.anchor); + _change_notify("anchor_left"); + _change_notify("anchor_right"); + _change_notify("anchor_top"); + _change_notify("anchor_bottom"); + } else { + _compute_margins(Rect2(data.pos_cache, new_size), data.anchor, data.margin); + } _size_changed(); } @@ -1799,52 +1869,83 @@ Rect2 Control::get_anchorable_rect() const { void Control::add_icon_override(const StringName &p_name, const Ref<Texture> &p_icon) { - ERR_FAIL_COND(p_icon.is_null()); - data.icon_override[p_name] = p_icon; + if (data.icon_override.has(p_name)) { + data.icon_override[p_name]->disconnect("changed", this, "_override_changed"); + } + + // clear if "null" is passed instead of a 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", this, "_override_changed", Vector<Variant>(), CONNECT_REFERENCE_COUNTED); + } + } notification(NOTIFICATION_THEME_CHANGED); - update(); } void Control::add_shader_override(const StringName &p_name, const Ref<Shader> &p_shader) { - ERR_FAIL_COND(p_shader.is_null()); - data.shader_override[p_name] = p_shader; + + if (data.shader_override.has(p_name)) { + data.shader_override[p_name]->disconnect("changed", this, "_override_changed"); + } + + // clear if "null" is passed instead of a shader + if (p_shader.is_null()) { + data.shader_override.erase(p_name); + } else { + data.shader_override[p_name] = p_shader; + if (data.shader_override[p_name].is_valid()) { + data.shader_override[p_name]->connect("changed", this, "_override_changed", Vector<Variant>(), CONNECT_REFERENCE_COUNTED); + } + } notification(NOTIFICATION_THEME_CHANGED); - update(); } void Control::add_style_override(const StringName &p_name, const Ref<StyleBox> &p_style) { - ERR_FAIL_COND(p_style.is_null()); - data.style_override[p_name] = p_style; + if (data.style_override.has(p_name)) { + data.style_override[p_name]->disconnect("changed", this, "_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", this, "_override_changed", Vector<Variant>(), CONNECT_REFERENCE_COUNTED); + } + } notification(NOTIFICATION_THEME_CHANGED); - update(); } void Control::add_font_override(const StringName &p_name, const Ref<Font> &p_font) { - ERR_FAIL_COND(p_font.is_null()); if (data.font_override.has(p_name)) { - _unref_font(data.font_override[p_name]); + data.font_override[p_name]->disconnect("changed", this, "_override_changed"); } - data.font_override[p_name] = p_font; - if (p_font.is_valid()) { - _ref_font(p_font); + // 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", this, "_override_changed", Vector<Variant>(), CONNECT_REFERENCE_COUNTED); + } } - notification(NOTIFICATION_THEME_CHANGED); - update(); } void Control::add_color_override(const StringName &p_name, const Color &p_color) { data.color_override[p_name] = p_color; notification(NOTIFICATION_THEME_CHANGED); - update(); } void Control::add_constant_override(const StringName &p_name, int p_constant) { data.constant_override[p_name] = p_constant; notification(NOTIFICATION_THEME_CHANGED); - update(); } void Control::set_focus_mode(FocusMode p_focus_mode) { @@ -1894,12 +1995,7 @@ Control *Control::find_next_valid_focus() const { Node *n = get_node(data.focus_next); if (n) { from = Object::cast_to<Control>(n); - - if (!from) { - - ERR_EXPLAIN("Next focus node is not a control: " + n->get_name()); - ERR_FAIL_V(NULL); - } + ERR_FAIL_COND_V_MSG(!from, NULL, "Next focus node is not a control: " + n->get_name() + "."); } else { return NULL; } @@ -1922,10 +2018,7 @@ Control *Control::find_next_valid_focus() const { break; } - if (next_child) { - - from = next_child; - } else { + if (!next_child) { next_child = _next_control(from); if (!next_child) { //nothing else.. go up and find either window or subwindow @@ -1992,12 +2085,7 @@ Control *Control::find_prev_valid_focus() const { Node *n = get_node(data.focus_prev); if (n) { from = Object::cast_to<Control>(n); - - if (!from) { - - ERR_EXPLAIN("Previous focus node is not a control: " + n->get_name()); - ERR_FAIL_V(NULL); - } + ERR_FAIL_COND_V_MSG(!from, NULL, "Previous focus node is not a control: " + n->get_name() + "."); } else { return NULL; } @@ -2143,7 +2231,6 @@ void Control::_propagate_theme_changed(CanvasItem *p_at, Control *p_owner, bool c->data.theme_owner = p_owner; } c->notification(NOTIFICATION_THEME_CHANGED); - c->update(); } } @@ -2196,6 +2283,7 @@ Ref<Theme> Control::get_theme() const { void Control::set_tooltip(const String &p_tooltip) { data.tooltip = p_tooltip; + update_configuration_warning(); } String Control::get_tooltip(const Point2 &p_pos) const { @@ -2279,12 +2367,7 @@ Control *Control::_get_focus_neighbour(Margin p_margin, int p_count) { Node *n = get_node(data.focus_neighbour[p_margin]); if (n) { c = Object::cast_to<Control>(n); - - if (!c) { - - ERR_EXPLAIN("Neighbour focus node is not a control: " + n->get_name()); - ERR_FAIL_V(NULL); - } + ERR_FAIL_COND_V_MSG(!c, NULL, "Neighbor focus node is not a control: " + n->get_name() + "."); } else { return NULL; } @@ -2487,6 +2570,7 @@ void Control::set_mouse_filter(MouseFilter p_filter) { ERR_FAIL_INDEX(p_filter, 3); data.mouse_filter = p_filter; + update_configuration_warning(); } Control::MouseFilter Control::get_mouse_filter() const { @@ -2550,32 +2634,10 @@ float Control::get_rotation_degrees() const { return Math::rad2deg(get_rotation()); } -//needed to update the control if the font changes.. -void Control::_ref_font(Ref<Font> p_sc) { - - if (!data.font_refcount.has(p_sc)) { - data.font_refcount[p_sc] = 1; - p_sc->connect("changed", this, "_font_changed"); - } else { - data.font_refcount[p_sc] += 1; - } -} - -void Control::_unref_font(Ref<Font> p_sc) { - - ERR_FAIL_COND(!data.font_refcount.has(p_sc)); - data.font_refcount[p_sc]--; - if (data.font_refcount[p_sc] == 0) { - p_sc->disconnect("changed", this, "_font_changed"); - data.font_refcount.erase(p_sc); - } -} - -void Control::_font_changed() { +void Control::_override_changed() { - update(); notification(NOTIFICATION_THEME_CHANGED); - minimum_size_changed(); //fonts affect minimum size pretty much almost always + minimum_size_changed(); // overrides are likely to affect minimum size } void Control::set_pivot_offset(const Vector2 &p_pivot) { @@ -2645,6 +2707,12 @@ bool Control::is_visibility_clip_disabled() const { void Control::get_argument_options(const StringName &p_function, int p_idx, List<String> *r_options) const { +#ifdef TOOLS_ENABLED + const String quote_style = EDITOR_DEF("text_editor/completion/use_single_quotes", 0) ? "'" : "\""; +#else + const String quote_style = "\""; +#endif + Node::get_argument_options(p_function, p_idx, r_options); if (p_idx == 0) { @@ -2662,10 +2730,24 @@ void Control::get_argument_options(const StringName &p_function, int p_idx, List sn.sort_custom<StringName::AlphCompare>(); for (List<StringName>::Element *E = sn.front(); E; E = E->next()) { - r_options->push_back("\"" + E->get() + "\""); + r_options->push_back(quote_style + E->get() + quote_style); } } } + +String Control::get_configuration_warning() const { + String warning = CanvasItem::get_configuration_warning(); + + if (data.mouse_filter == MOUSE_FILTER_IGNORE && data.tooltip != "") { + if (warning != String()) { + 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\"."); + } + + return warning; +} + void Control::set_clip_contents(bool p_clip) { data.clip_contents = p_clip; @@ -2707,20 +2789,23 @@ void Control::_bind_methods() { ClassDB::bind_method(D_METHOD("accept_event"), &Control::accept_event); ClassDB::bind_method(D_METHOD("get_minimum_size"), &Control::get_minimum_size); ClassDB::bind_method(D_METHOD("get_combined_minimum_size"), &Control::get_combined_minimum_size); - ClassDB::bind_method(D_METHOD("set_anchors_preset", "preset", "keep_margin"), &Control::set_anchors_preset, DEFVAL(false)); + ClassDB::bind_method(D_METHOD("set_anchors_preset", "preset", "keep_margins"), &Control::set_anchors_preset, DEFVAL(false)); ClassDB::bind_method(D_METHOD("set_margins_preset", "preset", "resize_mode", "margin"), &Control::set_margins_preset, DEFVAL(PRESET_MODE_MINSIZE), DEFVAL(0)); ClassDB::bind_method(D_METHOD("set_anchors_and_margins_preset", "preset", "resize_mode", "margin"), &Control::set_anchors_and_margins_preset, DEFVAL(PRESET_MODE_MINSIZE), DEFVAL(0)); - ClassDB::bind_method(D_METHOD("set_anchor", "margin", "anchor", "keep_margin", "push_opposite_anchor"), &Control::set_anchor, DEFVAL(false), DEFVAL(true)); ClassDB::bind_method(D_METHOD("_set_anchor", "margin", "anchor"), &Control::_set_anchor); + ClassDB::bind_method(D_METHOD("set_anchor", "margin", "anchor", "keep_margin", "push_opposite_anchor"), &Control::set_anchor, DEFVAL(false), DEFVAL(true)); ClassDB::bind_method(D_METHOD("get_anchor", "margin"), &Control::get_anchor); ClassDB::bind_method(D_METHOD("set_margin", "margin", "offset"), &Control::set_margin); ClassDB::bind_method(D_METHOD("set_anchor_and_margin", "margin", "anchor", "offset", "push_opposite_anchor"), &Control::set_anchor_and_margin, DEFVAL(false)); ClassDB::bind_method(D_METHOD("set_begin", "position"), &Control::set_begin); ClassDB::bind_method(D_METHOD("set_end", "position"), &Control::set_end); - ClassDB::bind_method(D_METHOD("set_position", "position"), &Control::set_position); - ClassDB::bind_method(D_METHOD("set_size", "size"), &Control::set_size); + ClassDB::bind_method(D_METHOD("set_position", "position", "keep_margins"), &Control::set_position, DEFVAL(false)); + ClassDB::bind_method(D_METHOD("_set_position", "margin"), &Control::_set_position); + ClassDB::bind_method(D_METHOD("set_size", "size", "keep_margins"), &Control::set_size, DEFVAL(false)); + ClassDB::bind_method(D_METHOD("_set_size", "size"), &Control::_set_size); ClassDB::bind_method(D_METHOD("set_custom_minimum_size", "size"), &Control::set_custom_minimum_size); - ClassDB::bind_method(D_METHOD("set_global_position", "position"), &Control::set_global_position); + ClassDB::bind_method(D_METHOD("set_global_position", "position", "keep_margins"), &Control::set_global_position, DEFVAL(false)); + ClassDB::bind_method(D_METHOD("_set_global_position", "position"), &Control::_set_global_position); ClassDB::bind_method(D_METHOD("set_rotation", "radians"), &Control::set_rotation); ClassDB::bind_method(D_METHOD("set_rotation_degrees", "degrees"), &Control::set_rotation_degrees); ClassDB::bind_method(D_METHOD("set_scale", "scale"), &Control::set_scale); @@ -2735,7 +2820,7 @@ void Control::_bind_methods() { ClassDB::bind_method(D_METHOD("get_scale"), &Control::get_scale); ClassDB::bind_method(D_METHOD("get_pivot_offset"), &Control::get_pivot_offset); ClassDB::bind_method(D_METHOD("get_custom_minimum_size"), &Control::get_custom_minimum_size); - ClassDB::bind_method(D_METHOD("get_parent_area_size"), &Control::get_size); + ClassDB::bind_method(D_METHOD("get_parent_area_size"), &Control::get_parent_area_size); ClassDB::bind_method(D_METHOD("get_global_position"), &Control::get_global_position); ClassDB::bind_method(D_METHOD("get_rect"), &Control::get_rect); ClassDB::bind_method(D_METHOD("get_global_rect"), &Control::get_global_rect); @@ -2829,21 +2914,25 @@ void Control::_bind_methods() { ClassDB::bind_method(D_METHOD("_theme_changed"), &Control::_theme_changed); - ClassDB::bind_method(D_METHOD("_font_changed"), &Control::_font_changed); + ClassDB::bind_method(D_METHOD("_override_changed"), &Control::_override_changed); BIND_VMETHOD(MethodInfo("_gui_input", PropertyInfo(Variant::OBJECT, "event", PROPERTY_HINT_RESOURCE_TYPE, "InputEvent"))); BIND_VMETHOD(MethodInfo(Variant::VECTOR2, "_get_minimum_size")); - BIND_VMETHOD(MethodInfo(Variant::OBJECT, "get_drag_data", PropertyInfo(Variant::VECTOR2, "position"))); + + MethodInfo get_drag_data = MethodInfo("get_drag_data", PropertyInfo(Variant::VECTOR2, "position")); + get_drag_data.return_val.usage |= PROPERTY_USAGE_NIL_IS_VARIANT; + BIND_VMETHOD(get_drag_data); + BIND_VMETHOD(MethodInfo(Variant::BOOL, "can_drop_data", PropertyInfo(Variant::VECTOR2, "position"), PropertyInfo(Variant::NIL, "data"))); BIND_VMETHOD(MethodInfo("drop_data", PropertyInfo(Variant::VECTOR2, "position"), PropertyInfo(Variant::NIL, "data"))); BIND_VMETHOD(MethodInfo(Variant::OBJECT, "_make_custom_tooltip", PropertyInfo(Variant::STRING, "for_text"))); BIND_VMETHOD(MethodInfo(Variant::BOOL, "_clips_input")); ADD_GROUP("Anchor", "anchor_"); - ADD_PROPERTYI(PropertyInfo(Variant::REAL, "anchor_left", PROPERTY_HINT_RANGE, "0,1,0.01"), "_set_anchor", "get_anchor", MARGIN_LEFT); - ADD_PROPERTYI(PropertyInfo(Variant::REAL, "anchor_top", PROPERTY_HINT_RANGE, "0,1,0.01"), "_set_anchor", "get_anchor", MARGIN_TOP); - ADD_PROPERTYI(PropertyInfo(Variant::REAL, "anchor_right", PROPERTY_HINT_RANGE, "0,1,0.01"), "_set_anchor", "get_anchor", MARGIN_RIGHT); - ADD_PROPERTYI(PropertyInfo(Variant::REAL, "anchor_bottom", PROPERTY_HINT_RANGE, "0,1,0.01"), "_set_anchor", "get_anchor", MARGIN_BOTTOM); + ADD_PROPERTYI(PropertyInfo(Variant::REAL, "anchor_left", PROPERTY_HINT_RANGE, "0,1,0.001,or_lesser,or_greater"), "_set_anchor", "get_anchor", MARGIN_LEFT); + ADD_PROPERTYI(PropertyInfo(Variant::REAL, "anchor_top", PROPERTY_HINT_RANGE, "0,1,0.001,or_lesser,or_greater"), "_set_anchor", "get_anchor", MARGIN_TOP); + ADD_PROPERTYI(PropertyInfo(Variant::REAL, "anchor_right", PROPERTY_HINT_RANGE, "0,1,0.001,or_lesser,or_greater"), "_set_anchor", "get_anchor", MARGIN_RIGHT); + ADD_PROPERTYI(PropertyInfo(Variant::REAL, "anchor_bottom", PROPERTY_HINT_RANGE, "0,1,0.001,or_lesser,or_greater"), "_set_anchor", "get_anchor", MARGIN_BOTTOM); ADD_GROUP("Margin", "margin_"); ADD_PROPERTYI(PropertyInfo(Variant::INT, "margin_left", PROPERTY_HINT_RANGE, "-4096,4096"), "set_margin", "get_margin", MARGIN_LEFT); @@ -2856,9 +2945,9 @@ 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("Rect", "rect_"); - ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "rect_position", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_EDITOR), "set_position", "get_position"); - ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "rect_global_position", PROPERTY_HINT_NONE, "", 0), "set_global_position", "get_global_position"); - ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "rect_size", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_EDITOR), "set_size", "get_size"); + ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "rect_position", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_EDITOR), "_set_position", "get_position"); + ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "rect_global_position", PROPERTY_HINT_NONE, "", 0), "_set_global_position", "get_global_position"); + ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "rect_size", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_EDITOR), "_set_size", "get_size"); ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "rect_min_size"), "set_custom_minimum_size", "get_custom_minimum_size"); ADD_PROPERTY(PropertyInfo(Variant::REAL, "rect_rotation", PROPERTY_HINT_RANGE, "-1080,1080,0.01"), "set_rotation_degrees", "get_rotation_degrees"); ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "rect_scale"), "set_scale", "get_scale"); diff --git a/scene/gui/control.h b/scene/gui/control.h index 0812457bd2..7305b3ce93 100644 --- a/scene/gui/control.h +++ b/scene/gui/control.h @@ -38,9 +38,6 @@ #include "scene/main/node.h" #include "scene/main/timer.h" #include "scene/resources/theme.h" -/** - @author Juan Linietsky <reduzio@gmail.com> -*/ class Viewport; class Label; @@ -139,8 +136,8 @@ private: bool operator()(const Control *p_a, const Control *p_b) const { if (p_a->get_canvas_layer() == p_b->get_canvas_layer()) return p_b->is_greater_than(p_a); - else - return p_a->get_canvas_layer() < p_b->get_canvas_layer(); + + return p_a->get_canvas_layer() < p_b->get_canvas_layer(); } }; @@ -207,7 +204,6 @@ private: HashMap<StringName, Ref<Font> > font_override; HashMap<StringName, Color> color_override; HashMap<StringName, int> constant_override; - Map<Ref<Font>, int> font_refcount; } data; @@ -218,6 +214,9 @@ private: Control *_get_focus_neighbour(Margin p_margin, int p_count = 0); void _set_anchor(Margin p_margin, float p_anchor); + void _set_position(const Point2 &p_point); + void _set_global_position(const Point2 &p_point); + void _set_size(const Size2 &p_size); void _propagate_theme_changed(CanvasItem *p_at, Control *p_owner, bool p_assign = true); void _theme_changed(); @@ -228,15 +227,13 @@ private: void _update_scroll(); void _resize(const Size2 &p_size); - Rect2 _compute_child_rect(const float p_anchors[4], const float p_margins[4]) const; void _compute_margins(Rect2 p_rect, const float p_anchors[4], float (&r_margins)[4]); + void _compute_anchors(Rect2 p_rect, const float p_margins[4], float (&r_anchors)[4]); void _size_changed(); String _get_tooltip() const; - void _ref_font(Ref<Font> p_sc); - void _unref_font(Ref<Font> p_sc); - void _font_changed(); + void _override_changed(); void _update_canvas_item_transform(); @@ -328,7 +325,7 @@ public: /* POSITIONING */ - void set_anchors_preset(LayoutPreset p_preset, bool p_keep_margin = true); + void set_anchors_preset(LayoutPreset p_preset, bool p_keep_margins = true); void set_margins_preset(LayoutPreset p_preset, LayoutPresetMode p_resize_mode = PRESET_MODE_MINSIZE, int p_margin = 0); void set_anchors_and_margins_preset(LayoutPreset p_preset, LayoutPresetMode p_resize_mode = PRESET_MODE_MINSIZE, int p_margin = 0); @@ -346,12 +343,12 @@ public: Point2 get_begin() const; Point2 get_end() const; - void set_position(const Point2 &p_point); - void set_global_position(const Point2 &p_point); + void set_position(const Point2 &p_point, bool p_keep_margins = false); + void set_global_position(const Point2 &p_point, bool p_keep_margins = false); Point2 get_position() const; Point2 get_global_position() const; - void set_size(const Size2 &p_size); + void set_size(const Size2 &p_size, bool p_keep_margins = false); Size2 get_size() const; Rect2 get_rect() const; @@ -486,6 +483,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; + virtual String get_configuration_warning() const; Control(); ~Control(); diff --git a/scene/gui/dialogs.cpp b/scene/gui/dialogs.cpp index 89f3d509d0..59bbdad97a 100644 --- a/scene/gui/dialogs.cpp +++ b/scene/gui/dialogs.cpp @@ -219,6 +219,14 @@ void WindowDialog::_notification(int p_what) { close_button->set_begin(Point2(-get_constant("close_h_ofs", "WindowDialog"), -get_constant("close_v_ofs", "WindowDialog"))); } break; + case NOTIFICATION_TRANSLATION_CHANGED: { + String new_title = tr(title); + if (title != new_title) { + title = new_title; + update(); + } + } break; + case NOTIFICATION_MOUSE_EXIT: { // Reset the mouse cursor when leaving the resizable window border. if (resizable && !drag_type) { @@ -226,11 +234,13 @@ void WindowDialog::_notification(int p_what) { set_default_cursor_shape(CURSOR_ARROW); } } break; + #ifdef TOOLS_ENABLED case NOTIFICATION_POST_POPUP: { if (get_tree() && Engine::get_singleton()->is_editor_hint() && EditorNode::get_singleton()) EditorNode::get_singleton()->dim_editor(true); } break; + case NOTIFICATION_POPUP_HIDE: { if (get_tree() && Engine::get_singleton()->is_editor_hint() && EditorNode::get_singleton()) EditorNode::get_singleton()->dim_editor(false); @@ -272,8 +282,11 @@ int WindowDialog::_drag_hit_test(const Point2 &pos) const { void WindowDialog::set_title(const String &p_title) { - title = tr(p_title); - update(); + String new_title = tr(p_title); + if (title != new_title) { + title = new_title; + update(); + } } String WindowDialog::get_title() const { @@ -296,8 +309,8 @@ Size2 WindowDialog::get_minimum_size() const { const int padding = button_width / 2; const int button_area = button_width + padding; - // as the title gets centered, title_width + close_button_width is not enough. - // we want a width w, such that w / 2 - title_width / 2 >= button_area, i.e. + // As the title gets centered, title_width + close_button_width is not enough. + // We want a width w, such that w / 2 - title_width / 2 >= button_area, i.e. // w >= 2 * button_area + title_width return Size2(2 * button_area + title_width, 1); @@ -324,7 +337,6 @@ void WindowDialog::_bind_methods() { WindowDialog::WindowDialog() { - //title="Hello!"; drag_type = DRAG_NONE; resizable = false; close_button = memnew(TextureButton); @@ -340,7 +352,6 @@ WindowDialog::~WindowDialog() { void PopupDialog::_notification(int p_what) { if (p_what == NOTIFICATION_DRAW) { - RID ci = get_canvas_item(); get_stylebox("panel", "PopupMenu")->draw(ci, Rect2(Point2(), get_size())); } @@ -362,15 +373,15 @@ void AcceptDialog::_post_popup() { void AcceptDialog::_notification(int p_what) { - if (p_what == NOTIFICATION_MODAL_CLOSE) { - - cancel_pressed(); - } else if (p_what == NOTIFICATION_READY) { - - _update_child_rects(); - } else if (p_what == NOTIFICATION_RESIZED) { + switch (p_what) { + case NOTIFICATION_MODAL_CLOSE: { + cancel_pressed(); + } break; - _update_child_rects(); + case NOTIFICATION_READY: + case NOTIFICATION_RESIZED: { + _update_child_rects(); + } break; } } @@ -406,12 +417,20 @@ void AcceptDialog::set_hide_on_ok(bool p_hide) { hide_on_ok = p_hide; } - bool AcceptDialog::get_hide_on_ok() const { return hide_on_ok; } +void AcceptDialog::set_autowrap(bool p_autowrap) { + + label->set_autowrap(p_autowrap); +} +bool AcceptDialog::has_autowrap() { + + return label->has_autowrap(); +} + void AcceptDialog::register_text_enter(Node *p_line_edit) { ERR_FAIL_NULL(p_line_edit); @@ -530,6 +549,8 @@ void AcceptDialog::_bind_methods() { ClassDB::bind_method(D_METHOD("_custom_action"), &AcceptDialog::_custom_action); ClassDB::bind_method(D_METHOD("set_text", "text"), &AcceptDialog::set_text); ClassDB::bind_method(D_METHOD("get_text"), &AcceptDialog::get_text); + ClassDB::bind_method(D_METHOD("set_autowrap", "autowrap"), &AcceptDialog::set_autowrap); + ClassDB::bind_method(D_METHOD("has_autowrap"), &AcceptDialog::has_autowrap); ADD_SIGNAL(MethodInfo("confirmed")); ADD_SIGNAL(MethodInfo("custom_action", PropertyInfo(Variant::STRING, "action"))); @@ -537,6 +558,7 @@ void AcceptDialog::_bind_methods() { ADD_GROUP("Dialog", "dialog"); ADD_PROPERTY(PropertyInfo(Variant::STRING, "dialog_text", PROPERTY_HINT_MULTILINE_TEXT, "", PROPERTY_USAGE_DEFAULT_INTL), "set_text", "get_text"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "dialog_hide_on_ok"), "set_hide_on_ok", "get_hide_on_ok"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "dialog_autowrap"), "set_autowrap", "has_autowrap"); } bool AcceptDialog::swap_ok_cancel = false; @@ -555,7 +577,6 @@ AcceptDialog::AcceptDialog() { label->set_anchor(MARGIN_BOTTOM, ANCHOR_END); label->set_begin(Point2(margin, margin)); label->set_end(Point2(-margin, -button_margin - 10)); - //label->set_autowrap(true); add_child(label); hbc = memnew(HBoxContainer); diff --git a/scene/gui/dialogs.h b/scene/gui/dialogs.h index 4b89ac54c5..1a0350ba18 100644 --- a/scene/gui/dialogs.h +++ b/scene/gui/dialogs.h @@ -37,9 +37,6 @@ #include "scene/gui/panel.h" #include "scene/gui/popup.h" #include "scene/gui/texture_button.h" -/** - @author Juan Linietsky <reduzio@gmail.com> -*/ class WindowDialog : public Popup { @@ -145,6 +142,9 @@ public: void set_text(String p_text); String get_text() const; + void set_autowrap(bool p_autowrap); + bool has_autowrap(); + AcceptDialog(); ~AcceptDialog(); }; diff --git a/scene/gui/file_dialog.cpp b/scene/gui/file_dialog.cpp index 5599c64daa..9bc593ea3b 100644 --- a/scene/gui/file_dialog.cpp +++ b/scene/gui/file_dialog.cpp @@ -48,8 +48,9 @@ void FileDialog::_notification(int p_what) { if (p_what == NOTIFICATION_ENTER_TREE) { - refresh->set_icon(get_icon("reload")); dir_up->set_icon(get_icon("parent_folder")); + refresh->set_icon(get_icon("reload")); + show_hidden->set_icon(get_icon("toggle_hidden")); } if (p_what == NOTIFICATION_POPUP_HIDE) { @@ -86,7 +87,9 @@ void FileDialog::_unhandled_input(const Ref<InputEvent> &p_event) { _dir_entered(".."); } break; - default: { handled = false; } + default: { + handled = false; + } } if (handled) @@ -299,11 +302,8 @@ bool FileDialog::_is_open_should_be_disabled() { Dictionary d = ti->get_metadata(0); // Opening a file, but selected a folder? Forbidden. - if (((mode == MODE_OPEN_FILE || mode == MODE_OPEN_FILES) && d["dir"]) || // Flipped case, also forbidden. - (mode == MODE_OPEN_DIR && !d["dir"])) - return true; - - return false; + return ((mode == MODE_OPEN_FILE || mode == MODE_OPEN_FILES) && d["dir"]) || // Flipped case, also forbidden. + (mode == MODE_OPEN_DIR && !d["dir"]); } void FileDialog::_go_up() { @@ -381,6 +381,18 @@ void FileDialog::_tree_item_activated() { } } +void FileDialog::update_file_name() { + int idx = filter->get_selected() - 1; + if ((idx == -1 && filter->get_item_count() == 2) || (filter->get_item_count() > 2 && idx >= 0 && idx < filter->get_item_count() - 2)) { + if (idx == -1) idx += 1; + String filter_str = filters[idx]; + String file_str = file->get_text(); + String base_name = file_str.get_basename(); + file_str = base_name + "." + filter_str.strip_edges().to_lower(); + file->set_text(file_str); + } +} + void FileDialog::update_file_list() { tree->clear(); @@ -388,23 +400,22 @@ void FileDialog::update_file_list() { TreeItem *root = tree->create_item(); Ref<Texture> folder = get_icon("folder"); + const Color folder_color = get_color("folder_icon_modulate"); List<String> files; List<String> dirs; - bool isdir; - bool ishidden; - bool show_hidden = show_hidden_files; + bool is_hidden; String item; - while ((item = dir_access->get_next(&isdir)) != "") { + while ((item = dir_access->get_next()) != "") { if (item == "." || item == "..") continue; - ishidden = dir_access->current_is_hidden(); + is_hidden = dir_access->current_is_hidden(); - if (show_hidden || !ishidden) { - if (!isdir) + if (show_hidden_files || !is_hidden) { + if (!dir_access->current_is_dir()) files.push_back(item); else dirs.push_back(item); @@ -419,6 +430,7 @@ void FileDialog::update_file_list() { TreeItem *ti = tree->create_item(root); ti->set_text(0, dir_name); ti->set_icon(0, folder); + ti->set_icon_modulate(0, folder_color); Dictionary d; d["name"] = dir_name; @@ -429,8 +441,6 @@ void FileDialog::update_file_list() { dirs.pop_front(); } - dirs.clear(); - List<String> patterns; // build filter if (filter->get_selected() == filter->get_item_count() - 1) { @@ -505,12 +515,11 @@ void FileDialog::update_file_list() { if (tree->get_root() && tree->get_root()->get_children() && tree->get_selected() == NULL) tree->get_root()->get_children()->select(0); - - files.clear(); } void FileDialog::_filter_selected(int) { + update_file_name(); update_file_list(); } @@ -802,6 +811,7 @@ void FileDialog::_bind_methods() { ClassDB::bind_method(D_METHOD("_select_drive"), &FileDialog::_select_drive); ClassDB::bind_method(D_METHOD("_make_dir"), &FileDialog::_make_dir); ClassDB::bind_method(D_METHOD("_make_dir_confirm"), &FileDialog::_make_dir_confirm); + ClassDB::bind_method(D_METHOD("_update_file_name"), &FileDialog::update_file_name); ClassDB::bind_method(D_METHOD("_update_file_list"), &FileDialog::update_file_list); ClassDB::bind_method(D_METHOD("_update_dir"), &FileDialog::update_dir); ClassDB::bind_method(D_METHOD("_go_up"), &FileDialog::_go_up); @@ -861,7 +871,7 @@ FileDialog::FileDialog() { HBoxContainer *hbc = memnew(HBoxContainer); dir_up = memnew(ToolButton); - dir_up->set_tooltip(RTR("Go to parent folder")); + dir_up->set_tooltip(RTR("Go to parent folder.")); hbc->add_child(dir_up); dir_up->connect("pressed", this, "_go_up"); @@ -871,9 +881,17 @@ FileDialog::FileDialog() { dir->set_h_size_flags(SIZE_EXPAND_FILL); refresh = memnew(ToolButton); + refresh->set_tooltip(RTR("Refresh files.")); refresh->connect("pressed", this, "_update_file_list"); hbc->add_child(refresh); + show_hidden = memnew(ToolButton); + 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->connect("toggled", this, "set_show_hidden_files"); + hbc->add_child(show_hidden); + drives = memnew(OptionButton); hbc->add_child(drives); drives->connect("item_selected", this, "_select_drive"); diff --git a/scene/gui/file_dialog.h b/scene/gui/file_dialog.h index 85edac0b12..4fd6d0d13c 100644 --- a/scene/gui/file_dialog.h +++ b/scene/gui/file_dialog.h @@ -38,9 +38,7 @@ #include "scene/gui/option_button.h" #include "scene/gui/tool_button.h" #include "scene/gui/tree.h" -/** - @author Juan Linietsky <reduzio@gmail.com> -*/ + class FileDialog : public ConfirmationDialog { GDCLASS(FileDialog, ConfirmationDialog); @@ -90,6 +88,7 @@ private: ToolButton *dir_up; ToolButton *refresh; + ToolButton *show_hidden; Vector<String> filters; @@ -101,6 +100,7 @@ private: bool invalidated; void update_dir(); + void update_file_name(); void update_file_list(); void update_filters(); diff --git a/scene/gui/gradient_edit.cpp b/scene/gui/gradient_edit.cpp index cfbc9d6d18..75f5f79873 100644 --- a/scene/gui/gradient_edit.cpp +++ b/scene/gui/gradient_edit.cpp @@ -29,10 +29,17 @@ /*************************************************************************/ #include "gradient_edit.h" + #include "core/os/keyboard.h" -#include "editor/editor_scale.h" +#ifdef TOOLS_ENABLED +#include "editor/editor_scale.h" #define SPACING (3 * EDSCALE) +#define POINT_WIDTH (8 * EDSCALE) +#else +#define SPACING 3 +#define POINT_WIDTH 8 +#endif GradientEdit::GradientEdit() { grabbed = -1; diff --git a/scene/gui/gradient_edit.h b/scene/gui/gradient_edit.h index fd340b3f6c..6f31107729 100644 --- a/scene/gui/gradient_edit.h +++ b/scene/gui/gradient_edit.h @@ -33,10 +33,8 @@ #include "scene/gui/color_picker.h" #include "scene/gui/popup.h" -#include "scene/resources/color_ramp.h" #include "scene/resources/default_theme/theme_data.h" - -#define POINT_WIDTH (8 * EDSCALE) +#include "scene/resources/gradient.h" class GradientEdit : public Control { diff --git a/scene/gui/graph_edit.cpp b/scene/gui/graph_edit.cpp index 94c65b3e64..fdffb26cb5 100644 --- a/scene/gui/graph_edit.cpp +++ b/scene/gui/graph_edit.cpp @@ -221,8 +221,8 @@ void GraphEdit::_graph_node_raised(Node *p_gn) { } int first_not_comment = 0; for (int i = 0; i < get_child_count(); i++) { - GraphNode *gn = Object::cast_to<GraphNode>(get_child(i)); - if (gn && !gn->is_comment()) { + GraphNode *gn2 = Object::cast_to<GraphNode>(get_child(i)); + if (gn2 && !gn2->is_comment()) { first_not_comment = i; break; } @@ -261,8 +261,9 @@ void GraphEdit::add_child_notify(Node *p_child) { void GraphEdit::remove_child_notify(Node *p_child) { Control::remove_child_notify(p_child); - - top_layer->call_deferred("raise"); //top layer always on top! + if (is_inside_tree()) { + top_layer->call_deferred("raise"); //top layer always on top! + } GraphNode *gn = Object::cast_to<GraphNode>(p_child); if (gn) { gn->disconnect("offset_changed", this, "_graph_node_moved"); @@ -478,7 +479,7 @@ void GraphEdit::_top_layer_input(const Ref<InputEvent> &p_ev) { connecting_color = gn->get_connection_input_color(j); connecting_target = false; connecting_to = pos; - just_disconnected = true; + just_disconnected = false; return; } @@ -549,11 +550,18 @@ void GraphEdit::_top_layer_input(const Ref<InputEvent> &p_ev) { emit_signal("connection_request", from, from_slot, to, to_slot); } else if (!just_disconnected) { + String from = connecting_from; int from_slot = connecting_index; Vector2 ofs = Vector2(mb->get_position().x, mb->get_position().y); - emit_signal("connection_to_empty", from, from_slot, ofs); + + if (!connecting_out) { + emit_signal("connection_from_empty", from, from_slot, ofs); + } else { + emit_signal("connection_to_empty", from, from_slot, ofs); + } } + connecting = false; top_layer->update(); update(); @@ -768,8 +776,16 @@ void GraphEdit::_top_layer_draw() { _draw_cos_line(top_layer, pos, topos, col, col); } - if (box_selecting) - top_layer->draw_rect(box_selecting_rect, Color(0.7, 0.7, 1.0, 0.3)); + if (box_selecting) { + top_layer->draw_rect( + box_selecting_rect, + get_color("box_selection_fill_color", "Editor")); + + top_layer->draw_rect( + box_selecting_rect, + get_color("box_selection_stroke_color", "Editor"), + false); + } } void GraphEdit::set_selected(Node *p_child) { @@ -958,33 +974,33 @@ void GraphEdit::_gui_input(const Ref<InputEvent> &p_ev) { previus_selected.clear(); for (int i = get_child_count() - 1; i >= 0; i--) { - GraphNode *gn = Object::cast_to<GraphNode>(get_child(i)); - if (!gn || !gn->is_selected()) + GraphNode *gn2 = Object::cast_to<GraphNode>(get_child(i)); + if (!gn2 || !gn2->is_selected()) continue; - previus_selected.push_back(gn); + previus_selected.push_back(gn2); } } else if (b->get_shift()) { box_selection_mode_aditive = false; previus_selected.clear(); for (int i = get_child_count() - 1; i >= 0; i--) { - GraphNode *gn = Object::cast_to<GraphNode>(get_child(i)); - if (!gn || !gn->is_selected()) + GraphNode *gn2 = Object::cast_to<GraphNode>(get_child(i)); + if (!gn2 || !gn2->is_selected()) continue; - previus_selected.push_back(gn); + previus_selected.push_back(gn2); } } else { box_selection_mode_aditive = true; previus_selected.clear(); for (int i = get_child_count() - 1; i >= 0; i--) { - GraphNode *gn = Object::cast_to<GraphNode>(get_child(i)); - if (!gn) + GraphNode *gn2 = Object::cast_to<GraphNode>(get_child(i)); + if (!gn2) continue; - gn->set_selected(false); + gn2->set_selected(false); } } } @@ -1020,14 +1036,28 @@ void GraphEdit::_gui_input(const Ref<InputEvent> &p_ev) { } Ref<InputEventKey> k = p_ev; - if (k.is_valid() && k->get_scancode() == KEY_D && k->is_pressed() && k->get_command()) { - emit_signal("duplicate_nodes_request"); - accept_event(); - } - if (k.is_valid() && k->get_scancode() == KEY_DELETE && k->is_pressed()) { - emit_signal("delete_nodes_request"); - accept_event(); + if (k.is_valid()) { + + if (k->get_scancode() == KEY_D && k->is_pressed() && k->get_command()) { + emit_signal("duplicate_nodes_request"); + accept_event(); + } + + if (k->get_scancode() == KEY_C && k->is_pressed() && k->get_command()) { + emit_signal("copy_nodes_request"); + accept_event(); + } + + if (k->get_scancode() == KEY_V && k->is_pressed() && k->get_command()) { + emit_signal("paste_nodes_request"); + accept_event(); + } + + if (k->get_scancode() == KEY_DELETE && k->is_pressed()) { + emit_signal("delete_nodes_request"); + accept_event(); + } } Ref<InputEventMagnifyGesture> magnify_gesture = p_ev; @@ -1050,7 +1080,7 @@ void GraphEdit::set_connection_activity(const StringName &p_from, int p_from_por if (E->get().from == p_from && E->get().from_port == p_from_port && E->get().to == p_to && E->get().to_port == p_to_port) { - if (ABS(E->get().activity - p_activity) < CMP_EPSILON) { + if (Math::is_equal_approx(E->get().activity, p_activity)) { //update only if changed top_layer->update(); connections_layer->update(); @@ -1287,8 +1317,11 @@ void GraphEdit::_bind_methods() { ADD_SIGNAL(MethodInfo("disconnection_request", PropertyInfo(Variant::STRING, "from"), PropertyInfo(Variant::INT, "from_slot"), PropertyInfo(Variant::STRING, "to"), PropertyInfo(Variant::INT, "to_slot"))); ADD_SIGNAL(MethodInfo("popup_request", PropertyInfo(Variant::VECTOR2, "position"))); ADD_SIGNAL(MethodInfo("duplicate_nodes_request")); + ADD_SIGNAL(MethodInfo("copy_nodes_request")); + ADD_SIGNAL(MethodInfo("paste_nodes_request")); ADD_SIGNAL(MethodInfo("node_selected", PropertyInfo(Variant::OBJECT, "node", PROPERTY_HINT_RESOURCE_TYPE, "Node"))); ADD_SIGNAL(MethodInfo("connection_to_empty", PropertyInfo(Variant::STRING, "from"), PropertyInfo(Variant::INT, "from_slot"), PropertyInfo(Variant::VECTOR2, "release_position"))); + ADD_SIGNAL(MethodInfo("connection_from_empty", PropertyInfo(Variant::STRING, "to"), PropertyInfo(Variant::INT, "to_slot"), PropertyInfo(Variant::VECTOR2, "release_position"))); ADD_SIGNAL(MethodInfo("delete_nodes_request")); ADD_SIGNAL(MethodInfo("_begin_node_move")); ADD_SIGNAL(MethodInfo("_end_node_move")); diff --git a/scene/gui/graph_node.cpp b/scene/gui/graph_node.cpp index 8c588109d6..f7bef4ed39 100644 --- a/scene/gui/graph_node.cpp +++ b/scene/gui/graph_node.cpp @@ -159,12 +159,9 @@ void GraphNode::_resort() { fit_child_in_rect(c, r); cache_y.push_back(vofs + size.y * 0.5); - if (vofs > 0) - vofs += sep; - vofs += size.y; + vofs += size.y + sep; } - _change_notify(); update(); connpos_dirty = true; } @@ -409,9 +406,12 @@ Size2 GraphNode::get_minimum_size() const { void GraphNode::set_title(const String &p_title) { + if (title == p_title) + return; title = p_title; - minimum_size_changed(); update(); + _change_notify("title"); + minimum_size_changed(); } String GraphNode::get_title() const { @@ -592,13 +592,14 @@ void GraphNode::_gui_input(const Ref<InputEvent> &p_ev) { Ref<InputEventMouseButton> mb = p_ev; if (mb.is_valid()) { - ERR_EXPLAIN("GraphNode must be the child of a GraphEdit node."); - ERR_FAIL_COND(get_parent_control() == NULL); + ERR_FAIL_COND_MSG(get_parent_control() == NULL, "GraphNode must be the child of a GraphEdit node."); if (mb->is_pressed() && mb->get_button_index() == 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 + get_parent_control()->grab_focus(); emit_signal("close_request"); accept_event(); return; @@ -615,9 +616,7 @@ void GraphNode::_gui_input(const Ref<InputEvent> &p_ev) { return; } - //send focus to parent emit_signal("raise_request"); - get_parent_control()->grab_focus(); } if (!mb->is_pressed() && mb->get_button_index() == BUTTON_LEFT) { diff --git a/scene/gui/item_list.cpp b/scene/gui/item_list.cpp index efe452305b..a3bc68ffcd 100644 --- a/scene/gui/item_list.cpp +++ b/scene/gui/item_list.cpp @@ -470,6 +470,8 @@ Size2 ItemList::Item::get_icon_size() const { void ItemList::_gui_input(const Ref<InputEvent> &p_event) { + double prev_scroll = scroll_bar->get_value(); + Ref<InputEventMouseMotion> mm = p_event; if (defer_select_single >= 0 && mm.is_valid()) { defer_select_single = -1; @@ -747,9 +749,21 @@ void ItemList::_gui_input(const Ref<InputEvent> &p_event) { search_string = ""; } - search_string += String::chr(k->get_unicode()); - for (int i = 0; i < items.size(); i++) { - if (items[i].text.begins_with(search_string)) { + if (String::chr(k->get_unicode()) != search_string) + search_string += String::chr(k->get_unicode()); + + for (int i = current + 1; i <= items.size(); i++) { + if (i == items.size()) { + if (current == 0 || current == -1) + break; + else + i = 0; + } + + if (i == current) + break; + + if (items[i].text.findn(search_string) == 0) { set_current(i); ensure_current_is_visible(); if (select_mode == SELECT_SINGLE) { @@ -767,6 +781,9 @@ void ItemList::_gui_input(const Ref<InputEvent> &p_event) { scroll_bar->set_value(scroll_bar->get_value() + scroll_bar->get_page() * pan_gesture->get_delta().y / 8); } + + if (scroll_bar->get_value() != prev_scroll) + accept_event(); //accept event if scroll changed } void ItemList::ensure_current_is_visible() { @@ -1094,7 +1111,7 @@ void ItemList::_notification(int p_what) { if (items[i].disabled) modulate.a *= 0.5; - // If the icon is transposed, we have to swith the size so that it is drawn correctly + // If the icon is transposed, we have to switch the size so that it is drawn correctly if (items[i].icon_transposed) { Size2 size_tmp = draw_rect.size; draw_rect.size.x = size_tmp.y; @@ -1114,13 +1131,13 @@ void ItemList::_notification(int p_what) { int max_len = -1; - Vector2 size = font->get_string_size(items[i].text); + Vector2 size2 = font->get_string_size(items[i].text); if (fixed_column_width) max_len = fixed_column_width; else if (same_column_width) max_len = items[i].rect_cache.size.x; else - max_len = size.x; + max_len = size2.x; Color modulate = items[i].selected ? font_color_selected : (items[i].custom_fg != Color() ? items[i].custom_fg : font_color); if (items[i].disabled) @@ -1170,12 +1187,12 @@ void ItemList::_notification(int p_what) { } else { if (fixed_column_width > 0) - size.x = MIN(size.x, fixed_column_width); + size2.x = MIN(size2.x, fixed_column_width); if (icon_mode == ICON_MODE_TOP) { - text_ofs.x += (items[i].rect_cache.size.width - size.x) / 2; + text_ofs.x += (items[i].rect_cache.size.width - size2.x) / 2; } else { - text_ofs.y += (items[i].rect_cache.size.height - size.y) / 2; + text_ofs.y += (items[i].rect_cache.size.height - size2.y) / 2; } text_ofs.y += font->get_ascent(); @@ -1243,7 +1260,7 @@ int ItemList::get_item_at_position(const Point2 &p_pos, bool p_exact) const { Rect2 rc = items[i].rect_cache; if (i % current_columns == current_columns - 1) { - rc.size.width = get_size().width; //not right but works + rc.size.width = get_size().width - rc.position.x; //make sure you can still select the last item when clicking past the column } if (rc.has_point(pos)) { diff --git a/scene/gui/label.cpp b/scene/gui/label.cpp index e3e9368a12..510f1b18ad 100644 --- a/scene/gui/label.cpp +++ b/scene/gui/label.cpp @@ -247,11 +247,11 @@ void Label::_notification(int p_what) { n = String::char_uppercase(n); } - float move = font->draw_char(ci, Point2(x_ofs_shadow, y_ofs) + shadow_ofs, c, n, font_color_shadow, false); + float move = drawer.draw_char(ci, Point2(x_ofs_shadow, y_ofs) + shadow_ofs, c, n, font_color_shadow); if (use_outline) { - font->draw_char(ci, Point2(x_ofs_shadow, y_ofs) + Vector2(-shadow_ofs.x, shadow_ofs.y), c, n, font_color_shadow, false); - font->draw_char(ci, Point2(x_ofs_shadow, y_ofs) + Vector2(shadow_ofs.x, -shadow_ofs.y), c, n, font_color_shadow, false); - font->draw_char(ci, Point2(x_ofs_shadow, y_ofs) + Vector2(-shadow_ofs.x, -shadow_ofs.y), c, n, font_color_shadow, false); + drawer.draw_char(ci, Point2(x_ofs_shadow, y_ofs) + Vector2(-shadow_ofs.x, shadow_ofs.y), c, n, font_color_shadow); + drawer.draw_char(ci, Point2(x_ofs_shadow, y_ofs) + Vector2(shadow_ofs.x, -shadow_ofs.y), c, n, font_color_shadow); + drawer.draw_char(ci, Point2(x_ofs_shadow, y_ofs) + Vector2(-shadow_ofs.x, -shadow_ofs.y), c, n, font_color_shadow); } x_ofs_shadow += move; chars_total_shadow++; @@ -470,7 +470,6 @@ void Label::regenerate_word_cache() { wc->word_len = i - word_pos; wc->space_count = space_count; current_word_size = char_width; - space_count = 0; word_pos = i; } } diff --git a/scene/gui/label.h b/scene/gui/label.h index 561c42ef9e..2cc55a47ef 100644 --- a/scene/gui/label.h +++ b/scene/gui/label.h @@ -32,9 +32,7 @@ #define LABEL_H #include "scene/gui/control.h" -/** - @author Juan Linietsky <reduzio@gmail.com> -*/ + class Label : public Control { GDCLASS(Label, Control); diff --git a/scene/gui/line_edit.cpp b/scene/gui/line_edit.cpp index 4a2bb3dad1..4a763844f8 100644 --- a/scene/gui/line_edit.cpp +++ b/scene/gui/line_edit.cpp @@ -29,6 +29,7 @@ /*************************************************************************/ #include "line_edit.h" + #include "core/message_queue.h" #include "core/os/keyboard.h" #include "core/os/os.h" @@ -43,7 +44,7 @@ static bool _is_text_char(CharType c) { - return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') || c == '_'; + return !is_symbol(c); } void LineEdit::_gui_input(Ref<InputEvent> p_event) { @@ -55,8 +56,10 @@ void LineEdit::_gui_input(Ref<InputEvent> p_event) { if (b->is_pressed() && b->get_button_index() == BUTTON_RIGHT && context_menu_enabled) { menu->set_position(get_global_transform().xform(get_local_mouse_position())); menu->set_size(Vector2(1, 1)); + menu->set_scale(get_global_transform().get_scale()); menu->popup(); grab_focus(); + accept_event(); return; } @@ -66,6 +69,7 @@ void LineEdit::_gui_input(Ref<InputEvent> p_event) { _reset_caret_blink_timer(); if (b->is_pressed()) { + accept_event(); //don't pass event further when clicked on text field if (!text.empty() && is_editable() && _is_over_clear_button(b->get_position())) { clear_button_status.press_attempt = true; clear_button_status.pressing_inside = true; @@ -157,6 +161,38 @@ void LineEdit::_gui_input(Ref<InputEvent> p_event) { if (!k->is_pressed()) return; + +#ifdef APPLE_STYLE_KEYS + if (k->get_control() && !k->get_shift() && !k->get_alt() && !k->get_command()) { + uint32_t remap_key = KEY_UNKNOWN; + switch (k->get_scancode()) { + case KEY_F: { + remap_key = KEY_RIGHT; + } break; + case KEY_B: { + remap_key = KEY_LEFT; + } break; + case KEY_P: { + remap_key = KEY_UP; + } break; + case KEY_N: { + remap_key = KEY_DOWN; + } break; + case KEY_D: { + remap_key = KEY_DELETE; + } break; + case KEY_H: { + remap_key = KEY_BACKSPACE; + } break; + } + + if (remap_key != KEY_UNKNOWN) { + k->set_scancode(remap_key); + k->set_control(false); + } + } +#endif + unsigned int code = k->get_scancode(); if (k->get_command()) { @@ -320,7 +356,7 @@ void LineEdit::_gui_input(Ref<InputEvent> p_event) { handled = false; break; } - // numlock disabled. fallthrough to key_left + FALLTHROUGH; } case KEY_LEFT: { @@ -367,7 +403,7 @@ void LineEdit::_gui_input(Ref<InputEvent> p_event) { handled = false; break; } - // numlock disabled. fallthrough to key_right + FALLTHROUGH; } case KEY_RIGHT: { @@ -474,7 +510,7 @@ void LineEdit::_gui_input(Ref<InputEvent> p_event) { handled = false; break; } - // numlock disabled. fallthrough to key_home + FALLTHROUGH; } case KEY_HOME: { @@ -487,7 +523,7 @@ void LineEdit::_gui_input(Ref<InputEvent> p_event) { handled = false; break; } - // numlock disabled. fallthrough to key_end + FALLTHROUGH; } case KEY_END: { @@ -504,7 +540,7 @@ void LineEdit::_gui_input(Ref<InputEvent> p_event) { if (handled) { accept_event(); - } else if (!k->get_alt() && !k->get_command()) { + } else if (!k->get_command()) { if (k->get_unicode() >= 32 && k->get_scancode() != KEY_DELETE) { if (editable) { @@ -588,10 +624,7 @@ bool LineEdit::_is_over_clear_button(const Point2 &p_pos) const { } Ref<Texture> icon = Control::get_icon("clear"); int x_ofs = get_stylebox("normal")->get_offset().x; - if (p_pos.x > get_size().width - icon->get_width() - x_ofs) { - return true; - } - return false; + return p_pos.x > get_size().width - icon->get_width() - x_ofs; } void LineEdit::_notification(int p_what) { @@ -611,12 +644,15 @@ void LineEdit::_notification(int p_what) { #endif case NOTIFICATION_RESIZED: { - if (expand_to_text_length) { - window_pos = 0; //force scroll back since it's expanding to text length - } + window_pos = 0; set_cursor_position(get_cursor_position()); } break; + case NOTIFICATION_TRANSLATION_CHANGED: { + placeholder_translated = tr(placeholder); + update_placeholder_width(); + update(); + } break; case MainLoop::NOTIFICATION_WM_FOCUS_IN: { window_has_focus = true; draw_caret = true; @@ -642,10 +678,8 @@ void LineEdit::_notification(int p_what) { RID ci = get_canvas_item(); Ref<StyleBox> style = get_stylebox("normal"); - float disabled_alpha = 1.0; // used to set the disabled input text color if (!is_editable()) { style = get_stylebox("read_only"); - disabled_alpha = .5; draw_caret = false; } @@ -691,20 +725,19 @@ void LineEdit::_notification(int p_what) { int font_ascent = font->get_ascent(); Color selection_color = get_color("selection_color"); - Color font_color = get_color("font_color"); + Color font_color = is_editable() ? get_color("font_color") : get_color("font_color_uneditable"); Color font_color_selected = get_color("font_color_selected"); Color cursor_color = get_color("cursor_color"); - const String &t = using_placeholder ? placeholder : text; + const String &t = using_placeholder ? placeholder_translated : text; // draw placeholder color if (using_placeholder) font_color.a *= placeholder_alpha; - font_color.a *= disabled_alpha; bool display_clear_icon = !using_placeholder && is_editable() && clear_button_enabled; if (right_icon.is_valid() || display_clear_icon) { Ref<Texture> r_icon = display_clear_icon ? Control::get_icon("clear") : right_icon; - Color color_icon(1, 1, 1, disabled_alpha * .9); + Color color_icon(1, 1, 1, !is_editable() ? .5 * .9 : .9); if (display_clear_icon) { if (clear_button_status.press_attempt && clear_button_status.pressing_inside) { color_icon = get_color("clear_button_color_pressed"); @@ -721,6 +754,8 @@ void LineEdit::_notification(int p_what) { } else { x_ofs = MAX(style->get_margin(MARGIN_LEFT), x_ofs - r_icon->get_width() - style->get_margin(MARGIN_RIGHT)); } + + ofs_max -= r_icon->get_width(); } int caret_height = font->get_height() > y_area ? y_area : font->get_height(); @@ -888,7 +923,8 @@ void LineEdit::cut_text() { void LineEdit::paste_text() { - String paste_buffer = OS::get_singleton()->get_clipboard(); + // Strip escape characters like \n and \t as they can't be displayed on LineEdit. + String paste_buffer = OS::get_singleton()->get_clipboard().strip_escapes(); if (paste_buffer != "") { @@ -917,6 +953,10 @@ void LineEdit::undo() { TextOperation op = undo_stack_pos->get(); text = op.text; set_cursor_position(op.cursor_pos); + + if (expand_to_text_length) + minimum_size_changed(); + _emit_text_change(); } @@ -931,6 +971,10 @@ void LineEdit::redo() { TextOperation op = undo_stack_pos->get(); text = op.text; set_cursor_position(op.cursor_pos); + + if (expand_to_text_length) + minimum_size_changed(); + _emit_text_change(); } @@ -956,6 +1000,8 @@ void LineEdit::set_cursor_at_pixel_pos(int p_x) { Ref<StyleBox> style = get_stylebox("normal"); int pixel_ofs = 0; Size2 size = get_size(); + bool display_clear_icon = !text.empty() && is_editable() && clear_button_enabled; + int r_icon_width = Control::get_icon("clear")->get_width(); switch (align) { @@ -970,10 +1016,16 @@ void LineEdit::set_cursor_at_pixel_pos(int p_x) { pixel_ofs = int(style->get_offset().x); else pixel_ofs = int(size.width - (cached_width)) / 2; + + if (display_clear_icon) + pixel_ofs -= int(r_icon_width / 2 + style->get_margin(MARGIN_RIGHT)); } break; case ALIGN_RIGHT: { pixel_ofs = int(size.width - style->get_margin(MARGIN_RIGHT) - (cached_width)); + + if (display_clear_icon) + pixel_ofs -= int(r_icon_width + style->get_margin(MARGIN_RIGHT)); } break; } @@ -1104,16 +1156,9 @@ String LineEdit::get_text() const { void LineEdit::set_placeholder(String p_text) { - placeholder = tr(p_text); - if ((max_length <= 0) || (placeholder.length() <= max_length)) { - Ref<Font> font = get_font("font"); - cached_placeholder_width = 0; - if (font != NULL) { - for (int i = 0; i < placeholder.length(); i++) { - cached_placeholder_width += font->get_char_size(placeholder[i]).width; - } - } - } + placeholder = p_text; + placeholder_translated = tr(placeholder); + update_placeholder_width(); update(); } @@ -1154,14 +1199,14 @@ void LineEdit::set_cursor_position(int p_pos) { if (cursor_pos <= window_pos) { /* Adjust window if cursor goes too much to the left */ - if (window_pos > 0) - set_window_pos(window_pos - 1); - - } else if (cursor_pos > window_pos) { + set_window_pos(MAX(0, cursor_pos - 1)); + } else { /* Adjust window if cursor goes too much to the right */ int window_width = get_size().width - style->get_minimum_size().width; - if (right_icon.is_valid()) { - window_width -= right_icon->get_width(); + bool display_clear_icon = !text.empty() && is_editable() && clear_button_enabled; + if (right_icon.is_valid() || display_clear_icon) { + Ref<Texture> r_icon = display_clear_icon ? Control::get_icon("clear") : right_icon; + window_width -= r_icon->get_width(); } if (window_width < 0) @@ -1225,6 +1270,7 @@ void LineEdit::append_at_cursor(String p_text) { void LineEdit::clear_internal() { + deselect(); _clear_undo_stack(); cached_width = 0; cursor_pos = 0; @@ -1315,7 +1361,37 @@ void LineEdit::select_all() { void LineEdit::set_editable(bool p_editable) { + if (editable == p_editable) + return; + editable = p_editable; + + // Reorganize context menu. + menu->clear(); + + if (editable) { + menu->add_item(RTR("Undo"), MENU_UNDO, KEY_MASK_CMD | KEY_Z); + menu->add_item(RTR("Redo"), MENU_REDO, KEY_MASK_CMD | KEY_MASK_SHIFT | KEY_Z); + } + + if (editable) { + menu->add_separator(); + menu->add_item(RTR("Cut"), MENU_CUT, KEY_MASK_CMD | KEY_X); + } + + menu->add_item(RTR("Copy"), MENU_COPY, KEY_MASK_CMD | KEY_C); + + if (editable) { + menu->add_item(RTR("Paste"), MENU_PASTE, KEY_MASK_CMD | KEY_V); + } + + menu->add_separator(); + menu->add_item(RTR("Select All"), MENU_SELECT_ALL, KEY_MASK_CMD | KEY_A); + + if (editable) { + menu->add_item(RTR("Clear"), MENU_CLEAR); + } + update(); } @@ -1339,8 +1415,7 @@ void LineEdit::set_secret_character(const String &p_string) { // An empty string as the secret character would crash the engine // It also wouldn't make sense to use multiple characters as the secret character - ERR_EXPLAIN("Secret character must be exactly one character long (" + itos(p_string.length()) + " characters given)"); - ERR_FAIL_COND(p_string.length() != 1); + ERR_FAIL_COND_MSG(p_string.length() != 1, "Secret character must be exactly one character long (" + itos(p_string.length()) + " characters given)."); secret_character = p_string; update(); @@ -1481,6 +1556,18 @@ void LineEdit::_emit_text_change() { text_changed_dirty = false; } +void LineEdit::update_placeholder_width() { + if ((max_length <= 0) || (placeholder_translated.length() <= max_length)) { + Ref<Font> font = get_font("font"); + cached_placeholder_width = 0; + if (font != NULL) { + for (int i = 0; i < placeholder_translated.length(); i++) { + cached_placeholder_width += font->get_char_size(placeholder_translated[i]).width; + } + } + } +} + void LineEdit::_clear_redo() { _create_undo_state(); if (undo_stack_pos == NULL) { @@ -1611,7 +1698,6 @@ LineEdit::LineEdit() { deselect(); set_focus_mode(FOCUS_ALL); - editable = true; set_default_cursor_shape(CURSOR_IBEAM); set_mouse_filter(MOUSE_FILTER_STOP); @@ -1626,15 +1712,8 @@ LineEdit::LineEdit() { context_menu_enabled = true; menu = memnew(PopupMenu); add_child(menu); - menu->add_item(RTR("Cut"), MENU_CUT, KEY_MASK_CMD | KEY_X); - menu->add_item(RTR("Copy"), MENU_COPY, KEY_MASK_CMD | KEY_C); - menu->add_item(RTR("Paste"), MENU_PASTE, KEY_MASK_CMD | KEY_V); - menu->add_separator(); - menu->add_item(RTR("Select All"), MENU_SELECT_ALL, KEY_MASK_CMD | KEY_A); - menu->add_item(RTR("Clear"), MENU_CLEAR); - menu->add_separator(); - menu->add_item(RTR("Undo"), MENU_UNDO, KEY_MASK_CMD | KEY_Z); - menu->add_item(RTR("Redo"), MENU_REDO, KEY_MASK_CMD | KEY_MASK_SHIFT | KEY_Z); + editable = false; // initialise to opposite first, so we get past the early-out in set_editable + set_editable(true); menu->connect("id_pressed", this, "menu_option"); expand_to_text_length = false; } diff --git a/scene/gui/line_edit.h b/scene/gui/line_edit.h index 3b29780dc0..1d33f7d4ce 100644 --- a/scene/gui/line_edit.h +++ b/scene/gui/line_edit.h @@ -34,9 +34,6 @@ #include "scene/gui/control.h" #include "scene/gui/popup_menu.h" -/** - @author Juan Linietsky <reduzio@gmail.com> -*/ class LineEdit : public Control { GDCLASS(LineEdit, Control); @@ -72,6 +69,7 @@ private: String undo_text; String text; String placeholder; + String placeholder_translated; String secret_character; float placeholder_alpha; String ime_text; @@ -126,6 +124,8 @@ private: void _emit_text_change(); bool expand_to_text_length; + void update_placeholder_width(); + bool caret_blink_enabled; bool draw_caret; bool window_has_focus; diff --git a/scene/gui/link_button.h b/scene/gui/link_button.h index ffe248ac5e..17c4bca67b 100644 --- a/scene/gui/link_button.h +++ b/scene/gui/link_button.h @@ -32,7 +32,7 @@ #define LINKBUTTON_H #include "scene/gui/base_button.h" -#include "scene/resources/bit_mask.h" +#include "scene/resources/bit_map.h" class LinkButton : public BaseButton { diff --git a/scene/gui/menu_button.cpp b/scene/gui/menu_button.cpp index b67d8c00d6..e12cd55e6f 100644 --- a/scene/gui/menu_button.cpp +++ b/scene/gui/menu_button.cpp @@ -55,8 +55,9 @@ void MenuButton::pressed() { Size2 size = get_size(); Point2 gp = get_global_position(); - popup->set_global_position(gp + Size2(0, size.height)); + popup->set_global_position(gp + Size2(0, size.height * get_global_transform().get_scale().y)); popup->set_size(Size2(size.width, 0)); + popup->set_scale(get_global_transform().get_scale()); popup->set_parent_rect(Rect2(Point2(gp - popup->get_global_position()), get_size())); popup->popup(); } @@ -91,6 +92,16 @@ bool MenuButton::is_switch_on_hover() { return switch_on_hover; } +void MenuButton::_notification(int p_what) { + + if (p_what == NOTIFICATION_VISIBILITY_CHANGED) { + + if (!is_visible_in_tree()) { + popup->hide(); + } + } +} + void MenuButton::_bind_methods() { ClassDB::bind_method(D_METHOD("get_popup"), &MenuButton::get_popup); @@ -116,15 +127,18 @@ MenuButton::MenuButton() { switch_on_hover = false; set_flat(true); + set_toggle_mode(true); set_disable_shortcuts(false); set_enabled_focus_mode(FOCUS_NONE); + set_process_unhandled_key_input(true); + set_action_mode(ACTION_MODE_BUTTON_PRESS); + popup = memnew(PopupMenu); popup->hide(); add_child(popup); - popup->set_as_toplevel(true); popup->set_pass_on_modal_close_click(false); - set_process_unhandled_key_input(true); - set_action_mode(ACTION_MODE_BUTTON_PRESS); + popup->connect("about_to_show", this, "set_pressed", varray(true)); // For when switching from another MenuButton. + popup->connect("popup_hide", this, "set_pressed", varray(false)); } MenuButton::~MenuButton() { diff --git a/scene/gui/menu_button.h b/scene/gui/menu_button.h index 794840035e..5448ff13f2 100644 --- a/scene/gui/menu_button.h +++ b/scene/gui/menu_button.h @@ -33,9 +33,7 @@ #include "scene/gui/button.h" #include "scene/gui/popup_menu.h" -/** - @author Juan Linietsky <reduzio@gmail.com> -*/ + class MenuButton : public Button { GDCLASS(MenuButton, Button); @@ -52,6 +50,7 @@ class MenuButton : public Button { void _gui_input(Ref<InputEvent> p_event); protected: + void _notification(int p_what); static void _bind_methods(); public: diff --git a/scene/gui/nine_patch_rect.h b/scene/gui/nine_patch_rect.h index ac17e52fc1..f31a09a482 100644 --- a/scene/gui/nine_patch_rect.h +++ b/scene/gui/nine_patch_rect.h @@ -32,9 +32,7 @@ #define NINE_PATCH_RECT_H #include "scene/gui/control.h" -/** - @author Juan Linietsky <reduzio@gmail.com> -*/ + class NinePatchRect : public Control { GDCLASS(NinePatchRect, Control); diff --git a/scene/gui/option_button.cpp b/scene/gui/option_button.cpp index 98f427cc07..d1840e43a3 100644 --- a/scene/gui/option_button.cpp +++ b/scene/gui/option_button.cpp @@ -43,35 +43,42 @@ Size2 OptionButton::get_minimum_size() const { void OptionButton::_notification(int p_what) { - if (p_what == NOTIFICATION_DRAW) { - - if (!has_icon("arrow")) - return; - - RID ci = get_canvas_item(); - Ref<Texture> arrow = Control::get_icon("arrow"); - Ref<StyleBox> normal = get_stylebox("normal"); - Color clr = Color(1, 1, 1); - if (get_constant("modulate_arrow")) { - switch (get_draw_mode()) { - case DRAW_PRESSED: - clr = get_color("font_color_pressed"); - break; - case DRAW_HOVER: - clr = get_color("font_color_hover"); - break; - case DRAW_DISABLED: - clr = get_color("font_color_disabled"); - break; - default: - clr = get_color("font_color"); + switch (p_what) { + case NOTIFICATION_DRAW: { + + if (!has_icon("arrow")) + return; + + RID ci = get_canvas_item(); + Ref<Texture> arrow = Control::get_icon("arrow"); + Color clr = Color(1, 1, 1); + if (get_constant("modulate_arrow")) { + switch (get_draw_mode()) { + case DRAW_PRESSED: + clr = get_color("font_color_pressed"); + break; + case DRAW_HOVER: + clr = get_color("font_color_hover"); + break; + case DRAW_DISABLED: + clr = get_color("font_color_disabled"); + break; + default: + clr = get_color("font_color"); + } } - } - Size2 size = get_size(); + Size2 size = get_size(); - Point2 ofs(size.width - arrow->get_width() - get_constant("arrow_margin"), int(Math::abs((size.height - arrow->get_height()) / 2))); - arrow->draw(ci, ofs, clr); + Point2 ofs(size.width - arrow->get_width() - get_constant("arrow_margin"), int(Math::abs((size.height - arrow->get_height()) / 2))); + arrow->draw(ci, ofs, clr); + } break; + case NOTIFICATION_VISIBILITY_CHANGED: { + + if (!is_visible_in_tree()) { + popup->hide(); + } + } break; } } @@ -81,44 +88,27 @@ void OptionButton::_focused(int p_which) { void OptionButton::_selected(int p_which) { - int selid = -1; - for (int i = 0; i < popup->get_item_count(); i++) { - - bool is_clicked = popup->get_item_id(i) == p_which; - if (is_clicked) { - selid = i; - break; - } - } - - if (selid == -1 && p_which >= 0 && p_which < popup->get_item_count()) { - _select(p_which, true); - } else { - - ERR_FAIL_COND(selid == -1); - - _select(selid, true); - } + _select(p_which, true); } void OptionButton::pressed() { Size2 size = get_size(); - popup->set_global_position(get_global_position() + Size2(0, size.height)); + popup->set_global_position(get_global_position() + Size2(0, size.height * get_global_transform().get_scale().y)); popup->set_size(Size2(size.width, 0)); - + popup->set_scale(get_global_transform().get_scale()); popup->popup(); } -void OptionButton::add_icon_item(const Ref<Texture> &p_icon, const String &p_label, int p_ID) { +void OptionButton::add_icon_item(const Ref<Texture> &p_icon, const String &p_label, int p_id) { - popup->add_icon_radio_check_item(p_icon, p_label, p_ID); + popup->add_icon_radio_check_item(p_icon, p_label, p_id); if (popup->get_item_count() == 1) select(0); } -void OptionButton::add_item(const String &p_label, int p_ID) { +void OptionButton::add_item(const String &p_label, int p_id) { - popup->add_radio_check_item(p_label, p_ID); + popup->add_radio_check_item(p_label, p_id); if (popup->get_item_count() == 1) select(0); } @@ -131,9 +121,9 @@ void OptionButton::set_item_icon(int p_idx, const Ref<Texture> &p_icon) { popup->set_item_icon(p_idx, p_icon); } -void OptionButton::set_item_id(int p_idx, int p_ID) { +void OptionButton::set_item_id(int p_idx, int p_id) { - popup->set_item_id(p_idx, p_ID); + popup->set_item_id(p_idx, p_id); } void OptionButton::set_item_metadata(int p_idx, const Variant &p_metadata) { @@ -160,6 +150,12 @@ int OptionButton::get_item_id(int p_idx) const { return popup->get_item_id(p_idx); } + +int OptionButton::get_item_index(int p_id) const { + + return popup->get_item_index(p_id); +} + Variant OptionButton::get_item_metadata(int p_idx) const { return popup->get_item_metadata(p_idx); @@ -288,7 +284,7 @@ void OptionButton::_set_items(const Array &p_items) { void OptionButton::get_translatable_strings(List<String> *p_strings) const { - return popup->get_translatable_strings(p_strings); + popup->get_translatable_strings(p_strings); } void OptionButton::_bind_methods() { @@ -306,6 +302,7 @@ void OptionButton::_bind_methods() { ClassDB::bind_method(D_METHOD("get_item_text", "idx"), &OptionButton::get_item_text); ClassDB::bind_method(D_METHOD("get_item_icon", "idx"), &OptionButton::get_item_icon); ClassDB::bind_method(D_METHOD("get_item_id", "idx"), &OptionButton::get_item_id); + ClassDB::bind_method(D_METHOD("get_item_index", "id"), &OptionButton::get_item_index); ClassDB::bind_method(D_METHOD("get_item_metadata", "idx"), &OptionButton::get_item_metadata); ClassDB::bind_method(D_METHOD("is_item_disabled", "idx"), &OptionButton::is_item_disabled); ClassDB::bind_method(D_METHOD("get_item_count"), &OptionButton::get_item_count); @@ -326,23 +323,26 @@ void OptionButton::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "items", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR | PROPERTY_USAGE_INTERNAL), "_set_items", "_get_items"); // "selected" property must come after "items", otherwise GH-10213 occurs ADD_PROPERTY(PropertyInfo(Variant::INT, "selected"), "_select_int", "get_selected"); - ADD_SIGNAL(MethodInfo("item_selected", PropertyInfo(Variant::INT, "ID"))); - ADD_SIGNAL(MethodInfo("item_focused", PropertyInfo(Variant::INT, "ID"))); + ADD_SIGNAL(MethodInfo("item_selected", PropertyInfo(Variant::INT, "id"))); + ADD_SIGNAL(MethodInfo("item_focused", PropertyInfo(Variant::INT, "id"))); } OptionButton::OptionButton() { current = -1; + set_toggle_mode(true); set_text_align(ALIGN_LEFT); set_action_mode(ACTION_MODE_BUTTON_PRESS); popup = memnew(PopupMenu); popup->hide(); add_child(popup); - popup->set_as_toplevel(true); popup->set_pass_on_modal_close_click(false); - popup->connect("id_pressed", this, "_selected"); + popup->set_notify_transform(true); + popup->set_allow_search(true); + popup->connect("index_pressed", this, "_selected"); popup->connect("id_focused", this, "_focused"); + popup->connect("popup_hide", this, "set_pressed", varray(false)); } OptionButton::~OptionButton() { diff --git a/scene/gui/option_button.h b/scene/gui/option_button.h index 121b4c002d..7210708042 100644 --- a/scene/gui/option_button.h +++ b/scene/gui/option_button.h @@ -33,9 +33,7 @@ #include "scene/gui/button.h" #include "scene/gui/popup_menu.h" -/** - @author Juan Linietsky <reduzio@gmail.com> -*/ + class OptionButton : public Button { GDCLASS(OptionButton, Button); @@ -59,18 +57,19 @@ protected: static void _bind_methods(); public: - void add_icon_item(const Ref<Texture> &p_icon, const String &p_label, int p_ID = -1); - void add_item(const String &p_label, int p_ID = -1); + void add_icon_item(const Ref<Texture> &p_icon, const String &p_label, int p_id = -1); + void add_item(const String &p_label, int p_id = -1); void set_item_text(int p_idx, const String &p_text); void set_item_icon(int p_idx, const Ref<Texture> &p_icon); - void set_item_id(int p_idx, int p_ID); + void set_item_id(int p_idx, int p_id); void set_item_metadata(int p_idx, const Variant &p_metadata); void set_item_disabled(int p_idx, bool p_disabled); String get_item_text(int p_idx) const; Ref<Texture> get_item_icon(int p_idx) const; int get_item_id(int p_idx) const; + int get_item_index(int p_id) const; Variant get_item_metadata(int p_idx) const; bool is_item_disabled(int p_idx) const; diff --git a/scene/gui/panel.h b/scene/gui/panel.h index f8d15e4261..84bf6e75f5 100644 --- a/scene/gui/panel.h +++ b/scene/gui/panel.h @@ -32,9 +32,7 @@ #define PANEL_H #include "scene/gui/control.h" -/** - @author Juan Linietsky <reduzio@gmail.com> -*/ + class Panel : public Control { GDCLASS(Panel, Control); diff --git a/scene/gui/popup.cpp b/scene/gui/popup.cpp index 2d2c54594a..32380b6457 100644 --- a/scene/gui/popup.cpp +++ b/scene/gui/popup.cpp @@ -48,6 +48,14 @@ void Popup::_notification(int p_what) { update_configuration_warning(); } + if (p_what == NOTIFICATION_EXIT_TREE) { + if (popped_up) { + popped_up = false; + notification(NOTIFICATION_POPUP_HIDE); + emit_signal("popup_hide"); + } + } + if (p_what == NOTIFICATION_ENTER_TREE) { //small helper to make editing of these easier in editor #ifdef TOOLS_ENABLED @@ -65,8 +73,8 @@ void Popup::_notification(int p_what) { void Popup::_fix_size() { Point2 pos = get_global_position(); - Size2 size = get_size(); - Point2 window_size = get_viewport_rect().size; + Size2 size = get_size() * get_scale(); + Point2 window_size = get_viewport_rect().size - get_viewport_transform().get_origin(); if (pos.x + size.width > window_size.width) pos.x = window_size.width - size.width; @@ -115,6 +123,18 @@ void Popup::set_as_minsize() { set_size(total_minsize); } +void Popup::popup_centered_clamped(const Size2 &p_size, float p_fallback_ratio) { + + Size2 popup_size = p_size; + Size2 window_size = get_viewport_rect().size; + + // clamp popup size in each dimension if window size is too small (using fallback ratio) + popup_size.x = MIN(window_size.x * p_fallback_ratio, popup_size.x); + popup_size.y = MIN(window_size.y * p_fallback_ratio, popup_size.y); + + popup_centered(popup_size); +} + void Popup::popup_centered_minsize(const Size2 &p_minsize) { set_custom_minimum_size(p_minsize); @@ -124,60 +144,44 @@ void Popup::popup_centered_minsize(const Size2 &p_minsize) { void Popup::popup_centered(const Size2 &p_size) { - Point2 window_size = get_viewport_rect().size; - - emit_signal("about_to_show"); Rect2 rect; + Size2 window_size = get_viewport_rect().size; rect.size = p_size == Size2() ? get_size() : p_size; - rect.position = ((window_size - rect.size) / 2.0).floor(); - set_position(rect.position); - set_size(rect.size); - - show_modal(exclusive); - _fix_size(); - Control *focusable = find_next_valid_focus(); - if (focusable) - focusable->grab_focus(); - - _post_popup(); - notification(NOTIFICATION_POST_POPUP); - popped_up = true; + _popup(rect, true); } void Popup::popup_centered_ratio(float p_screen_ratio) { - emit_signal("about_to_show"); - Rect2 rect; - Point2 window_size = get_viewport_rect().size; + Size2 window_size = get_viewport_rect().size; rect.size = (window_size * p_screen_ratio).floor(); rect.position = ((window_size - rect.size) / 2.0).floor(); - set_position(rect.position); - set_size(rect.size); - show_modal(exclusive); - _fix_size(); + _popup(rect, true); +} - Control *focusable = find_next_valid_focus(); - if (focusable) - focusable->grab_focus(); +void Popup::popup(const Rect2 &p_bounds) { - _post_popup(); - notification(NOTIFICATION_POST_POPUP); - popped_up = true; + _popup(p_bounds); } -void Popup::popup(const Rect2 &p_bounds) { +void Popup::_popup(const Rect2 &p_bounds, const bool p_centered) { emit_signal("about_to_show"); show_modal(exclusive); // Fit the popup into the optionally provided bounds. if (!p_bounds.has_no_area()) { - set_position(p_bounds.position); set_size(p_bounds.size); + + // check if p_bounds.size was using an outdated cached values + if (p_centered && p_bounds.size != get_size()) { + set_position(p_bounds.position - ((get_size() - p_bounds.size) / 2.0).floor()); + } else { + set_position(p_bounds.position); + } } _fix_size(); @@ -203,9 +207,11 @@ bool Popup::is_exclusive() const { void Popup::_bind_methods() { + ClassDB::bind_method(D_METHOD("set_as_minsize"), &Popup::set_as_minsize); ClassDB::bind_method(D_METHOD("popup_centered", "size"), &Popup::popup_centered, DEFVAL(Size2())); ClassDB::bind_method(D_METHOD("popup_centered_ratio", "ratio"), &Popup::popup_centered_ratio, DEFVAL(0.75)); ClassDB::bind_method(D_METHOD("popup_centered_minsize", "minsize"), &Popup::popup_centered_minsize, DEFVAL(Size2())); + ClassDB::bind_method(D_METHOD("popup_centered_clamped", "size", "fallback_ratio"), &Popup::popup_centered_clamped, DEFVAL(Size2()), DEFVAL(0.75)); ClassDB::bind_method(D_METHOD("popup", "bounds"), &Popup::popup, DEFVAL(Rect2())); ClassDB::bind_method(D_METHOD("set_exclusive", "enable"), &Popup::set_exclusive); ClassDB::bind_method(D_METHOD("is_exclusive"), &Popup::is_exclusive); @@ -229,7 +235,7 @@ Popup::Popup() { String Popup::get_configuration_warning() const { if (is_visible_in_tree()) { - return TTR("Popups will hide by default unless you call popup() or any of the popup*() functions. Making them visible for editing is fine though, but they will hide upon running."); + return TTR("Popups will hide by default unless you call popup() or any of the popup*() functions. Making them visible for editing is fine, but they will hide upon running."); } return String(); diff --git a/scene/gui/popup.h b/scene/gui/popup.h index 7ccefe1d75..925760984e 100644 --- a/scene/gui/popup.h +++ b/scene/gui/popup.h @@ -33,9 +33,6 @@ #include "scene/gui/control.h" -/** - @author Juan Linietsky <reduzio@gmail.com> -*/ class Popup : public Control { GDCLASS(Popup, Control); @@ -43,6 +40,9 @@ class Popup : public Control { bool exclusive; bool popped_up; +private: + void _popup(const Rect2 &p_bounds = Rect2(), const bool p_centered = false); + protected: virtual void _post_popup() {} @@ -64,6 +64,7 @@ public: void popup_centered(const Size2 &p_size = Size2()); void popup_centered_minsize(const Size2 &p_minsize = Size2()); void set_as_minsize(); + void popup_centered_clamped(const Size2 &p_size = Size2(), float p_fallback_ratio = 0.75); virtual void popup(const Rect2 &p_bounds = Rect2()); virtual String get_configuration_warning() const; diff --git a/scene/gui/popup_menu.cpp b/scene/gui/popup_menu.cpp index ac94ac63ec..a7c6c5ccab 100644 --- a/scene/gui/popup_menu.cpp +++ b/scene/gui/popup_menu.cpp @@ -31,6 +31,7 @@ #include "popup_menu.h" #include "core/os/input.h" #include "core/os/keyboard.h" +#include "core/os/os.h" #include "core/print_string.h" #include "core/translation.h" @@ -54,9 +55,11 @@ Size2 PopupMenu::get_minimum_size() const { Ref<Font> font = get_font("font"); float max_w = 0; + float icon_w = 0; int font_h = font->get_height(); - int check_w = MAX(get_icon("checked")->get_width(), get_icon("radio_checked")->get_width()); + int check_w = MAX(get_icon("checked")->get_width(), get_icon("radio_checked")->get_width()) + hseparation; int accel_max_w = 0; + bool has_check = false; for (int i = 0; i < items.size(); i++) { @@ -65,8 +68,7 @@ Size2 PopupMenu::get_minimum_size() const { Size2 icon_size = items[i].icon->get_size(); size.height = MAX(icon_size.height, font_h); - size.width += icon_size.width; - size.width += hseparation; + icon_w = MAX(icon_size.width + hseparation, icon_w); } else { size.height = font_h; @@ -74,10 +76,8 @@ Size2 PopupMenu::get_minimum_size() const { size.width += items[i].h_ofs; - if (items[i].checkable_type) { - - size.width += check_w + hseparation; - } + if (items[i].checkable_type) + has_check = true; String text = items[i].shortcut.is_valid() ? String(tr(items[i].shortcut->get_name())) : items[i].xl_text; size.width += font->get_string_size(text).width; @@ -91,16 +91,17 @@ Size2 PopupMenu::get_minimum_size() const { accel_max_w = MAX(accel_w, accel_max_w); } - if (items[i].submenu != "") { - + if (items[i].submenu != "") size.width += get_icon("submenu")->get_width(); - } - minsize.height += size.height; max_w = MAX(max_w, size.width); + + minsize.height += size.height; } - minsize.width += max_w + accel_max_w; + minsize.width += max_w + icon_w + accel_max_w; + if (has_check) + minsize.width += check_w; return minsize; } @@ -147,11 +148,9 @@ int PopupMenu::_get_mouse_over(const Point2 &p_over) const { void PopupMenu::_activate_submenu(int over) { Node *n = get_node(items[over].submenu); - ERR_EXPLAIN("item subnode does not exist: " + items[over].submenu); - ERR_FAIL_COND(!n); + ERR_FAIL_COND_MSG(!n, "Item subnode does not exist: " + items[over].submenu + "."); Popup *pm = Object::cast_to<Popup>(n); - ERR_EXPLAIN("item subnode is not a Popup: " + items[over].submenu); - ERR_FAIL_COND(!pm); + ERR_FAIL_COND_MSG(!pm, "Item subnode is not a Popup: " + items[over].submenu + "."); if (pm->is_visible_in_tree()) return; //already visible! @@ -159,13 +158,14 @@ void PopupMenu::_activate_submenu(int over) { Rect2 pr(p, get_size()); Ref<StyleBox> style = get_stylebox("panel"); - Point2 pos = p + Point2(get_size().width, items[over]._ofs_cache - style->get_offset().y); + Point2 pos = p + Point2(get_size().width, items[over]._ofs_cache - style->get_offset().y) * get_global_transform().get_scale(); Size2 size = pm->get_size(); // fix pos if (pos.x + size.width > get_viewport_rect().size.width) pos.x = p.x - size.width; pm->set_position(pos); + pm->set_scale(get_global_transform().get_scale()); pm->popup(); PopupMenu *pum = Object::cast_to<PopupMenu>(pm); @@ -196,11 +196,11 @@ void PopupMenu::_scroll(float p_factor, const Point2 &p_over) { int vseparation = get_constant("vseparation"); Ref<Font> font = get_font("font"); - float dy = (vseparation + font->get_height()) * 3 * p_factor; + float dy = (vseparation + font->get_height()) * 3 * p_factor * get_global_transform().get_scale().y; if (dy > 0 && global_y < 0) dy = MIN(dy, -global_y - 1); - else if (dy < 0 && global_y + get_size().y > get_viewport_rect().size.y) - dy = -MIN(-dy, global_y + get_size().y - get_viewport_rect().size.y - 1); + else if (dy < 0 && global_y + get_size().y * get_global_transform().get_scale().y > get_viewport_rect().size.y) + dy = -MIN(-dy, global_y + get_size().y * get_global_transform().get_scale().y - get_viewport_rect().size.y - 1); set_position(get_position() + Vector2(0, dy)); Ref<InputEventMouseMotion> ie; @@ -239,7 +239,7 @@ void PopupMenu::_gui_input(const Ref<InputEvent> &p_event) { for (int i = search_from; i >= 0; i--) { - if (i < 0 || i >= items.size()) + if (i >= items.size()) continue; if (!items[i].separator && !items[i].disabled) { @@ -289,7 +289,7 @@ void PopupMenu::_gui_input(const Ref<InputEvent> &p_event) { case BUTTON_WHEEL_DOWN: { - if (get_global_position().y + get_size().y > get_viewport_rect().size.y) { + if (get_global_position().y + get_size().y * get_global_transform().get_scale().y > get_viewport_rect().size.y) { _scroll(-b->get_factor(), b->get_position()); } } break; @@ -354,7 +354,7 @@ void PopupMenu::_gui_input(const Ref<InputEvent> &p_event) { } int over = _get_mouse_over(m->get_position()); - int id = (over < 0 || items[over].separator || items[over].disabled) ? -1 : (items[over].ID >= 0 ? items[over].ID : over); + int id = (over < 0 || items[over].separator || items[over].disabled) ? -1 : (items[over].id >= 0 ? items[over].id : over); if (id < 0) { mouse_over = -1; @@ -379,6 +379,43 @@ void PopupMenu::_gui_input(const Ref<InputEvent> &p_event) { _scroll(-pan_gesture->get_delta().y, pan_gesture->get_position()); } } + + Ref<InputEventKey> k = p_event; + + if (allow_search && k.is_valid() && k->get_unicode()) { + + uint64_t now = OS::get_singleton()->get_ticks_msec(); + uint64_t diff = now - search_time_msec; + uint64_t max_interval = uint64_t(GLOBAL_DEF("gui/timers/incremental_search_max_interval_msec", 2000)); + search_time_msec = now; + + if (diff > max_interval) { + search_string = ""; + } + + if (String::chr(k->get_unicode()) != search_string) + search_string += String::chr(k->get_unicode()); + + for (int i = mouse_over + 1; i <= items.size(); i++) { + if (i == items.size()) { + if (mouse_over <= 0) + break; + else + i = 0; + } + + if (i == mouse_over) + break; + + if (items[i].text.findn(search_string) == 0) { + mouse_over = i; + emit_signal("id_focused", i); + update(); + accept_event(); + break; + } + } + } } bool PopupMenu::has_point(const Point2 &p_point) const { @@ -415,7 +452,6 @@ void PopupMenu::_notification(int p_what) { minimum_size_changed(); update(); - } break; case NOTIFICATION_DRAW: { @@ -443,15 +479,31 @@ void PopupMenu::_notification(int p_what) { Color font_color_hover = get_color("font_color_hover"); float font_h = font->get_height(); + // Add the check and the wider icon to the offset of all items. + float icon_ofs = 0.0; + bool has_check = false; + for (int i = 0; i < items.size(); i++) { + + if (!items[i].icon.is_null()) + icon_ofs = MAX(items[i].icon->get_size().width, icon_ofs); + + if (items[i].checkable_type) + has_check = true; + } + if (icon_ofs > 0.0) + icon_ofs += hseparation; + + float check_ofs = 0.0; + if (has_check) + check_ofs = MAX(get_icon("checked")->get_width(), get_icon("radio_checked")->get_width()) + hseparation; + for (int i = 0; i < items.size(); i++) { if (i > 0) ofs.y += vseparation; Point2 item_ofs = ofs; - float h; Size2 icon_size; - - Color icon_color(1, 1, 1, items[i].disabled ? 0.5 : 1); + float h; if (!items[i].icon.is_null()) { @@ -489,16 +541,15 @@ void PopupMenu::_notification(int p_what) { } } + Color icon_color(1, 1, 1, items[i].disabled ? 0.5 : 1); + if (items[i].checkable_type) { Texture *icon = (items[i].checked ? check[items[i].checkable_type - 1] : uncheck[items[i].checkable_type - 1]).ptr(); icon->draw(ci, item_ofs + Point2(0, Math::floor((h - icon->get_height()) / 2.0)), icon_color); - item_ofs.x += icon->get_width() + hseparation; } if (!items[i].icon.is_null()) { - items[i].icon->draw(ci, item_ofs + Point2(0, Math::floor((h - icon_size.height) / 2.0)), icon_color); - item_ofs.x += items[i].icon->get_width(); - item_ofs.x += hseparation; + items[i].icon->draw(ci, item_ofs + Size2(check_ofs, 0) + Point2(0, Math::floor((h - icon_size.height) / 2.0)), icon_color); } if (items[i].submenu != "") { @@ -514,21 +565,21 @@ void PopupMenu::_notification(int p_what) { } } else { + item_ofs.x += icon_ofs + check_ofs; font->draw(ci, item_ofs + Point2(0, Math::floor((h - font_h) / 2.0)), text, items[i].disabled ? font_color_disabled : (i == mouse_over ? font_color_hover : font_color)); } if (items[i].accel || (items[i].shortcut.is_valid() && items[i].shortcut->is_valid())) { //accelerator - String text = _get_accel_text(i); - item_ofs.x = size.width - style->get_margin(MARGIN_RIGHT) - font->get_string_size(text).width; - font->draw(ci, item_ofs + Point2(0, Math::floor((h - font_h) / 2.0)), text, i == mouse_over ? font_color_hover : font_color_accel); + String text2 = _get_accel_text(i); + item_ofs.x = size.width - style->get_margin(MARGIN_RIGHT) - font->get_string_size(text2).width; + font->draw(ci, item_ofs + Point2(0, Math::floor((h - font_h) / 2.0)), text2, i == mouse_over ? font_color_hover : font_color_accel); } items.write[i]._ofs_cache = ofs.y; ofs.y += h; } - } break; case MainLoop::NOTIFICATION_WM_FOCUS_OUT: { @@ -576,93 +627,93 @@ void PopupMenu::_notification(int p_what) { } } -void PopupMenu::add_icon_item(const Ref<Texture> &p_icon, const String &p_label, int p_ID, uint32_t p_accel) { +void PopupMenu::add_icon_item(const Ref<Texture> &p_icon, const String &p_label, int p_id, uint32_t p_accel) { Item item; item.icon = p_icon; item.text = p_label; item.xl_text = tr(p_label); item.accel = p_accel; - item.ID = p_ID; + item.id = p_id; items.push_back(item); update(); minimum_size_changed(); } -void PopupMenu::add_item(const String &p_label, int p_ID, uint32_t p_accel) { +void PopupMenu::add_item(const String &p_label, int p_id, uint32_t p_accel) { Item item; item.text = p_label; item.xl_text = tr(p_label); item.accel = p_accel; - item.ID = p_ID; + item.id = p_id == -1 ? items.size() : p_id; items.push_back(item); update(); minimum_size_changed(); } -void PopupMenu::add_submenu_item(const String &p_label, const String &p_submenu, int p_ID) { +void PopupMenu::add_submenu_item(const String &p_label, const String &p_submenu, int p_id) { Item item; item.text = p_label; item.xl_text = tr(p_label); - item.ID = p_ID; + item.id = p_id; item.submenu = p_submenu; items.push_back(item); update(); minimum_size_changed(); } -void PopupMenu::add_icon_check_item(const Ref<Texture> &p_icon, const String &p_label, int p_ID, uint32_t p_accel) { +void PopupMenu::add_icon_check_item(const Ref<Texture> &p_icon, const String &p_label, int p_id, uint32_t p_accel) { Item item; item.icon = p_icon; item.text = p_label; item.xl_text = tr(p_label); item.accel = p_accel; - item.ID = p_ID; + item.id = p_id; item.checkable_type = Item::CHECKABLE_TYPE_CHECK_BOX; items.push_back(item); update(); minimum_size_changed(); } -void PopupMenu::add_check_item(const String &p_label, int p_ID, uint32_t p_accel) { +void PopupMenu::add_check_item(const String &p_label, int p_id, uint32_t p_accel) { Item item; item.text = p_label; item.xl_text = tr(p_label); item.accel = p_accel; - item.ID = p_ID; + item.id = p_id == -1 ? items.size() : p_id; item.checkable_type = Item::CHECKABLE_TYPE_CHECK_BOX; items.push_back(item); update(); minimum_size_changed(); } -void PopupMenu::add_radio_check_item(const String &p_label, int p_ID, uint32_t p_accel) { +void PopupMenu::add_radio_check_item(const String &p_label, int p_id, uint32_t p_accel) { - add_check_item(p_label, p_ID, p_accel); + add_check_item(p_label, p_id, p_accel); items.write[items.size() - 1].checkable_type = Item::CHECKABLE_TYPE_RADIO_BUTTON; update(); minimum_size_changed(); } -void PopupMenu::add_icon_radio_check_item(const Ref<Texture> &p_icon, const String &p_label, int p_ID, uint32_t p_accel) { +void PopupMenu::add_icon_radio_check_item(const Ref<Texture> &p_icon, const String &p_label, int p_id, uint32_t p_accel) { - add_icon_check_item(p_icon, p_label, p_ID, p_accel); + add_icon_check_item(p_icon, p_label, p_id, p_accel); items.write[items.size() - 1].checkable_type = Item::CHECKABLE_TYPE_RADIO_BUTTON; update(); minimum_size_changed(); } -void PopupMenu::add_icon_shortcut(const Ref<Texture> &p_icon, const Ref<ShortCut> &p_shortcut, int p_ID, bool p_global) { +void PopupMenu::add_icon_shortcut(const Ref<Texture> &p_icon, const Ref<ShortCut> &p_shortcut, int p_id, bool p_global) { ERR_FAIL_COND(p_shortcut.is_null()); _ref_shortcut(p_shortcut); Item item; - item.ID = p_ID; + item.id = p_id; item.icon = p_icon; item.shortcut = p_shortcut; item.shortcut_is_global = p_global; @@ -671,14 +722,14 @@ void PopupMenu::add_icon_shortcut(const Ref<Texture> &p_icon, const Ref<ShortCut minimum_size_changed(); } -void PopupMenu::add_shortcut(const Ref<ShortCut> &p_shortcut, int p_ID, bool p_global) { +void PopupMenu::add_shortcut(const Ref<ShortCut> &p_shortcut, int p_id, bool p_global) { ERR_FAIL_COND(p_shortcut.is_null()); _ref_shortcut(p_shortcut); Item item; - item.ID = p_ID; + item.id = p_id; item.shortcut = p_shortcut; item.shortcut_is_global = p_global; items.push_back(item); @@ -686,14 +737,14 @@ void PopupMenu::add_shortcut(const Ref<ShortCut> &p_shortcut, int p_ID, bool p_g minimum_size_changed(); } -void PopupMenu::add_icon_check_shortcut(const Ref<Texture> &p_icon, const Ref<ShortCut> &p_shortcut, int p_ID, bool p_global) { +void PopupMenu::add_icon_check_shortcut(const Ref<Texture> &p_icon, const Ref<ShortCut> &p_shortcut, int p_id, bool p_global) { ERR_FAIL_COND(p_shortcut.is_null()); _ref_shortcut(p_shortcut); Item item; - item.ID = p_ID; + item.id = p_id; item.shortcut = p_shortcut; item.checkable_type = Item::CHECKABLE_TYPE_CHECK_BOX; item.icon = p_icon; @@ -703,14 +754,14 @@ void PopupMenu::add_icon_check_shortcut(const Ref<Texture> &p_icon, const Ref<Sh minimum_size_changed(); } -void PopupMenu::add_check_shortcut(const Ref<ShortCut> &p_shortcut, int p_ID, bool p_global) { +void PopupMenu::add_check_shortcut(const Ref<ShortCut> &p_shortcut, int p_id, bool p_global) { ERR_FAIL_COND(p_shortcut.is_null()); _ref_shortcut(p_shortcut); Item item; - item.ID = p_ID; + item.id = p_id; item.shortcut = p_shortcut; item.shortcut_is_global = p_global; item.checkable_type = Item::CHECKABLE_TYPE_CHECK_BOX; @@ -719,21 +770,21 @@ void PopupMenu::add_check_shortcut(const Ref<ShortCut> &p_shortcut, int p_ID, bo minimum_size_changed(); } -void PopupMenu::add_radio_check_shortcut(const Ref<ShortCut> &p_shortcut, int p_ID, bool p_global) { +void PopupMenu::add_radio_check_shortcut(const Ref<ShortCut> &p_shortcut, int p_id, bool p_global) { - add_check_shortcut(p_shortcut, p_ID, p_global); + add_check_shortcut(p_shortcut, p_id, p_global); items.write[items.size() - 1].checkable_type = Item::CHECKABLE_TYPE_RADIO_BUTTON; update(); minimum_size_changed(); } -void PopupMenu::add_multistate_item(const String &p_label, int p_max_states, int p_default_state, int p_ID, uint32_t p_accel) { +void PopupMenu::add_multistate_item(const String &p_label, int p_max_states, int p_default_state, int p_id, uint32_t p_accel) { Item item; item.text = p_label; item.xl_text = tr(p_label); item.accel = p_accel; - item.ID = p_ID; + item.id = p_id; item.max_states = p_max_states; item.state = p_default_state; items.push_back(item); @@ -767,10 +818,10 @@ void PopupMenu::set_item_checked(int p_idx, bool p_checked) { update(); minimum_size_changed(); } -void PopupMenu::set_item_id(int p_idx, int p_ID) { +void PopupMenu::set_item_id(int p_idx, int p_id) { ERR_FAIL_INDEX(p_idx, items.size()); - items.write[p_idx].ID = p_ID; + items.write[p_idx].id = p_id; update(); minimum_size_changed(); @@ -866,14 +917,14 @@ bool PopupMenu::is_item_checked(int p_idx) const { int PopupMenu::get_item_id(int p_idx) const { ERR_FAIL_INDEX_V(p_idx, items.size(), 0); - return items[p_idx].ID; + return items[p_idx].id; } -int PopupMenu::get_item_index(int p_ID) const { +int PopupMenu::get_item_index(int p_id) const { for (int i = 0; i < items.size(); i++) { - if (items[i].ID == p_ID) + if (items[i].id == p_id) return i; } @@ -1062,7 +1113,7 @@ void PopupMenu::activate_item(int p_item) { ERR_FAIL_INDEX(p_item, items.size()); ERR_FAIL_COND(items[p_item].separator); - int id = items[p_item].ID >= 0 ? items[p_item].ID : p_item; + int id = items[p_item].id >= 0 ? items[p_item].id : p_item; //hide all parent PopupMenus Node *next = get_parent(); @@ -1124,7 +1175,7 @@ void PopupMenu::add_separator(const String &p_text) { Item sep; sep.separator = true; - sep.ID = -1; + sep.id = -1; if (p_text != String()) { sep.text = p_text; sep.xl_text = tr(p_text); @@ -1274,6 +1325,16 @@ float PopupMenu::get_submenu_popup_delay() const { return submenu_timer->get_wait_time(); } +void PopupMenu::set_allow_search(bool p_allow) { + + allow_search = p_allow; +} + +bool PopupMenu::get_allow_search() const { + + return allow_search; +} + void PopupMenu::set_hide_on_window_lose_focus(bool p_enabled) { hide_on_window_lose_focus = p_enabled; @@ -1392,6 +1453,9 @@ void PopupMenu::_bind_methods() { ClassDB::bind_method(D_METHOD("set_hide_on_window_lose_focus", "enable"), &PopupMenu::set_hide_on_window_lose_focus); ClassDB::bind_method(D_METHOD("is_hide_on_window_lose_focus"), &PopupMenu::is_hide_on_window_lose_focus); + ClassDB::bind_method(D_METHOD("set_allow_search", "allow"), &PopupMenu::set_allow_search); + ClassDB::bind_method(D_METHOD("get_allow_search"), &PopupMenu::get_allow_search); + ClassDB::bind_method(D_METHOD("_submenu_timeout"), &PopupMenu::_submenu_timeout); ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "items", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR | PROPERTY_USAGE_INTERNAL), "_set_items", "_get_items"); @@ -1399,9 +1463,10 @@ void PopupMenu::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::BOOL, "hide_on_checkable_item_selection"), "set_hide_on_checkable_item_selection", "is_hide_on_checkable_item_selection"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "hide_on_state_item_selection"), "set_hide_on_state_item_selection", "is_hide_on_state_item_selection"); ADD_PROPERTY(PropertyInfo(Variant::REAL, "submenu_popup_delay"), "set_submenu_popup_delay", "get_submenu_popup_delay"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "allow_search"), "set_allow_search", "get_allow_search"); - ADD_SIGNAL(MethodInfo("id_pressed", PropertyInfo(Variant::INT, "ID"))); - ADD_SIGNAL(MethodInfo("id_focused", PropertyInfo(Variant::INT, "ID"))); + ADD_SIGNAL(MethodInfo("id_pressed", PropertyInfo(Variant::INT, "id"))); + ADD_SIGNAL(MethodInfo("id_focused", PropertyInfo(Variant::INT, "id"))); ADD_SIGNAL(MethodInfo("index_pressed", PropertyInfo(Variant::INT, "index"))); } @@ -1420,6 +1485,10 @@ PopupMenu::PopupMenu() { initial_button_mask = 0; during_grabbed_click = false; + allow_search = false; + search_time_msec = 0; + search_string = ""; + set_focus_mode(FOCUS_ALL); set_as_toplevel(true); set_hide_on_item_selection(true); diff --git a/scene/gui/popup_menu.h b/scene/gui/popup_menu.h index 767ff568fe..8bfe8fc607 100644 --- a/scene/gui/popup_menu.h +++ b/scene/gui/popup_menu.h @@ -33,10 +33,6 @@ #include "scene/gui/popup.h" -/** - @author Juan Linietsky <reduzio@gmail.com> -*/ - class PopupMenu : public Popup { GDCLASS(PopupMenu, Popup); @@ -55,7 +51,7 @@ class PopupMenu : public Popup { int state; bool separator; bool disabled; - int ID; + int id; Variant metadata; String submenu; String tooltip; @@ -112,6 +108,10 @@ class PopupMenu : public Popup { void _ref_shortcut(Ref<ShortCut> p_sc); void _unref_shortcut(Ref<ShortCut> p_sc); + bool allow_search; + uint64_t search_time_msec; + String search_string; + protected: virtual bool has_point(const Point2 &p_point) const; @@ -120,26 +120,26 @@ protected: static void _bind_methods(); public: - void add_icon_item(const Ref<Texture> &p_icon, const String &p_label, int p_ID = -1, uint32_t p_accel = 0); - void add_item(const String &p_label, int p_ID = -1, uint32_t p_accel = 0); - void add_icon_check_item(const Ref<Texture> &p_icon, const String &p_label, int p_ID = -1, uint32_t p_accel = 0); - void add_check_item(const String &p_label, int p_ID = -1, uint32_t p_accel = 0); - void add_radio_check_item(const String &p_label, int p_ID = -1, uint32_t p_accel = 0); - void add_icon_radio_check_item(const Ref<Texture> &p_icon, const String &p_label, int p_ID = -1, uint32_t p_accel = 0); - void add_submenu_item(const String &p_label, const String &p_submenu, int p_ID = -1); - - void add_icon_shortcut(const Ref<Texture> &p_icon, const Ref<ShortCut> &p_shortcut, int p_ID = -1, bool p_global = false); - void add_shortcut(const Ref<ShortCut> &p_shortcut, int p_ID = -1, bool p_global = false); - void add_icon_check_shortcut(const Ref<Texture> &p_icon, const Ref<ShortCut> &p_shortcut, int p_ID = -1, bool p_global = false); - void add_check_shortcut(const Ref<ShortCut> &p_shortcut, int p_ID = -1, bool p_global = false); - void add_radio_check_shortcut(const Ref<ShortCut> &p_shortcut, int p_ID = -1, bool p_global = false); - - void add_multistate_item(const String &p_label, int p_max_states, int p_default_state, int p_ID = -1, uint32_t p_accel = 0); + void add_icon_item(const Ref<Texture> &p_icon, const String &p_label, int p_id = -1, uint32_t p_accel = 0); + void add_item(const String &p_label, int p_id = -1, uint32_t p_accel = 0); + void add_icon_check_item(const Ref<Texture> &p_icon, const String &p_label, int p_id = -1, uint32_t p_accel = 0); + void add_check_item(const String &p_label, int p_id = -1, uint32_t p_accel = 0); + void add_radio_check_item(const String &p_label, int p_id = -1, uint32_t p_accel = 0); + void add_icon_radio_check_item(const Ref<Texture> &p_icon, const String &p_label, int p_id = -1, uint32_t p_accel = 0); + void add_submenu_item(const String &p_label, const String &p_submenu, int p_id = -1); + + void add_icon_shortcut(const Ref<Texture> &p_icon, const Ref<ShortCut> &p_shortcut, int p_id = -1, bool p_global = false); + void add_shortcut(const Ref<ShortCut> &p_shortcut, int p_id = -1, bool p_global = false); + void add_icon_check_shortcut(const Ref<Texture> &p_icon, const Ref<ShortCut> &p_shortcut, int p_id = -1, bool p_global = false); + void add_check_shortcut(const Ref<ShortCut> &p_shortcut, int p_id = -1, bool p_global = false); + void add_radio_check_shortcut(const Ref<ShortCut> &p_shortcut, int p_id = -1, bool p_global = false); + + void add_multistate_item(const String &p_label, int p_max_states, int p_default_state, int p_id = -1, uint32_t p_accel = 0); void set_item_text(int p_idx, const String &p_text); void set_item_icon(int p_idx, const Ref<Texture> &p_icon); void set_item_checked(int p_idx, bool p_checked); - void set_item_id(int p_idx, int p_ID); + void set_item_id(int p_idx, int p_id); void set_item_accelerator(int p_idx, uint32_t p_accel); void set_item_metadata(int p_idx, const Variant &p_meta); void set_item_disabled(int p_idx, bool p_disabled); @@ -161,7 +161,7 @@ public: Ref<Texture> get_item_icon(int p_idx) const; bool is_item_checked(int p_idx) const; int get_item_id(int p_idx) const; - int get_item_index(int p_ID) const; + int get_item_index(int p_id) const; uint32_t get_item_accelerator(int p_idx) const; Variant get_item_metadata(int p_idx) const; bool is_item_disabled(int p_idx) const; @@ -206,6 +206,9 @@ public: void set_submenu_popup_delay(float p_time); float get_submenu_popup_delay() const; + void set_allow_search(bool p_allow); + bool get_allow_search() const; + virtual void popup(const Rect2 &p_bounds = Rect2()); void set_hide_on_window_lose_focus(bool p_enabled); diff --git a/scene/gui/progress_bar.cpp b/scene/gui/progress_bar.cpp index 2195ec9778..0154a452ad 100644 --- a/scene/gui/progress_bar.cpp +++ b/scene/gui/progress_bar.cpp @@ -39,9 +39,12 @@ Size2 ProgressBar::get_minimum_size() const { Size2 minimum_size = bg->get_minimum_size(); minimum_size.height = MAX(minimum_size.height, fg->get_minimum_size().height); minimum_size.width = MAX(minimum_size.width, fg->get_minimum_size().width); - //if (percent_visible) { this is needed, else the progressbar will collapse - minimum_size.height = MAX(minimum_size.height, bg->get_minimum_size().height + font->get_height()); - //} + if (percent_visible) { + minimum_size.height = MAX(minimum_size.height, bg->get_minimum_size().height + font->get_height()); + } else { // this is needed, else the progressbar will collapse + minimum_size.width = MAX(minimum_size.width, 1); + minimum_size.height = MAX(minimum_size.height, 1); + } return minimum_size; } @@ -57,7 +60,7 @@ void ProgressBar::_notification(int p_what) { draw_style_box(bg, Rect2(Point2(), get_size())); float r = get_as_ratio(); int mp = fg->get_minimum_size().width; - int p = r * get_size().width - mp; + int p = r * (get_size().width - mp); if (p > 0) { draw_style_box(fg, Rect2(Point2(), Size2(p + fg->get_minimum_size().width, get_size().height))); diff --git a/scene/gui/range.cpp b/scene/gui/range.cpp index c24e62c8cb..ed5dd77f53 100644 --- a/scene/gui/range.cpp +++ b/scene/gui/range.cpp @@ -35,9 +35,9 @@ String Range::get_configuration_warning() const { if (shared->exp_ratio && shared->min <= 0) { if (warning != String()) { - warning += "\n"; + warning += "\n\n"; } - warning += TTR("If exp_edit is true min_value must be > 0."); + warning += TTR("If \"Exp Edit\" is enabled, \"Min Value\" must be greater than 0."); } return warning; @@ -63,7 +63,7 @@ void Range::Shared::emit_value_changed() { void Range::_changed_notify(const char *p_what) { - emit_signal("changed", shared->val); + emit_signal("changed"); update(); _change_notify(p_what); } @@ -79,6 +79,7 @@ void Range::Shared::emit_changed(const char *p_what) { } void Range::set_value(double p_val) { + if (shared->step > 0) p_val = Math::round(p_val / shared->step) * shared->step; @@ -179,12 +180,12 @@ double Range::get_as_ratio() const { float value = CLAMP(get_value(), shared->min, shared->max); double v = Math::log(value) / Math::log((double)2); - return (v - exp_min) / (exp_max - exp_min); + return CLAMP((v - exp_min) / (exp_max - exp_min), 0, 1); } else { float value = CLAMP(get_value(), shared->min, shared->max); - return (value - get_min()) / (get_max() - get_min()); + return CLAMP((value - get_min()) / (get_max() - get_min()), 0, 1); } } @@ -303,22 +304,27 @@ bool Range::is_ratio_exp() const { } void Range::set_allow_greater(bool p_allow) { + shared->allow_greater = p_allow; } bool Range::is_greater_allowed() const { + return shared->allow_greater; } void Range::set_allow_lesser(bool p_allow) { + shared->allow_lesser = p_allow; } bool Range::is_lesser_allowed() const { + return shared->allow_lesser; } Range::Range() { + shared = memnew(Shared); shared->min = 0; shared->max = 100; diff --git a/scene/gui/range.h b/scene/gui/range.h index cf0add8c89..8ce450f8fc 100644 --- a/scene/gui/range.h +++ b/scene/gui/range.h @@ -32,9 +32,7 @@ #define RANGE_H #include "scene/gui/control.h" -/** - @author Juan Linietsky <reduzio@gmail.com> -*/ + class Range : public Control { GDCLASS(Range, Control); diff --git a/scene/gui/reference_rect.cpp b/scene/gui/reference_rect.cpp index 553e133946..052c8ccd05 100644 --- a/scene/gui/reference_rect.cpp +++ b/scene/gui/reference_rect.cpp @@ -38,26 +38,41 @@ void ReferenceRect::_notification(int p_what) { if (!is_inside_tree()) return; - if (Engine::get_singleton()->is_editor_hint()) + if (Engine::get_singleton()->is_editor_hint() || !editor_only) draw_rect(Rect2(Point2(), get_size()), border_color, false); } } -void ReferenceRect::set_border_color(const Color &color) { - border_color = color; +void ReferenceRect::set_border_color(const Color &p_color) { + border_color = p_color; + update(); } Color ReferenceRect::get_border_color() const { return border_color; } +void ReferenceRect::set_editor_only(const bool &p_enabled) { + editor_only = p_enabled; + update(); +} + +bool ReferenceRect::get_editor_only() const { + return editor_only; +} + void ReferenceRect::_bind_methods() { ClassDB::bind_method(D_METHOD("get_border_color"), &ReferenceRect::get_border_color); ClassDB::bind_method(D_METHOD("set_border_color", "color"), &ReferenceRect::set_border_color); + ClassDB::bind_method(D_METHOD("get_editor_only"), &ReferenceRect::get_editor_only); + ClassDB::bind_method(D_METHOD("set_editor_only", "enabled"), &ReferenceRect::set_editor_only); + ADD_PROPERTY(PropertyInfo(Variant::COLOR, "border_color"), "set_border_color", "get_border_color"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "editor_only"), "set_editor_only", "get_editor_only"); } ReferenceRect::ReferenceRect() { border_color = Color(1, 0, 0); + editor_only = true; } diff --git a/scene/gui/reference_rect.h b/scene/gui/reference_rect.h index de3ccaeeca..7a88333cf2 100644 --- a/scene/gui/reference_rect.h +++ b/scene/gui/reference_rect.h @@ -37,6 +37,7 @@ class ReferenceRect : public Control { GDCLASS(ReferenceRect, Control); Color border_color; + bool editor_only; protected: void _notification(int p_what); @@ -45,8 +46,11 @@ protected: public: ReferenceRect(); - void set_border_color(const Color &color); + void set_border_color(const Color &p_color); Color get_border_color() const; + + void set_editor_only(const bool &p_enabled); + bool get_editor_only() const; }; #endif // REFERENCE_RECT_H diff --git a/scene/gui/rich_text_label.cpp b/scene/gui/rich_text_label.cpp index 2e466fd582..8223ea6d1e 100644 --- a/scene/gui/rich_text_label.cpp +++ b/scene/gui/rich_text_label.cpp @@ -307,6 +307,25 @@ int RichTextLabel::_process_line(ItemFrame *p_frame, const Vector2 &p_ofs, int & switch (it->type) { + case ITEM_ALIGN: { + + ItemAlign *align_it = static_cast<ItemAlign *>(it); + + align = align_it->align; + + } break; + case ITEM_INDENT: { + + if (it != l.from) { + ItemIndent *indent_it = static_cast<ItemIndent *>(it); + + int indent = indent_it->level * tab_size * cfont->get_char_size(' ').width; + margin += indent; + begin += indent; + wofs += indent; + } + + } break; case ITEM_TEXT: { ItemText *text = static_cast<ItemText *>(it); @@ -440,14 +459,13 @@ int RichTextLabel::_process_line(ItemFrame *p_frame, const Vector2 &p_ofs, int & if (p_font_color_shadow.a > 0) { float x_ofs_shadow = align_ofs + pofs; float y_ofs_shadow = y + lh - line_descent; - float move = font->draw_char(ci, Point2(x_ofs_shadow, y_ofs_shadow) + shadow_ofs, c[i], c[i + 1], p_font_color_shadow); + font->draw_char(ci, Point2(x_ofs_shadow, y_ofs_shadow) + shadow_ofs, c[i], c[i + 1], p_font_color_shadow); if (p_shadow_as_outline) { font->draw_char(ci, Point2(x_ofs_shadow, y_ofs_shadow) + Vector2(-shadow_ofs.x, shadow_ofs.y), c[i], c[i + 1], p_font_color_shadow); font->draw_char(ci, Point2(x_ofs_shadow, y_ofs_shadow) + Vector2(shadow_ofs.x, -shadow_ofs.y), c[i], c[i + 1], p_font_color_shadow); font->draw_char(ci, Point2(x_ofs_shadow, y_ofs_shadow) + Vector2(-shadow_ofs.x, -shadow_ofs.y), c[i], c[i + 1], p_font_color_shadow); } - x_ofs_shadow += move; } if (selected) { @@ -544,7 +562,7 @@ int RichTextLabel::_process_line(ItemFrame *p_frame, const Vector2 &p_ofs, int & Vector2 draw_ofs = Point2(wofs, y); Color font_color_shadow = get_color("font_color_shadow"); bool use_outline = get_constant("shadow_as_outline"); - Point2 shadow_ofs(get_constant("shadow_offset_x"), get_constant("shadow_offset_y")); + Point2 shadow_ofs2(get_constant("shadow_offset_x"), get_constant("shadow_offset_y")); if (p_mode == PROCESS_CACHE) { @@ -568,7 +586,7 @@ int RichTextLabel::_process_line(ItemFrame *p_frame, const Vector2 &p_ofs, int & for (int i = 0; i < frame->lines.size(); i++) { - _process_line(frame, Point2(), ly, available_width, i, PROCESS_CACHE, cfont, Color(), font_color_shadow, use_outline, shadow_ofs); + _process_line(frame, Point2(), ly, available_width, i, PROCESS_CACHE, cfont, Color(), font_color_shadow, use_outline, shadow_ofs2); table->columns.write[column].min_width = MAX(table->columns[column].min_width, frame->lines[i].minimum_width); table->columns.write[column].max_width = MAX(table->columns[column].max_width, frame->lines[i].maximum_width); } @@ -592,7 +610,7 @@ int RichTextLabel::_process_line(ItemFrame *p_frame, const Vector2 &p_ofs, int & //assign actual widths for (int i = 0; i < table->columns.size(); i++) { table->columns.write[i].width = table->columns[i].min_width; - if (table->columns[i].expand) + if (table->columns[i].expand && total_ratio > 0) table->columns.write[i].width += table->columns[i].expand_ratio * remaining_width / total_ratio; table->total_width += table->columns[i].width + hseparation; } @@ -641,7 +659,7 @@ int RichTextLabel::_process_line(ItemFrame *p_frame, const Vector2 &p_ofs, int & for (int i = 0; i < frame->lines.size(); i++) { int ly = 0; - _process_line(frame, Point2(), ly, table->columns[column].width, i, PROCESS_CACHE, cfont, Color(), font_color_shadow, use_outline, shadow_ofs); + _process_line(frame, Point2(), ly, table->columns[column].width, i, PROCESS_CACHE, cfont, Color(), font_color_shadow, use_outline, shadow_ofs2); frame->lines.write[i].height_cache = ly; //actual height frame->lines.write[i].height_accum_cache = ly; //actual height } @@ -674,9 +692,9 @@ int RichTextLabel::_process_line(ItemFrame *p_frame, const Vector2 &p_ofs, int & if (visible) { if (p_mode == PROCESS_DRAW) { - nonblank_line_count += _process_line(frame, p_ofs + offset + draw_ofs + Vector2(0, yofs), ly, table->columns[column].width, i, PROCESS_DRAW, cfont, ccolor, font_color_shadow, use_outline, shadow_ofs); + nonblank_line_count += _process_line(frame, p_ofs + offset + draw_ofs + Vector2(0, yofs), ly, table->columns[column].width, i, PROCESS_DRAW, cfont, ccolor, font_color_shadow, use_outline, shadow_ofs2); } else if (p_mode == PROCESS_POINTER) { - _process_line(frame, p_ofs + offset + draw_ofs + Vector2(0, yofs), ly, table->columns[column].width, i, PROCESS_POINTER, cfont, ccolor, font_color_shadow, use_outline, shadow_ofs, p_click_pos, r_click_item, r_click_char, r_outside); + _process_line(frame, p_ofs + offset + draw_ofs + Vector2(0, yofs), ly, table->columns[column].width, i, PROCESS_POINTER, cfont, ccolor, font_color_shadow, use_outline, shadow_ofs2, p_click_pos, r_click_item, r_click_char, r_outside); if (r_click_item && *r_click_item) { RETURN; // exit early } @@ -711,7 +729,8 @@ int RichTextLabel::_process_line(ItemFrame *p_frame, const Vector2 &p_ofs, int & } break; - default: {} + default: { + } } Item *itp = it; @@ -752,6 +771,8 @@ void RichTextLabel::_scroll_changed(double) { else scroll_following = false; + scroll_updated = true; + update(); } @@ -777,7 +798,6 @@ void RichTextLabel::_update_scroll() { main->first_invalid_line = 0; //invalidate ALL _validate_line_caches(main); } - scroll_updated = true; } void RichTextLabel::_notification(int p_what) { @@ -801,11 +821,7 @@ void RichTextLabel::_notification(int p_what) { } break; case NOTIFICATION_THEME_CHANGED: { - if (is_inside_tree() && use_bbcode) { - parse_bbcode(bbcode); - //first_invalid_line=0; //invalidate ALL - //update(); - } + update(); } break; case NOTIFICATION_DRAW: { @@ -926,84 +942,80 @@ void RichTextLabel::_gui_input(Ref<InputEvent> p_event) { return; if (b->get_button_index() == BUTTON_LEFT) { + if (b->is_pressed() && !b->is_doubleclick()) { + scroll_updated = false; + int line = 0; + Item *item = NULL; - if (true) { - - if (b->is_pressed() && !b->is_doubleclick()) { - scroll_updated = false; - int line = 0; - Item *item = NULL; - - bool outside; - _find_click(main, b->get_position(), &item, &line, &outside); + bool outside; + _find_click(main, b->get_position(), &item, &line, &outside); - if (item) { + if (item) { - if (selection.enabled) { + if (selection.enabled) { - selection.click = item; - selection.click_char = line; + selection.click = item; + selection.click_char = line; - // Erase previous selection. - if (selection.active) { - selection.from = NULL; - selection.from_char = '\0'; - selection.to = NULL; - selection.to_char = '\0'; - selection.active = false; + // Erase previous selection. + if (selection.active) { + selection.from = NULL; + selection.from_char = '\0'; + selection.to = NULL; + selection.to_char = '\0'; + selection.active = false; - update(); - } + update(); } } - } else if (b->is_pressed() && b->is_doubleclick() && selection.enabled) { + } + } else if (b->is_pressed() && b->is_doubleclick() && selection.enabled) { - //doubleclick: select word - int line = 0; - Item *item = NULL; - bool outside; + //doubleclick: select word + int line = 0; + Item *item = NULL; + bool outside; - _find_click(main, b->get_position(), &item, &line, &outside); + _find_click(main, b->get_position(), &item, &line, &outside); - while (item && item->type != ITEM_TEXT) { + while (item && item->type != ITEM_TEXT) { - item = _get_next_item(item, true); - } + item = _get_next_item(item, true); + } - if (item && item->type == ITEM_TEXT) { + if (item && item->type == ITEM_TEXT) { - String itext = static_cast<ItemText *>(item)->text; + String itext = static_cast<ItemText *>(item)->text; - int beg, end; - if (select_word(itext, line, beg, end)) { + int beg, end; + if (select_word(itext, line, beg, end)) { - selection.from = item; - selection.to = item; - selection.from_char = beg; - selection.to_char = end - 1; - selection.active = true; - update(); - } + selection.from = item; + selection.to = item; + selection.from_char = beg; + selection.to_char = end - 1; + selection.active = true; + update(); } - } else if (!b->is_pressed()) { + } + } else if (!b->is_pressed()) { - selection.click = NULL; + selection.click = NULL; - if (!b->is_doubleclick() && !scroll_updated) { - int line = 0; - Item *item = NULL; + if (!b->is_doubleclick() && !scroll_updated) { + int line = 0; + Item *item = NULL; - bool outside; - _find_click(main, b->get_position(), &item, &line, &outside); + bool outside; + _find_click(main, b->get_position(), &item, &line, &outside); - if (item) { + if (item) { - Variant meta; - if (!outside && _find_meta(item, &meta)) { - //meta clicked + Variant meta; + if (!outside && _find_meta(item, &meta)) { + //meta clicked - emit_signal("meta_clicked", meta); - } + emit_signal("meta_clicked", meta); } } } @@ -1133,18 +1145,19 @@ void RichTextLabel::_gui_input(Ref<InputEvent> p_event) { } Variant meta; - if (item && !outside && _find_meta(item, &meta)) { - if (meta_hovering != item) { + ItemMeta *item_meta; + if (item && !outside && _find_meta(item, &meta, &item_meta)) { + if (meta_hovering != item_meta) { if (meta_hovering) { emit_signal("meta_hover_ended", current_meta); } - meta_hovering = static_cast<ItemMeta *>(item); + meta_hovering = item_meta; current_meta = meta; emit_signal("meta_hover_started", meta); } } else if (meta_hovering) { - emit_signal("meta_hover_ended", current_meta); meta_hovering = NULL; + emit_signal("meta_hover_ended", current_meta); current_meta = false; } } @@ -1269,7 +1282,7 @@ bool RichTextLabel::_find_strikethrough(Item *p_item) { return false; } -bool RichTextLabel::_find_meta(Item *p_item, Variant *r_meta) { +bool RichTextLabel::_find_meta(Item *p_item, Variant *r_meta, ItemMeta **r_item) { Item *item = p_item; @@ -1280,6 +1293,8 @@ bool RichTextLabel::_find_meta(Item *p_item, Variant *r_meta) { ItemMeta *meta = static_cast<ItemMeta *>(item); if (r_meta) *r_meta = meta->meta; + if (r_item) + *r_item = meta; return true; } @@ -1289,6 +1304,23 @@ bool RichTextLabel::_find_meta(Item *p_item, Variant *r_meta) { return false; } +bool RichTextLabel::_find_layout_subitem(Item *from, Item *to) { + + if (from && from != to) { + if (from->type != ITEM_FONT && from->type != ITEM_COLOR && from->type != ITEM_UNDERLINE && from->type != ITEM_STRIKETHROUGH) + return true; + + for (List<Item *>::Element *E = from->subitems.front(); E; E = E->next()) { + bool layout = _find_layout_subitem(E->get(), to); + + if (layout) + return true; + } + } + + return false; +} + void RichTextLabel::_validate_line_caches(ItemFrame *p_frame) { if (p_frame->first_invalid_line == p_frame->lines.size()) @@ -1406,9 +1438,13 @@ void RichTextLabel::_add_item(Item *p_item, bool p_enter, bool p_ensure_newline) if (p_enter) current = p_item; - if (p_ensure_newline && current_frame->lines[current_frame->lines.size() - 1].from) { - _invalidate_current_line(current_frame); - current_frame->lines.resize(current_frame->lines.size() + 1); + if (p_ensure_newline) { + Item *from = current_frame->lines[current_frame->lines.size() - 1].from; + // only create a new line for Item types that generate content/layout, ignore those that represent formatting/styling + if (_find_layout_subitem(from, p_item)) { + _invalidate_current_line(current_frame); + current_frame->lines.resize(current_frame->lines.size() + 1); + } } if (current_frame->lines[current_frame->lines.size() - 1].from == NULL) { @@ -1902,37 +1938,37 @@ Error RichTextLabel::append_bbcode(const String &p_bbcode) { if (col.begins_with("#")) color = Color::html(col); else if (col == "aqua") - color = Color::html("#00FFFF"); + color = Color(0, 1, 1); else if (col == "black") - color = Color::html("#000000"); + color = Color(0, 0, 0); else if (col == "blue") - color = Color::html("#0000FF"); + color = Color(0, 0, 1); else if (col == "fuchsia") - color = Color::html("#FF00FF"); + color = Color(1, 0, 1); else if (col == "gray" || col == "grey") - color = Color::html("#808080"); + color = Color(0.5, 0.5, 0.5); else if (col == "green") - color = Color::html("#008000"); + color = Color(0, 0.5, 0); else if (col == "lime") - color = Color::html("#00FF00"); + color = Color(0, 1, 0); else if (col == "maroon") - color = Color::html("#800000"); + color = Color(0.5, 0, 0); else if (col == "navy") - color = Color::html("#000080"); + color = Color(0, 0, 0.5); else if (col == "olive") - color = Color::html("#808000"); + color = Color(0.5, 0.5, 0); else if (col == "purple") - color = Color::html("#800080"); + color = Color(0.5, 0, 0.5); else if (col == "red") - color = Color::html("#FF0000"); + color = Color(1, 0, 0); else if (col == "silver") - color = Color::html("#C0C0C0"); + color = Color(0.75, 0.75, 0.75); else if (col == "teal") - color = Color::html("#008008"); + color = Color(0, 0.5, 0.5); else if (col == "white") - color = Color::html("#FFFFFF"); + color = Color(1, 1, 1); else if (col == "yellow") - color = Color::html("#FFFF00"); + color = Color(1, 1, 0); else color = base_color; @@ -2001,7 +2037,7 @@ bool RichTextLabel::search(const String &p_string, bool p_from_selection, bool p Item *it = main; int charidx = 0; - if (p_from_selection && selection.active && selection.enabled) { + if (p_from_selection && selection.active) { it = selection.to; charidx = selection.to_char + 1; } @@ -2331,6 +2367,7 @@ RichTextLabel::RichTextLabel() { tab_size = 4; default_align = ALIGN_LEFT; underline_meta = true; + meta_hovering = NULL; override_selected_font_color = false; scroll_visible = false; diff --git a/scene/gui/rich_text_label.h b/scene/gui/rich_text_label.h index bec2c5ac02..21d099c37a 100644 --- a/scene/gui/rich_text_label.h +++ b/scene/gui/rich_text_label.h @@ -285,7 +285,8 @@ private: Color _find_color(Item *p_item, const Color &p_default_color); bool _find_underline(Item *p_item); bool _find_strikethrough(Item *p_item); - bool _find_meta(Item *p_item, Variant *r_meta); + bool _find_meta(Item *p_item, Variant *r_meta, ItemMeta **r_item = NULL); + bool _find_layout_subitem(Item *from, Item *to); void _update_scroll(); void _scroll_changed(double); diff --git a/scene/gui/scroll_bar.cpp b/scene/gui/scroll_bar.cpp index 2938654ed9..a7c15151ae 100644 --- a/scene/gui/scroll_bar.cpp +++ b/scene/gui/scroll_bar.cpp @@ -53,29 +53,19 @@ void ScrollBar::_gui_input(Ref<InputEvent> p_event) { if (b.is_valid()) { accept_event(); - if (b->get_button_index() == 5 && b->is_pressed()) { + if (b->get_button_index() == BUTTON_WHEEL_DOWN && b->is_pressed()) { - /* - if (orientation==VERTICAL) - set_val( get_val() + get_page() / 4.0 ); - else - */ set_value(get_value() + get_page() / 4.0); accept_event(); } - if (b->get_button_index() == 4 && b->is_pressed()) { + if (b->get_button_index() == BUTTON_WHEEL_UP && b->is_pressed()) { - /* - if (orientation==HORIZONTAL) - set_val( get_val() - get_page() / 4.0 ); - else - */ set_value(get_value() - get_page() / 4.0); accept_event(); } - if (b->get_button_index() != 1) + if (b->get_button_index() != BUTTON_LEFT) return; if (b->is_pressed()) { @@ -449,27 +439,26 @@ double ScrollBar::get_grabber_size() const { } double ScrollBar::get_area_size() const { - - if (orientation == VERTICAL) { - - double area = get_size().height; - area -= get_stylebox("scroll")->get_minimum_size().height; - area -= get_icon("increment")->get_height(); - area -= get_icon("decrement")->get_height(); - area -= get_grabber_min_size(); - return area; - - } else if (orientation == HORIZONTAL) { - - double area = get_size().width; - area -= get_stylebox("scroll")->get_minimum_size().width; - area -= get_icon("increment")->get_width(); - area -= get_icon("decrement")->get_width(); - area -= get_grabber_min_size(); - return area; - } else { - - return 0; + switch (orientation) { + case VERTICAL: { + double area = get_size().height; + area -= get_stylebox("scroll")->get_minimum_size().height; + area -= get_icon("increment")->get_height(); + area -= get_icon("decrement")->get_height(); + area -= get_grabber_min_size(); + return area; + } break; + case HORIZONTAL: { + double area = get_size().width; + area -= get_stylebox("scroll")->get_minimum_size().width; + area -= get_icon("increment")->get_width(); + area -= get_icon("decrement")->get_width(); + area -= get_grabber_min_size(); + return area; + } break; + default: { + return 0.0; + } } } diff --git a/scene/gui/scroll_bar.h b/scene/gui/scroll_bar.h index 5ceabfc06b..cbcee1dae3 100644 --- a/scene/gui/scroll_bar.h +++ b/scene/gui/scroll_bar.h @@ -33,10 +33,6 @@ #include "scene/gui/range.h" -/** - @author Juan Linietsky <reduzio@gmail.com> -*/ - class ScrollBar : public Range { GDCLASS(ScrollBar, Range); diff --git a/scene/gui/scroll_container.cpp b/scene/gui/scroll_container.cpp index 28292309b9..461281a4ed 100644 --- a/scene/gui/scroll_container.cpp +++ b/scene/gui/scroll_container.cpp @@ -88,13 +88,16 @@ void ScrollContainer::_cancel_drag() { void ScrollContainer::_gui_input(const Ref<InputEvent> &p_gui_input) { + 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()) { // only horizontal is enabled, scroll horizontally - if (h_scroll->is_visible() && !v_scroll->is_visible()) { + if (h_scroll->is_visible() && (!v_scroll->is_visible() || mb->get_shift())) { 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()); @@ -103,7 +106,7 @@ void ScrollContainer::_gui_input(const Ref<InputEvent> &p_gui_input) { if (mb->get_button_index() == BUTTON_WHEEL_DOWN && mb->is_pressed()) { // only horizontal is enabled, scroll horizontally - if (h_scroll->is_visible() && !v_scroll->is_visible()) { + if (h_scroll->is_visible() && (!v_scroll->is_visible() || mb->get_shift())) { 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()); @@ -122,6 +125,9 @@ void ScrollContainer::_gui_input(const Ref<InputEvent> &p_gui_input) { } } + if (v_scroll->get_value() != prev_v_scroll || h_scroll->get_value() != prev_h_scroll) + accept_event(); //accept event if scroll changed + if (!OS::get_singleton()->has_touchscreen_ui_hint()) return; @@ -134,19 +140,17 @@ void ScrollContainer::_gui_input(const Ref<InputEvent> &p_gui_input) { _cancel_drag(); } - if (true) { - drag_speed = Vector2(); - drag_accum = Vector2(); - last_drag_accum = Vector2(); - drag_from = Vector2(h_scroll->get_value(), v_scroll->get_value()); - drag_touching = OS::get_singleton()->has_touchscreen_ui_hint(); - drag_touching_deaccel = false; - beyond_deadzone = false; + drag_speed = Vector2(); + drag_accum = Vector2(); + last_drag_accum = Vector2(); + drag_from = Vector2(h_scroll->get_value(), v_scroll->get_value()); + drag_touching = OS::get_singleton()->has_touchscreen_ui_hint(); + drag_touching_deaccel = false; + beyond_deadzone = false; + time_since_motion = 0; + if (drag_touching) { + set_physics_process_internal(true); time_since_motion = 0; - if (drag_touching) { - set_physics_process_internal(true); - time_since_motion = 0; - } } } else { @@ -204,6 +208,9 @@ void ScrollContainer::_gui_input(const Ref<InputEvent> &p_gui_input) { v_scroll->set_value(v_scroll->get_value() + v_scroll->get_page() * pan_gesture->get_delta().y / 8); } } + + if (v_scroll->get_value() != prev_v_scroll || h_scroll->get_value() != prev_h_scroll) + accept_event(); //accept event if scroll changed } void ScrollContainer::_update_scrollbar_position() { @@ -368,8 +375,10 @@ void ScrollContainer::update_scrollbars() { Ref<StyleBox> sb = get_stylebox("bg"); size -= sb->get_minimum_size(); - Size2 hmin = h_scroll->get_combined_minimum_size(); - Size2 vmin = v_scroll->get_combined_minimum_size(); + Size2 hmin; + Size2 vmin; + if (scroll_h) hmin = h_scroll->get_combined_minimum_size(); + if (scroll_v) vmin = v_scroll->get_combined_minimum_size(); Size2 min = child_max_size; @@ -477,7 +486,7 @@ String ScrollContainer::get_configuration_warning() const { } if (found != 1) - return 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 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."); else return ""; } diff --git a/scene/gui/separator.h b/scene/gui/separator.h index 54ad9b5bb5..89039f3112 100644 --- a/scene/gui/separator.h +++ b/scene/gui/separator.h @@ -31,10 +31,6 @@ #ifndef SEPARATOR_H #define SEPARATOR_H -/** - @author Juan Linietsky <reduzio@gmail.com> -*/ - #include "scene/gui/control.h" class Separator : public Control { diff --git a/scene/gui/slider.cpp b/scene/gui/slider.cpp index eb04b85931..b777e77bc3 100644 --- a/scene/gui/slider.cpp +++ b/scene/gui/slider.cpp @@ -34,8 +34,15 @@ Size2 Slider::get_minimum_size() const { Ref<StyleBox> style = get_stylebox("slider"); - Size2i ms = style->get_minimum_size() + style->get_center_size(); - return ms; + Size2i ss = style->get_minimum_size() + style->get_center_size(); + + Ref<Texture> grabber = get_icon("grabber"); + Size2i rs = grabber->get_size(); + + if (orientation == HORIZONTAL) + return Size2i(ss.width, MAX(ss.height, rs.height)); + else + return Size2i(MAX(ss.width, rs.width), ss.height); } void Slider::_gui_input(Ref<InputEvent> p_event) { @@ -134,7 +141,11 @@ void Slider::_gui_input(Ref<InputEvent> p_event) { void Slider::_notification(int p_what) { switch (p_what) { + case NOTIFICATION_THEME_CHANGED: { + minimum_size_changed(); + update(); + } break; case NOTIFICATION_MOUSE_ENTER: { mouse_inside = true; @@ -284,6 +295,7 @@ Slider::Slider(Orientation p_orientation) { mouse_inside = false; grab.active = false; ticks = 0; + ticks_on_borders = false; custom_step = -1; editable = true; scrollable = true; diff --git a/scene/gui/spin_box.cpp b/scene/gui/spin_box.cpp index d21143739c..3c63f6b2c7 100644 --- a/scene/gui/spin_box.cpp +++ b/scene/gui/spin_box.cpp @@ -29,6 +29,7 @@ /*************************************************************************/ #include "spin_box.h" +#include "core/math/expression.h" #include "core/os/input.h" Size2 SpinBox::get_minimum_size() const { @@ -40,7 +41,7 @@ Size2 SpinBox::get_minimum_size() const { void SpinBox::_value_changed(double) { - String value = String::num(get_value(), Math::step_decimals(get_step())); + String value = String::num(get_value(), Math::range_step_decimals(get_step())); if (prefix != "") value = prefix + " " + value; if (suffix != "") @@ -50,15 +51,18 @@ void SpinBox::_value_changed(double) { void SpinBox::_text_entered(const String &p_string) { - /* - if (!p_string.is_numeric()) + Ref<Expression> expr; + expr.instance(); + Error err = expr->parse(p_string); + if (err != OK) { return; - */ - String value = p_string; - if (prefix != "" && p_string.begins_with(prefix)) - value = p_string.substr(prefix.length(), p_string.length() - prefix.length()); - set_value(value.to_double()); - _value_changed(0); + } + + Variant value = expr->execute(Array(), NULL, false); + if (value.get_type() != Variant::NIL) { + set_value(value); + _value_changed(0); + } } LineEdit *SpinBox::get_line_edit() { @@ -170,10 +174,14 @@ void SpinBox::_gui_input(const Ref<InputEvent> &p_event) { void SpinBox::_line_edit_focus_exit() { + // discontinue because the focus_exit was caused by right-click context menu + if (line_edit->get_menu()->is_visible()) + return; + _text_entered(line_edit->get_text()); } -inline void SpinBox::_adjust_width_for_icon(const Ref<Texture> icon) { +inline void SpinBox::_adjust_width_for_icon(const Ref<Texture> &icon) { int w = icon->get_width(); if (w != last_w) { @@ -277,6 +285,7 @@ SpinBox::SpinBox() { add_child(line_edit); line_edit->set_anchors_and_margins_preset(Control::PRESET_WIDE); + line_edit->set_mouse_filter(MOUSE_FILTER_PASS); //connect("value_changed",this,"_value_changed"); line_edit->connect("text_entered", this, "_text_entered", Vector<Variant>(), CONNECT_DEFERRED); line_edit->connect("focus_exited", this, "_line_edit_focus_exit", Vector<Variant>(), CONNECT_DEFERRED); diff --git a/scene/gui/spin_box.h b/scene/gui/spin_box.h index 49dc6d950c..9cf977d2d6 100644 --- a/scene/gui/spin_box.h +++ b/scene/gui/spin_box.h @@ -62,7 +62,7 @@ class SpinBox : public Range { void _line_edit_focus_exit(); - inline void _adjust_width_for_icon(const Ref<Texture> icon); + inline void _adjust_width_for_icon(const Ref<Texture> &icon); protected: void _gui_input(const Ref<InputEvent> &p_event); diff --git a/scene/gui/split_container.cpp b/scene/gui/split_container.cpp index d6a93238b2..e5d1844d39 100644 --- a/scene/gui/split_container.cpp +++ b/scene/gui/split_container.cpp @@ -164,34 +164,31 @@ void SplitContainer::_notification(int p_what) { _resort(); } break; - case NOTIFICATION_MOUSE_ENTER: { - - mouse_inside = true; - update(); - } break; case NOTIFICATION_MOUSE_EXIT: { mouse_inside = false; - update(); + if (get_constant("autohide")) + update(); } break; case NOTIFICATION_DRAW: { if (!_getch(0) || !_getch(1)) return; - if (collapsed || (!mouse_inside && get_constant("autohide"))) + if (collapsed || (!dragging && !mouse_inside && get_constant("autohide"))) + return; + + if (dragger_visibility != DRAGGER_VISIBLE) return; int sep = dragger_visibility != DRAGGER_HIDDEN_COLLAPSED ? get_constant("separation") : 0; Ref<Texture> tex = get_icon("grabber"); Size2 size = get_size(); - if (dragger_visibility == DRAGGER_VISIBLE) { - if (vertical) - draw_texture(tex, Point2i((size.x - tex->get_width()) / 2, middle_sep + (sep - tex->get_height()) / 2)); - else - draw_texture(tex, Point2i(middle_sep + (sep - tex->get_width()) / 2, (size.y - tex->get_height()) / 2)); - } + if (vertical) + draw_texture(tex, Point2i((size.x - tex->get_width()) / 2, middle_sep + (sep - tex->get_height()) / 2)); + else + draw_texture(tex, Point2i(middle_sep + (sep - tex->get_width()) / 2, (size.y - tex->get_height()) / 2)); } break; case NOTIFICATION_THEME_CHANGED: { @@ -241,7 +238,23 @@ void SplitContainer::_gui_input(const Ref<InputEvent> &p_event) { Ref<InputEventMouseMotion> mm = p_event; - if (mm.is_valid() && dragging) { + if (mm.is_valid()) { + + bool mouse_inside_state = false; + if (vertical) + mouse_inside_state = mm->get_position().y > middle_sep && mm->get_position().y < middle_sep + get_constant("separation"); + else + mouse_inside_state = mm->get_position().x > middle_sep && mm->get_position().x < middle_sep + get_constant("separation"); + + if (mouse_inside != mouse_inside_state) { + + mouse_inside = mouse_inside_state; + if (get_constant("autohide")) + update(); + } + + if (!dragging) + return; split_offset = drag_ofs + ((vertical ? mm->get_position().y : mm->get_position().x) - drag_from); should_clamp_split_offset = true; diff --git a/scene/gui/split_container.h b/scene/gui/split_container.h index 3c1ca09a9f..97838e19a3 100644 --- a/scene/gui/split_container.h +++ b/scene/gui/split_container.h @@ -35,7 +35,7 @@ class SplitContainer : public Container { - GDCLASS(SplitContainer, Container) + GDCLASS(SplitContainer, Container); public: enum DraggerVisibility { diff --git a/scene/gui/tab_container.cpp b/scene/gui/tab_container.cpp index e35967861c..292d80be9d 100644 --- a/scene/gui/tab_container.cpp +++ b/scene/gui/tab_container.cpp @@ -86,8 +86,8 @@ void TabContainer::_gui_input(const Ref<InputEvent> &p_event) { emit_signal("pre_popup_pressed"); Vector2 popup_pos = get_global_position(); - popup_pos.x += size.width - popup->get_size().width; - popup_pos.y += menu->get_height(); + popup_pos.x += size.width * get_global_transform().get_scale().x - popup->get_size().width * popup->get_global_transform().get_scale().x; + popup_pos.y += menu->get_height() * get_global_transform().get_scale().y; popup->set_global_position(popup_pos); popup->popup(); @@ -127,6 +127,9 @@ void TabContainer::_gui_input(const Ref<InputEvent> &p_event) { // Activate the clicked tab. pos.x -= tabs_ofs_cache; for (int i = first_tab_cache; i <= last_tab_cache; i++) { + if (get_tab_hidden(i)) { + continue; + } int tab_width = _get_tab_width(i); if (pos.x < tab_width) { if (!get_tab_disabled(i)) { @@ -143,6 +146,11 @@ void TabContainer::_notification(int p_what) { switch (p_what) { + case NOTIFICATION_TRANSLATION_CHANGED: { + + minimum_size_changed(); + update(); + } break; case NOTIFICATION_RESIZED: { Vector<Control *> tabs = _get_tabs(); @@ -178,7 +186,6 @@ void TabContainer::_notification(int p_what) { first_tab_cache--; } } break; - case NOTIFICATION_DRAW: { RID canvas = get_canvas_item(); @@ -216,6 +223,9 @@ void TabContainer::_notification(int p_what) { // Check if all tabs would fit into the header area. int all_tabs_width = 0; for (int i = 0; i < tabs.size(); i++) { + if (get_tab_hidden(i)) { + continue; + } int tab_width = _get_tab_width(i); all_tabs_width += tab_width; @@ -241,6 +251,9 @@ void TabContainer::_notification(int p_what) { all_tabs_width = 0; Vector<int> tab_widths; for (int i = first_tab_cache; i < tabs.size(); i++) { + if (get_tab_hidden(i)) { + continue; + } int tab_width = _get_tab_width(i); if (all_tabs_width + tab_width > header_width && tab_widths.size() > 0) break; @@ -267,6 +280,9 @@ void TabContainer::_notification(int p_what) { // Draw all visible tabs. int x = 0; for (int i = 0; i < tab_widths.size(); i++) { + if (get_tab_hidden(i)) { + continue; + } Ref<StyleBox> tab_style; Color font_color; if (get_tab_disabled(i + first_tab_cache)) { @@ -287,7 +303,7 @@ void TabContainer::_notification(int p_what) { // Draw the tab contents. Control *control = Object::cast_to<Control>(tabs[i + first_tab_cache]); - String text = control->has_meta("_tab_name") ? String(tr(String(control->get_meta("_tab_name")))) : String(control->get_name()); + String text = control->has_meta("_tab_name") ? String(tr(String(control->get_meta("_tab_name")))) : String(tr(control->get_name())); int x_content = tab_rect.position.x + tab_style->get_margin(MARGIN_LEFT); int top_margin = tab_style->get_margin(MARGIN_TOP); @@ -338,6 +354,7 @@ void TabContainer::_notification(int p_what) { } } break; case NOTIFICATION_THEME_CHANGED: { + minimum_size_changed(); call_deferred("_on_theme_changed"); //wait until all changed theme } break; @@ -354,7 +371,7 @@ int TabContainer::_get_tab_width(int p_index) const { ERR_FAIL_INDEX_V(p_index, get_tab_count(), 0); Control *control = Object::cast_to<Control>(_get_tabs()[p_index]); - if (!control || control->is_set_as_toplevel()) + if (!control || control->is_set_as_toplevel() || get_tab_hidden(p_index)) return 0; // Get the width of the text displayed on the tab. @@ -719,6 +736,7 @@ void TabContainer::set_tab_title(int p_tab, const String &p_title) { Control *child = _get_tab(p_tab); ERR_FAIL_COND(!child); child->set_meta("_tab_name", p_title); + update(); } String TabContainer::get_tab_title(int p_tab) const { @@ -736,6 +754,7 @@ void TabContainer::set_tab_icon(int p_tab, const Ref<Texture> &p_icon) { Control *child = _get_tab(p_tab); ERR_FAIL_COND(!child); child->set_meta("_tab_icon", p_icon); + update(); } Ref<Texture> TabContainer::get_tab_icon(int p_tab) const { @@ -765,6 +784,36 @@ bool TabContainer::get_tab_disabled(int p_tab) const { return false; } +void TabContainer::set_tab_hidden(int p_tab, bool p_hidden) { + + Control *child = _get_tab(p_tab); + ERR_FAIL_COND(!child); + child->set_meta("_tab_hidden", p_hidden); + update(); + for (int i = 0; i < get_tab_count(); i++) { + int try_tab = (p_tab + 1 + i) % get_tab_count(); + if (get_tab_disabled(try_tab) || get_tab_hidden(try_tab)) { + continue; + } + + set_current_tab(try_tab); + return; + } + + //assumed no other tab can be switched to, just hide + child->hide(); +} + +bool TabContainer::get_tab_hidden(int p_tab) const { + + Control *child = _get_tab(p_tab); + ERR_FAIL_COND_V(!child, false); + if (child->has_meta("_tab_hidden")) + return child->get_meta("_tab_hidden"); + else + return false; +} + void TabContainer::get_translatable_strings(List<String> *p_strings) const { Vector<Control *> tabs = _get_tabs(); diff --git a/scene/gui/tab_container.h b/scene/gui/tab_container.h index dbf2bb9231..0314f86837 100644 --- a/scene/gui/tab_container.h +++ b/scene/gui/tab_container.h @@ -97,6 +97,9 @@ public: void set_tab_disabled(int p_tab, bool p_disabled); bool get_tab_disabled(int p_tab) const; + void set_tab_hidden(int p_tab, bool p_hidden); + bool get_tab_hidden(int p_tab) const; + int get_tab_count() const; void set_current_tab(int p_current); int get_current_tab() const; diff --git a/scene/gui/tabs.cpp b/scene/gui/tabs.cpp index ac643c1320..7b0836cd28 100644 --- a/scene/gui/tabs.cpp +++ b/scene/gui/tabs.cpp @@ -222,6 +222,10 @@ void Tabs::_notification(int p_what) { switch (p_what) { + case NOTIFICATION_TRANSLATION_CHANGED: { + minimum_size_changed(); + update(); + } break; case NOTIFICATION_MOUSE_EXIT: { rb_hover = -1; cb_hover = -1; @@ -232,7 +236,6 @@ void Tabs::_notification(int p_what) { _update_cache(); _ensure_no_over_offset(); ensure_tab_visible(current); - } break; case NOTIFICATION_DRAW: { _update_cache(); @@ -394,7 +397,6 @@ void Tabs::_notification(int p_what) { } else { buttons_visible = false; } - } break; } } @@ -890,6 +892,8 @@ void Tabs::ensure_tab_visible(int p_idx) { } Rect2 Tabs::get_tab_rect(int p_tab) const { + + ERR_FAIL_INDEX_V(p_tab, tabs.size(), Rect2()); return Rect2(tabs[p_tab].ofs_cache, 0, tabs[p_tab].size_cache, get_size().height); } diff --git a/scene/gui/text_edit.cpp b/scene/gui/text_edit.cpp index 09fbb39866..1d434e5a2a 100644 --- a/scene/gui/text_edit.cpp +++ b/scene/gui/text_edit.cpp @@ -35,6 +35,7 @@ #include "core/os/keyboard.h" #include "core/os/os.h" #include "core/project_settings.h" +#include "core/script_language.h" #include "scene/main/viewport.h" #ifdef TOOLS_ENABLED @@ -50,7 +51,7 @@ inline bool _is_symbol(CharType c) { static bool _is_text_char(CharType c) { - return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') || c == '_'; + return !is_symbol(c); } static bool _is_whitespace(CharType c) { @@ -104,6 +105,13 @@ static CharType _get_right_pair_symbol(CharType c) { return 0; } +static int _find_first_non_whitespace_column_of_line(const String &line) { + int left = 0; + while (left < line.length() && _is_whitespace(line[left])) + left++; + return left; +} + void TextEdit::Text::set_font(const Ref<Font> &p_font) { font = p_font; @@ -121,7 +129,7 @@ void TextEdit::Text::_update_line_cache(int p_line) const { int len = text[p_line].data.length(); const CharType *str = text[p_line].data.c_str(); - //update width + // Update width. for (int i = 0; i < len; i++) { w += get_char_width(str[i], str[i + 1], w); @@ -131,7 +139,7 @@ void TextEdit::Text::_update_line_cache(int p_line) const { text.write[p_line].wrap_amount_cache = -1; - //update regions + // Update regions. text.write[p_line].region_info.clear(); @@ -140,7 +148,7 @@ void TextEdit::Text::_update_line_cache(int p_line) const { if (!_is_symbol(str[i])) continue; if (str[i] == '\\') { - i++; //skip quoted anything + i++; // Skip quoted anything. continue; } @@ -267,7 +275,7 @@ void TextEdit::Text::clear() { } int TextEdit::Text::get_max_width(bool p_exclude_hidden) const { - //quite some work.. but should be fast enough. + // Quite some work, but should be fast enough. int max = 0; for (int i = 0; i < text.size(); i++) { @@ -292,6 +300,7 @@ void TextEdit::Text::insert(int p_at, const String &p_text) { line.marked = false; line.safe = false; line.breakpoint = false; + line.bookmark = false; line.hidden = false; line.width_cache = -1; line.wrap_amount_cache = -1; @@ -314,7 +323,7 @@ int TextEdit::Text::get_char_width(CharType c, CharType next_c, int px) const { if (left == 0) w = tab_w; else - w = tab_w - px % tab_w; // is right... + w = tab_w - px % tab_w; // Is right. } else { w = font->get_char_size(c, next_c).width; @@ -346,31 +355,37 @@ void TextEdit::_update_scrollbars() { if (line_numbers) total_width += cache.line_number_w; - if (draw_breakpoint_gutter) { + if (draw_breakpoint_gutter || draw_bookmark_gutter) { total_width += cache.breakpoint_gutter_width; } + if (draw_info_gutter) { + total_width += cache.info_gutter_width; + } + if (draw_fold_gutter) { total_width += cache.fold_gutter_width; } + if (draw_minimap) { + total_width += cache.minimap_width; + } + bool use_hscroll = true; bool use_vscroll = true; + // Thanks yessopie for this clever bit of logic. if (total_rows <= visible_rows && total_width <= visible_width) { - //thanks yessopie for this clever bit of logic + use_hscroll = false; use_vscroll = false; - } else { if (total_rows > visible_rows && total_width <= visible_width) { - //thanks yessopie for this clever bit of logic use_hscroll = false; } if (total_rows <= visible_rows && total_width > visible_width) { - //thanks yessopie for this clever bit of logic use_vscroll = false; } } @@ -394,6 +409,7 @@ void TextEdit::_update_scrollbars() { cursor.line_ofs = 0; cursor.wrap_ofs = 0; v_scroll->set_value(0); + v_scroll->set_max(0); v_scroll->hide(); } @@ -412,6 +428,7 @@ void TextEdit::_update_scrollbars() { cursor.x_ofs = 0; h_scroll->set_value(0); + h_scroll->set_max(0); h_scroll->hide(); } @@ -444,6 +461,7 @@ void TextEdit::_click_selection_held() { } void TextEdit::_update_selection_mode_pointer() { + dragging_selection = true; Point2 mp = get_local_mouse_position(); int row, col; @@ -459,6 +477,7 @@ void TextEdit::_update_selection_mode_pointer() { } void TextEdit::_update_selection_mode_word() { + dragging_selection = true; Point2 mp = get_local_mouse_position(); int row, col; @@ -466,7 +485,7 @@ void TextEdit::_update_selection_mode_word() { String line = text[row]; int beg = CLAMP(col, 0, line.length()); - // if its the first selection and on whitespace make sure we grab the word instead.. + // If its the first selection and on whitespace make sure we grab the word instead. if (!selection.active) { while (beg > 0 && line[beg] <= 32) { beg--; @@ -475,7 +494,7 @@ void TextEdit::_update_selection_mode_word() { int end = beg; bool symbol = beg < line.length() && _is_symbol(line[beg]); - // get the word end and begin points + // Get the word end and begin points. while (beg > 0 && line[beg - 1] > 32 && (symbol == _is_symbol(line[beg - 1]))) { beg--; } @@ -486,7 +505,7 @@ void TextEdit::_update_selection_mode_word() { end += 1; } - // initial selection + // Initial selection. if (!selection.active) { select(row, beg, row, end); selection.selecting_column = beg; @@ -515,6 +534,7 @@ void TextEdit::_update_selection_mode_word() { } void TextEdit::_update_selection_mode_line() { + dragging_selection = true; Point2 mp = get_local_mouse_position(); int row, col; @@ -522,11 +542,11 @@ void TextEdit::_update_selection_mode_line() { col = 0; if (row < selection.selecting_line) { - // cursor is above us + // Cursor is above us. cursor_set_line(row - 1, false); selection.selecting_column = text[selection.selecting_line].length(); } else { - // cursor is below us + // Cursor is below us. cursor_set_line(row + 1, false); selection.selecting_column = 0; col = text[row].length(); @@ -539,6 +559,48 @@ void TextEdit::_update_selection_mode_line() { click_select_held->start(); } +void TextEdit::_update_minimap_click() { + Point2 mp = get_local_mouse_position(); + + int xmargin_end = get_size().width - cache.style_normal->get_margin(MARGIN_RIGHT); + if (!dragging_minimap && (mp.x < xmargin_end - minimap_width || mp.y > xmargin_end)) { + minimap_clicked = false; + return; + } + minimap_clicked = true; + dragging_minimap = true; + + int row; + _get_minimap_mouse_row(Point2i(mp.x, mp.y), row); + + if (row >= get_first_visible_line() && (row < get_last_visible_line() || row >= (text.size() - 1))) { + minimap_scroll_ratio = v_scroll->get_as_ratio(); + minimap_scroll_click_pos = mp.y; + can_drag_minimap = true; + return; + } + + int wi; + int first_line = row - num_lines_from_rows(row, 0, -get_visible_rows() / 2, wi) + 1; + double delta = get_scroll_pos_for_line(first_line, wi) - get_v_scroll(); + if (delta < 0) { + _scroll_up(-delta); + } else { + _scroll_down(delta); + } +} + +void TextEdit::_update_minimap_drag() { + + if (!can_drag_minimap) { + return; + } + + Point2 mp = get_local_mouse_position(); + double diff = (mp.y - minimap_scroll_click_pos) / _get_control_height(); + v_scroll->set_as_ratio(minimap_scroll_ratio + diff); +} + void TextEdit::_notification(int p_what) { switch (p_what) { @@ -554,12 +616,13 @@ void TextEdit::_notification(int p_what) { case NOTIFICATION_RESIZED: { _update_scrollbars(); - call_deferred("_update_wrap_at"); + _update_wrap_at(); } break; case NOTIFICATION_THEME_CHANGED: { _update_caches(); _update_wrap_at(); + syntax_highlighting_cache.clear(); } break; case MainLoop::NOTIFICATION_WM_FOCUS_IN: { window_has_focus = true; @@ -575,33 +638,48 @@ void TextEdit::_notification(int p_what) { if (scrolling && get_v_scroll() != target_v_scroll) { double target_y = target_v_scroll - get_v_scroll(); double dist = sqrt(target_y * target_y); - double vel = ((target_y / dist) * v_scroll_speed) * get_physics_process_delta_time(); + // To ensure minimap is responsive overide the speed setting. + double vel = ((target_y / dist) * ((minimap_clicked) ? 3000 : v_scroll_speed)) * get_physics_process_delta_time(); if (Math::abs(vel) >= dist) { set_v_scroll(target_v_scroll); scrolling = false; + minimap_clicked = false; set_physics_process_internal(false); } else { set_v_scroll(get_v_scroll() + vel); } } else { scrolling = false; + minimap_clicked = false; set_physics_process_internal(false); } } break; case NOTIFICATION_DRAW: { + if (first_draw) { + // Size may not be the final one, so attempts to ensure cursor was visible may have failed. + adjust_viewport_to_cursor(); + first_draw = false; + } Size2 size = get_size(); if ((!has_focus() && !menu->has_focus()) || !window_has_focus) { draw_caret = false; } - if (draw_breakpoint_gutter) { + if (draw_breakpoint_gutter || draw_bookmark_gutter) { breakpoint_gutter_width = (get_row_height() * 55) / 100; cache.breakpoint_gutter_width = breakpoint_gutter_width; } else { cache.breakpoint_gutter_width = 0; } + if (draw_info_gutter) { + info_gutter_width = (get_row_height()); + cache.info_gutter_width = info_gutter_width; + } else { + cache.info_gutter_width = 0; + } + if (draw_fold_gutter) { fold_gutter_width = (get_row_height() * 55) / 100; cache.fold_gutter_width = fold_gutter_width; @@ -609,6 +687,11 @@ void TextEdit::_notification(int p_what) { cache.fold_gutter_width = 0; } + cache.minimap_width = 0; + if (draw_minimap) { + cache.minimap_width = minimap_width; + } + int line_number_char_count = 0; { @@ -631,14 +714,13 @@ void TextEdit::_notification(int p_what) { RID ci = get_canvas_item(); VisualServer::get_singleton()->canvas_item_set_clip(get_canvas_item(), true); - int xmargin_beg = cache.style_normal->get_margin(MARGIN_LEFT) + cache.line_number_w + cache.breakpoint_gutter_width + cache.fold_gutter_width; - int xmargin_end = size.width - cache.style_normal->get_margin(MARGIN_RIGHT); - //let's do it easy for now: + int xmargin_beg = cache.style_normal->get_margin(MARGIN_LEFT) + cache.line_number_w + cache.breakpoint_gutter_width + cache.fold_gutter_width + cache.info_gutter_width; + + int xmargin_end = size.width - cache.style_normal->get_margin(MARGIN_RIGHT) - cache.minimap_width; + // Let's do it easy for now. cache.style_normal->draw(ci, Rect2(Point2(), size)); - float readonly_alpha = 1.0; // used to set the input text color when in read-only mode if (readonly) { cache.style_readonly->draw(ci, Rect2(Point2(), size)); - readonly_alpha = .5; draw_caret = false; } if (has_focus()) @@ -648,8 +730,7 @@ void TextEdit::_notification(int p_what) { int visible_rows = get_visible_rows() + 1; - Color color = cache.font_color; - color.a *= readonly_alpha; + Color color = readonly ? cache.font_color_readonly : cache.font_color; if (syntax_coloring) { if (cache.background_color.a > 0.01) { @@ -657,6 +738,13 @@ void TextEdit::_notification(int p_what) { } } + if (line_length_guideline) { + int x = xmargin_beg + (int)cache.font->get_char_size('0').width * line_length_guideline_col - cursor.x_ofs; + if (x > xmargin_beg && x < xmargin_end) { + VisualServer::get_singleton()->canvas_item_add_line(ci, Point2(x, 0), Point2(x, size.height), cache.line_length_guideline_color); + } + } + int brace_open_match_line = -1; int brace_open_match_column = -1; bool brace_open_matching = false; @@ -666,10 +754,10 @@ void TextEdit::_notification(int p_what) { bool brace_close_matching = false; bool brace_close_mismatch = false; - if (brace_matching_enabled) { + if (brace_matching_enabled && cursor.line >= 0 && cursor.line < text.size() && cursor.column >= 0) { if (cursor.column < text[cursor.line].length()) { - //check for open + // Check for open. CharType c = text[cursor.line][cursor.column]; CharType closec = 0; @@ -691,7 +779,7 @@ void TextEdit::_notification(int p_what) { for (int j = from; j < text[i].length(); j++) { CharType cc = text[i][j]; - //ignore any brackets inside a string + // Ignore any brackets inside a string. if (cc == '"' || cc == '\'') { CharType quotation = cc; do { @@ -700,7 +788,7 @@ void TextEdit::_notification(int p_what) { break; } cc = text[i][j]; - //skip over escaped quotation marks inside strings + // Skip over escaped quotation marks inside strings. if (cc == '\\') { bool escaped = true; while (j + 1 < text[i].length() && text[i][j + 1] == '\\') { @@ -757,7 +845,7 @@ void TextEdit::_notification(int p_what) { for (int j = from; j >= 0; j--) { CharType cc = text[i][j]; - //ignore any brackets inside a string + // Ignore any brackets inside a string. if (cc == '"' || cc == '\'') { CharType quotation = cc; do { @@ -766,7 +854,7 @@ void TextEdit::_notification(int p_what) { break; } cc = text[i][j]; - //skip over escaped quotation marks inside strings + // Skip over escaped quotation marks inside strings. if (cc == quotation) { bool escaped = false; while (j - 1 >= 0 && text[i][j - 1] == '\\') { @@ -805,18 +893,168 @@ void TextEdit::_notification(int p_what) { Point2 cursor_pos; int cursor_insert_offset_y = 0; - // get the highlighted words + // Get the highlighted words. String highlighted_text = get_selection_text(); + // Check if highlighted words contains only whitespaces (tabs or spaces). + bool only_whitespaces_highlighted = highlighted_text.strip_edges() == String(); + String line_num_padding = line_numbers_zero_padded ? "0" : " "; int cursor_wrap_index = get_cursor_wrap_index(); FontDrawer drawer(cache.font, Color(1, 1, 1)); - int line = get_first_visible_line() - 1; + int first_visible_line = get_first_visible_line() - 1; int draw_amount = visible_rows + (smooth_scroll_enabled ? 1 : 0); - draw_amount += times_line_wraps(line + 1); + draw_amount += times_line_wraps(first_visible_line + 1); + + // minimap + if (draw_minimap) { + int minimap_visible_lines = _get_minimap_visible_rows(); + int minimap_line_height = (minimap_char_size.y + minimap_line_spacing); + int minimap_tab_size = minimap_char_size.x * indent_size; + + // calculate viewport size and y offset + int viewport_height = (draw_amount - 1) * minimap_line_height; + int control_height = _get_control_height() - viewport_height; + int viewport_offset_y = round(get_scroll_pos_for_line(first_visible_line) * control_height) / ((v_scroll->get_max() <= minimap_visible_lines) ? (minimap_visible_lines - draw_amount) : (v_scroll->get_max() - draw_amount)); + + // calculate the first line. + int num_lines_before = round((viewport_offset_y) / minimap_line_height); + int wi; + int minimap_line = (v_scroll->get_max() <= minimap_visible_lines) ? -1 : first_visible_line; + if (minimap_line >= 0) { + minimap_line -= num_lines_from_rows(first_visible_line, 0, -num_lines_before, wi); + minimap_line -= (smooth_scroll_enabled ? 1 : 0); + } + int minimap_draw_amount = minimap_visible_lines + times_line_wraps(minimap_line + 1); + + // draw the minimap + Color viewport_color = (cache.background_color.get_v() < 0.5) ? Color(1, 1, 1, 0.1) : Color(0, 0, 0, 0.1); + VisualServer::get_singleton()->canvas_item_add_rect(ci, Rect2((xmargin_end + 2), viewport_offset_y, cache.minimap_width, viewport_height), viewport_color); + for (int i = 0; i < minimap_draw_amount; i++) { + + minimap_line++; + + if (minimap_line < 0 || minimap_line >= (int)text.size()) { + break; + } + + while (is_line_hidden(minimap_line)) { + minimap_line++; + if (minimap_line < 0 || minimap_line >= (int)text.size()) { + break; + } + } + + Map<int, HighlighterInfo> color_map; + if (syntax_coloring) { + color_map = _get_line_syntax_highlighting(minimap_line); + } + + Color current_color = cache.font_color; + if (readonly) { + current_color = cache.font_color_readonly; + } + + Vector<String> wrap_rows = get_wrap_rows_text(minimap_line); + int line_wrap_amount = times_line_wraps(minimap_line); + int last_wrap_column = 0; + + for (int line_wrap_index = 0; line_wrap_index < line_wrap_amount + 1; line_wrap_index++) { + if (line_wrap_index != 0) { + i++; + if (i >= minimap_draw_amount) + break; + } + + const String &str = wrap_rows[line_wrap_index]; + int indent_px = line_wrap_index != 0 ? get_indent_level(minimap_line) : 0; + if (indent_px >= wrap_at) { + indent_px = 0; + } + indent_px = minimap_char_size.x * indent_px; + + if (line_wrap_index > 0) { + last_wrap_column += wrap_rows[line_wrap_index - 1].length(); + } + + if (minimap_line == cursor.line && cursor_wrap_index == line_wrap_index && highlight_current_line) { + VisualServer::get_singleton()->canvas_item_add_rect(ci, Rect2((xmargin_end + 2), i * 3, cache.minimap_width, 2), cache.current_line_color); + } + + Color previous_color; + int characters = 0; + int tabs = 0; + for (int j = 0; j < str.length(); j++) { + if (syntax_coloring) { + if (color_map.has(last_wrap_column + j)) { + current_color = color_map[last_wrap_column + j].color; + if (readonly) { + current_color.a = cache.font_color_readonly.a; + } + } + color = current_color; + } + + if (j == 0) { + previous_color = color; + } + + int xpos = indent_px + ((xmargin_end + minimap_char_size.x) + (minimap_char_size.x * j)) + tabs; + bool out_of_bounds = (xpos >= xmargin_end + cache.minimap_width); + + bool is_whitespace = _is_whitespace(str[j]); + if (!is_whitespace) { + characters++; + + if (j < str.length() - 1 && color == previous_color && !out_of_bounds) { + continue; + } + + // If we've changed colour we are at the start of a new section, therefore we need to go back to the end + // of the previous section to draw it, we'll also add the character back on. + if (color != previous_color) { + characters--; + j--; + + if (str[j] == '\t') { + tabs -= minimap_tab_size; + } + } + } + + if (characters > 0) { + previous_color.a *= 0.6; + // take one for zero indexing, and if we hit whitespace / the end of a word. + int chars = MAX(0, (j - (characters - 1)) - (is_whitespace ? 1 : 0)) + 1; + int char_x_ofs = indent_px + ((xmargin_end + minimap_char_size.x) + (minimap_char_size.x * chars)) + tabs; + VisualServer::get_singleton()->canvas_item_add_rect(ci, Rect2(Point2(char_x_ofs, minimap_line_height * i), Point2(minimap_char_size.x * characters, minimap_char_size.y)), previous_color); + } + + if (out_of_bounds) { + break; + } + + // re-adjust if we went backwards. + if (color != previous_color && !is_whitespace) { + characters++; + } + + if (str[j] == '\t') { + tabs += minimap_tab_size; + } + + previous_color = color; + characters = 0; + } + } + } + } + + // draw main text + int line = first_visible_line; for (int i = 0; i < draw_amount; i++) { line++; @@ -840,11 +1078,8 @@ void TextEdit::_notification(int p_what) { if (syntax_coloring) { color_map = _get_line_syntax_highlighting(line); } - // ensure we at least use the font color - Color current_color = cache.font_color; - if (readonly) { - current_color.a *= readonly_alpha; - } + // Ensure we at least use the font color. + Color current_color = readonly ? cache.font_color_readonly : cache.font_color; bool underlined = false; @@ -861,6 +1096,9 @@ void TextEdit::_notification(int p_what) { const String &str = wrap_rows[line_wrap_index]; int indent_px = line_wrap_index != 0 ? get_indent_level(line) * cache.font->get_char_size(' ').width : 0; + if (indent_px >= wrap_at) { + indent_px = 0; + } if (line_wrap_index > 0) last_wrap_column += wrap_rows[line_wrap_index - 1].length(); @@ -881,7 +1119,7 @@ void TextEdit::_notification(int p_what) { if (smooth_scroll_enabled) ofs_y += (-get_v_scroll_offset()) * get_row_height(); - // check if line contains highlighted word + // Check if line contains highlighted word. int highlighted_text_col = -1; int search_text_col = -1; int highlighted_word_col = -1; @@ -903,25 +1141,25 @@ void TextEdit::_notification(int p_what) { } if (str.length() == 0) { - // draw line background if empty as we won't loop at at all + // Draw line background if empty as we won't loop at at all. if (line == cursor.line && cursor_wrap_index == line_wrap_index && highlight_current_line) { VisualServer::get_singleton()->canvas_item_add_rect(ci, Rect2(ofs_x, ofs_y, xmargin_end, get_row_height()), cache.current_line_color); } - // give visual indication of empty selected line + // Give visual indication of empty selected line. if (selection.active && line >= selection.from_line && line <= selection.to_line && char_margin >= xmargin_beg) { int char_w = cache.font->get_char_size(' ').width; VisualServer::get_singleton()->canvas_item_add_rect(ci, Rect2(xmargin_beg + ofs_x, ofs_y, char_w, get_row_height()), cache.selection_color); } } else { - // if it has text, then draw current line marker in the margin, as line number etc will draw over it, draw the rest of line marker later. + // If it has text, then draw current line marker in the margin, as line number etc will draw over it, draw the rest of line marker later. if (line == cursor.line && cursor_wrap_index == line_wrap_index && highlight_current_line) { - VisualServer::get_singleton()->canvas_item_add_rect(ci, Rect2(0, ofs_y, xmargin_beg, get_row_height()), cache.current_line_color); + VisualServer::get_singleton()->canvas_item_add_rect(ci, Rect2(0, ofs_y, xmargin_beg + ofs_x, get_row_height()), cache.current_line_color); } } if (line_wrap_index == 0) { - // only do these if we are on the first wrapped part of a line + // Only do these if we are on the first wrapped part of a line. if (text.is_breakpoint(line) && !draw_breakpoint_gutter) { #ifdef TOOLS_ENABLED @@ -931,22 +1169,75 @@ void TextEdit::_notification(int p_what) { #endif } - // draw breakpoint marker + // Draw bookmark marker. + if (text.is_bookmark(line)) { + if (draw_bookmark_gutter) { + int vertical_gap = (get_row_height() * 40) / 100; + int horizontal_gap = (cache.breakpoint_gutter_width * 30) / 100; + int marker_radius = get_row_height() - (vertical_gap * 2); + VisualServer::get_singleton()->canvas_item_add_circle(ci, Point2(cache.style_normal->get_margin(MARGIN_LEFT) + horizontal_gap - 2 + marker_radius / 2, ofs_y + vertical_gap + marker_radius / 2), marker_radius, Color(cache.bookmark_color.r, cache.bookmark_color.g, cache.bookmark_color.b)); + } + } + + // Draw breakpoint marker. if (text.is_breakpoint(line)) { if (draw_breakpoint_gutter) { int vertical_gap = (get_row_height() * 40) / 100; int horizontal_gap = (cache.breakpoint_gutter_width * 30) / 100; int marker_height = get_row_height() - (vertical_gap * 2); int marker_width = cache.breakpoint_gutter_width - (horizontal_gap * 2); - // no transparency on marker + // No transparency on marker. VisualServer::get_singleton()->canvas_item_add_rect(ci, Rect2(cache.style_normal->get_margin(MARGIN_LEFT) + horizontal_gap - 2, ofs_y + vertical_gap, marker_width, marker_height), Color(cache.breakpoint_color.r, cache.breakpoint_color.g, cache.breakpoint_color.b)); } } - // draw fold markers + // Draw info icons. + if (draw_info_gutter && text.has_info_icon(line)) { + int vertical_gap = (get_row_height() * 40) / 100; + int horizontal_gap = (cache.info_gutter_width * 30) / 100; + int gutter_left = cache.style_normal->get_margin(MARGIN_LEFT) + cache.breakpoint_gutter_width; + + Ref<Texture> info_icon = text.get_info_icon(line); + // Ensure the icon fits the gutter size. + Size2i icon_size = info_icon->get_size(); + if (icon_size.width > cache.info_gutter_width - horizontal_gap) { + icon_size.width = cache.info_gutter_width - horizontal_gap; + } + if (icon_size.height > get_row_height() - horizontal_gap) { + icon_size.height = get_row_height() - horizontal_gap; + } + + Size2i icon_pos; + int xofs = horizontal_gap - (info_icon->get_width() / 4); + int yofs = vertical_gap - (info_icon->get_height() / 4); + icon_pos.x = gutter_left + xofs + ofs_x; + icon_pos.y = ofs_y + yofs; + + draw_texture_rect(info_icon, Rect2(icon_pos, icon_size)); + } + + // Draw execution marker. + if (executing_line == line) { + if (draw_breakpoint_gutter) { + int icon_extra_size = 4; + int vertical_gap = (get_row_height() * 40) / 100; + int horizontal_gap = (cache.breakpoint_gutter_width * 30) / 100; + int marker_height = get_row_height() - (vertical_gap * 2) + icon_extra_size; + int marker_width = cache.breakpoint_gutter_width - (horizontal_gap * 2) + icon_extra_size; + cache.executing_icon->draw_rect(ci, Rect2(cache.style_normal->get_margin(MARGIN_LEFT) + horizontal_gap - 2 - icon_extra_size / 2, ofs_y + vertical_gap - icon_extra_size / 2, marker_width, marker_height), false, Color(cache.executing_line_color.r, cache.executing_line_color.g, cache.executing_line_color.b)); + } else { +#ifdef TOOLS_ENABLED + VisualServer::get_singleton()->canvas_item_add_rect(ci, Rect2(xmargin_beg + ofs_x, ofs_y + get_row_height() - EDSCALE, xmargin_end - xmargin_beg, EDSCALE), cache.executing_line_color); +#else + VisualServer::get_singleton()->canvas_item_add_rect(ci, Rect2(xmargin_beg + ofs_x, ofs_y, xmargin_end - xmargin_beg, get_row_height()), cache.executing_line_color); +#endif + } + } + + // Draw fold markers. if (draw_fold_gutter) { int horizontal_gap = (cache.fold_gutter_width * 30) / 100; - int gutter_left = cache.style_normal->get_margin(MARGIN_LEFT) + cache.breakpoint_gutter_width + cache.line_number_w; + int gutter_left = cache.style_normal->get_margin(MARGIN_LEFT) + cache.breakpoint_gutter_width + cache.line_number_w + cache.info_gutter_width; if (is_folded(line)) { int xofs = horizontal_gap - (cache.can_fold_icon->get_width()) / 2; int yofs = (get_row_height() - cache.folded_icon->get_height()) / 2; @@ -958,7 +1249,7 @@ void TextEdit::_notification(int p_what) { } } - // draw line numbers + // Draw line numbers. if (cache.line_number_w) { int yofs = ofs_y + (get_row_height() - cache.font->get_height()) / 2; String fc = String::num(line + 1); @@ -966,18 +1257,18 @@ void TextEdit::_notification(int p_what) { fc = line_num_padding + fc; } - cache.font->draw(ci, Point2(cache.style_normal->get_margin(MARGIN_LEFT) + cache.breakpoint_gutter_width + ofs_x, yofs + cache.font->get_ascent()), fc, text.is_safe(line) ? cache.safe_line_number_color : cache.line_number_color); + cache.font->draw(ci, Point2(cache.style_normal->get_margin(MARGIN_LEFT) + cache.breakpoint_gutter_width + cache.info_gutter_width + ofs_x, yofs + cache.font->get_ascent()), fc, text.is_safe(line) ? cache.safe_line_number_color : cache.line_number_color); } } - //loop through characters in one line + // Loop through characters in one line. for (int j = 0; j < str.length(); j++) { if (syntax_coloring) { if (color_map.has(last_wrap_column + j)) { current_color = color_map[last_wrap_column + j].color; - if (readonly) { - current_color.a *= readonly_alpha; + if (readonly && current_color.a > cache.font_color_readonly.a) { + current_color.a = cache.font_color_readonly.a; } } color = current_color; @@ -985,20 +1276,20 @@ void TextEdit::_notification(int p_what) { int char_w; - //handle tabulator + // Handle tabulator. char_w = text.get_char_width(str[j], str[j + 1], char_ofs); if ((char_ofs + char_margin) < xmargin_beg) { char_ofs += char_w; - // line highlighting handle horizontal clipping + // Line highlighting handle horizontal clipping. if (line == cursor.line && cursor_wrap_index == line_wrap_index && highlight_current_line) { if (j == str.length() - 1) { - // end of line when last char is skipped + // End of line when last char is skipped. VisualServer::get_singleton()->canvas_item_add_rect(ci, Rect2(xmargin_beg + ofs_x, ofs_y, xmargin_end - (char_ofs + char_margin + char_w), get_row_height()), cache.current_line_color); } else if ((char_ofs + char_margin) > xmargin_beg) { - // char next to margin is skipped + // Char next to margin is skipped. VisualServer::get_singleton()->canvas_item_add_rect(ci, Rect2(xmargin_beg + ofs_x, ofs_y, (char_ofs + char_margin) - (xmargin_beg + ofs_x), get_row_height()), cache.current_line_color); } } @@ -1006,16 +1297,13 @@ void TextEdit::_notification(int p_what) { } if ((char_ofs + char_margin + char_w) >= xmargin_end) { - if (syntax_coloring) - continue; - else - break; + break; } bool in_search_result = false; if (search_text_col != -1) { - // if we are at the end check for new search result on same line + // If we are at the end check for new search result on same line. if (j >= search_text_col + search_text.length()) search_text_col = _get_column_pos_of_word(search_text, str, search_flags, j); @@ -1026,19 +1314,19 @@ void TextEdit::_notification(int p_what) { } } - //current line highlighting + // Current line highlighting. bool in_selection = (selection.active && line >= selection.from_line && line <= selection.to_line && (line > selection.from_line || last_wrap_column + j >= selection.from_column) && (line < selection.to_line || last_wrap_column + j < selection.to_column)); if (line == cursor.line && cursor_wrap_index == line_wrap_index && highlight_current_line) { - // draw the wrap indent offset highlight + // Draw the wrap indent offset highlight. if (line_wrap_index != 0 && j == 0) { - VisualServer::get_singleton()->canvas_item_add_rect(ci, Rect2(char_ofs + char_margin - indent_px, ofs_y, (char_ofs + char_margin), get_row_height()), cache.current_line_color); + VisualServer::get_singleton()->canvas_item_add_rect(ci, Rect2(char_ofs + char_margin + ofs_x - indent_px, ofs_y, indent_px, get_row_height()), cache.current_line_color); } - // if its the last char draw to end of the line + // If its the last char draw to end of the line. if (j == str.length() - 1) { - VisualServer::get_singleton()->canvas_item_add_rect(ci, Rect2(char_ofs + char_margin + char_w, ofs_y, xmargin_end - (char_ofs + char_margin + char_w), get_row_height()), cache.current_line_color); + VisualServer::get_singleton()->canvas_item_add_rect(ci, Rect2(char_ofs + char_margin + char_w + ofs_x, ofs_y, xmargin_end - (char_ofs + char_margin + char_w), get_row_height()), cache.current_line_color); } - // actual text + // Actual text. if (!in_selection) { VisualServer::get_singleton()->canvas_item_add_rect(ci, Rect2(Point2i(char_ofs + char_margin + ofs_x, ofs_y), Size2i(char_w, get_row_height())), cache.current_line_color); } @@ -1060,17 +1348,17 @@ void TextEdit::_notification(int p_what) { VisualServer::get_singleton()->canvas_item_add_rect(ci, Rect2(Point2i(char_ofs + char_margin + char_w + ofs_x - 1, ofs_y), Size2i(1, get_row_height())), border_color); } - if (highlight_all_occurrences) { + if (highlight_all_occurrences && !only_whitespaces_highlighted) { if (highlighted_text_col != -1) { - // if we are at the end check for new word on same line + // If we are at the end check for new word on same line. if (j > highlighted_text_col + highlighted_text.length()) { highlighted_text_col = _get_column_pos_of_word(highlighted_text, str, SEARCH_MATCH_CASE | SEARCH_WHOLE_WORDS, j); } bool in_highlighted_word = (j >= highlighted_text_col && j < highlighted_text_col + highlighted_text.length()); - // if this is the original highlighted text we don't want to highlight it again + // If this is the original highlighted text we don't want to highlight it again. if (cursor.line == line && cursor_wrap_index == line_wrap_index && (cursor.column >= highlighted_text_col && cursor.column <= highlighted_text_col + highlighted_text.length())) { in_highlighted_word = false; } @@ -1095,7 +1383,7 @@ void TextEdit::_notification(int p_what) { if (brace_open_mismatch) color = cache.brace_mismatch_color; - drawer.draw_char(ci, Point2i(char_ofs + char_margin + ofs_x, yofs + ascent), '_', str[j + 1], in_selection && override_selected_font_color ? cache.font_selected_color : color); + drawer.draw_char(ci, Point2i(char_ofs + char_margin + ofs_x, yofs + ascent), '_', str[j + 1], in_selection && override_selected_font_color ? cache.font_color_selected : color); } if ((brace_close_match_line == line && brace_close_match_column == last_wrap_column + j) || @@ -1103,7 +1391,7 @@ void TextEdit::_notification(int p_what) { if (brace_close_mismatch) color = cache.brace_mismatch_color; - drawer.draw_char(ci, Point2i(char_ofs + char_margin + ofs_x, yofs + ascent), '_', str[j + 1], in_selection && override_selected_font_color ? cache.font_selected_color : color); + drawer.draw_char(ci, Point2i(char_ofs + char_margin + ofs_x, yofs + ascent), '_', str[j + 1], in_selection && override_selected_font_color ? cache.font_color_selected : color); } } @@ -1169,24 +1457,28 @@ void TextEdit::_notification(int p_what) { if (cursor.column == last_wrap_column + j && cursor.line == line && cursor_wrap_index == line_wrap_index && block_caret && draw_caret && !insert_mode) { color = cache.caret_background_color; } else if (!syntax_coloring && block_caret) { - color = cache.font_color; - color.a *= readonly_alpha; + color = readonly ? cache.font_color_readonly : cache.font_color; } if (str[j] >= 32) { int yofs = ofs_y + (get_row_height() - cache.font->get_height()) / 2; - int w = drawer.draw_char(ci, Point2i(char_ofs + char_margin + ofs_x, yofs + ascent), str[j], str[j + 1], in_selection && override_selected_font_color ? cache.font_selected_color : color); + int w = drawer.draw_char(ci, Point2i(char_ofs + char_margin + ofs_x, yofs + ascent), str[j], str[j + 1], in_selection && override_selected_font_color ? cache.font_color_selected : color); if (underlined) { float line_width = 1.0; #ifdef TOOLS_ENABLED line_width *= EDSCALE; #endif - draw_rect(Rect2(char_ofs + char_margin + ofs_x, yofs + ascent + 2, w, line_width), in_selection && override_selected_font_color ? cache.font_selected_color : color); + draw_rect(Rect2(char_ofs + char_margin + ofs_x, yofs + ascent + 2, w, line_width), in_selection && override_selected_font_color ? cache.font_color_selected : color); } } else if (draw_tabs && str[j] == '\t') { int yofs = (get_row_height() - cache.tab_icon->get_height()) / 2; - cache.tab_icon->draw(ci, Point2(char_ofs + char_margin + ofs_x, ofs_y + yofs), in_selection && override_selected_font_color ? cache.font_selected_color : color); + cache.tab_icon->draw(ci, Point2(char_ofs + char_margin + ofs_x, ofs_y + yofs), in_selection && override_selected_font_color ? cache.font_color_selected : color); + } + + if (draw_spaces && str[j] == ' ') { + int yofs = (get_row_height() - cache.space_icon->get_height()) / 2; + cache.space_icon->draw(ci, Point2(char_ofs + char_margin + ofs_x, ofs_y + yofs), in_selection && override_selected_font_color ? cache.font_color_selected : color); } char_ofs += char_w; @@ -1261,16 +1553,9 @@ void TextEdit::_notification(int p_what) { } } - if (line_length_guideline) { - int x = xmargin_beg + cache.font->get_char_size('0').width * line_length_guideline_col - cursor.x_ofs; - if (x > xmargin_beg && x < xmargin_end) { - VisualServer::get_singleton()->canvas_item_add_line(ci, Point2(x, 0), Point2(x, size.height), cache.line_length_guideline_color); - } - } - bool completion_below = false; if (completion_active) { - // code completion box + // Code completion box. Ref<StyleBox> csb = get_stylebox("completion"); int maxlines = get_constant("completion_lines"); int cmax_width = get_constant("completion_max_width") * cache.font->get_char_size('x').x; @@ -1284,7 +1569,7 @@ void TextEdit::_notification(int p_what) { if (completion_options.size() < 50) { for (int i = 0; i < completion_options.size(); i++) { - int w2 = MIN(cache.font->get_string_size(completion_options[i]).x, cmax_width); + int w2 = MIN(cache.font->get_string_size(completion_options[i].display).x, cmax_width); if (w2 > w) w = w2; } @@ -1292,6 +1577,11 @@ void TextEdit::_notification(int p_what) { w = cmax_width; } + // Add space for completion icons. + const int icon_hsep = get_constant("hseparation", "ItemList"); + Size2 icon_area_size(get_row_height(), get_row_height()); + w += icon_area_size.width + icon_hsep; + int th = h + csb->get_minimum_size().y; if (cursor_pos.y + get_row_height() + th > get_size().height) { @@ -1319,7 +1609,7 @@ void TextEdit::_notification(int p_what) { } int line_from = CLAMP(completion_index - lines / 2, 0, completion_options.size() - lines); VisualServer::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, Size2(nofs, completion_rect.size.height)), cache.completion_existing_color); + draw_rect(Rect2(completion_rect.position + Vector2(icon_area_size.x + icon_hsep, 0), Size2(MIN(nofs, completion_rect.size.width - (icon_area_size.x + icon_hsep)), completion_rect.size.height)), cache.completion_existing_color); for (int i = 0; i < lines; i++) { @@ -1327,16 +1617,30 @@ void TextEdit::_notification(int p_what) { ERR_CONTINUE(l < 0 || l >= completion_options.size()); Color text_color = cache.completion_font_color; for (int j = 0; j < color_regions.size(); j++) { - if (completion_options[l].begins_with(color_regions[j].begin_key)) { + if (completion_options[l].insert_text.begins_with(color_regions[j].begin_key)) { text_color = color_regions[j].color; } } int yofs = (get_row_height() - cache.font->get_height()) / 2; - draw_string(cache.font, Point2(completion_rect.position.x, completion_rect.position.y + i * get_row_height() + cache.font->get_ascent() + yofs), completion_options[l], text_color, completion_rect.size.width); + Point2 title_pos(completion_rect.position.x, completion_rect.position.y + i * get_row_height() + cache.font->get_ascent() + yofs); + + // Draw completion icon if it is valid. + Ref<Texture> icon = completion_options[l].icon; + Rect2 icon_area(completion_rect.position.x, completion_rect.position.y + i * get_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; + draw_string(cache.font, title_pos, completion_options[l].display, text_color, completion_rect.size.width - (icon_area_size.x + icon_hsep)); } if (scrollw) { - //draw a small scroll rectangle to show a position in the options + // Draw a small scroll rectangle to show a position in the options. float r = maxlines / (float)completion_options.size(); float o = line_from / (float)completion_options.size(); draw_rect(Rect2(completion_rect.position.x + completion_rect.size.width, completion_rect.position.y + o * completion_rect.size.y, scrollw, completion_rect.size.y * r), scrollc); @@ -1345,7 +1649,7 @@ void TextEdit::_notification(int p_what) { completion_line_ofs = line_from; } - // check to see if the hint should be drawn + // Check to see if the hint should be drawn. bool show_hint = false; if (completion_hint != "") { if (completion_active) { @@ -1381,8 +1685,8 @@ void TextEdit::_notification(int p_what) { } } - Size2 size = Size2(max_w, sc * font->get_height() + spacing); - Size2 minsize = size + sb->get_minimum_size(); + Size2 size2 = Size2(max_w, sc * font->get_height() + spacing); + Size2 minsize = size2 + sb->get_minimum_size(); if (completion_hint_offset == -0xFFFF) { completion_hint_offset = cursor_pos.x - offset; @@ -1422,7 +1726,6 @@ void TextEdit::_notification(int p_what) { OS::get_singleton()->set_ime_active(true); OS::get_singleton()->set_ime_position(get_global_position() + cursor_pos + Point2(0, get_row_height())); } - } break; case NOTIFICATION_FOCUS_ENTER: { @@ -1494,8 +1797,7 @@ void TextEdit::_consume_pair_symbol(CharType ch) { } if ((ch == '\'' || ch == '"') && - cursor_get_column() > 0 && - _is_text_char(text[cursor.line][cursor_get_column() - 1])) { + cursor_get_column() > 0 && _is_text_char(text[cursor.line][cursor_get_column() - 1]) && !_is_pair_right_symbol(text[cursor.line][cursor_get_column()])) { insert_text_at_cursor(ch_single); cursor_set_column(cursor_position_to_move); return; @@ -1516,7 +1818,6 @@ void TextEdit::_consume_pair_symbol(CharType ch) { insert_text_at_cursor(ch_pair); cursor_set_column(cursor_position_to_move); - return; } void TextEdit::_consume_backspace_for_pair_symbol(int prev_line, int prev_column) { @@ -1557,37 +1858,34 @@ void TextEdit::backspace_at_cursor() { set_line_as_breakpoint(prev_line, true); } + if (text.has_info_icon(cursor.line)) { + set_line_info_icon(prev_line, text.get_info_icon(cursor.line), text.get_info(cursor.line)); + } + if (auto_brace_completion_enabled && cursor.column > 0 && _is_pair_left_symbol(text[cursor.line][cursor.column - 1])) { _consume_backspace_for_pair_symbol(prev_line, prev_column); } else { - // handle space indentation - if (cursor.column - indent_size >= 0 && indent_using_spaces) { - - // if there is enough spaces to count as a tab + // Handle space indentation. + if (cursor.column != 0 && indent_using_spaces) { + // Check if there are no other chars before cursor, just indentation. bool unindent = true; - for (int i = 1; i <= indent_size; i++) { - if (text[cursor.line][cursor.column - i] != ' ') { - unindent = false; - break; - } - } - - // and it is before the first character int i = 0; while (i < cursor.column && i < text[cursor.line].length()) { - if (text[cursor.line][i] != ' ' && text[cursor.line][i] != '\t') { + if (!_is_whitespace(text[cursor.line][i])) { unindent = false; break; } i++; } - // then we can remove it as a single character. + // Then we can remove all spaces as a single character. if (unindent) { - _remove_text(cursor.line, cursor.column - indent_size, cursor.line, cursor.column); - prev_column = cursor.column - indent_size; + // We want to remove spaces up to closest indent, or whole indent if cursor is pointing at it. + int spaces_to_delete = _calculate_spaces_till_next_left_indent(cursor.column); + prev_column = cursor.column - spaces_to_delete; + _remove_text(cursor.line, prev_column, cursor.line, cursor.column); } else { _remove_text(prev_line, prev_column, cursor.line, cursor.column); } @@ -1604,6 +1902,10 @@ void TextEdit::indent_right() { int start_line; int end_line; + + // This value informs us by how much we changed selection position by indenting right. + // Default is 1 for tab indentation. + int selection_offset = 1; begin_complex_operation(); if (is_selection_active()) { @@ -1614,7 +1916,7 @@ void TextEdit::indent_right() { end_line = start_line; } - // ignore if the cursor is not past the first column + // Ignore if the cursor is not past the first column. if (is_selection_active() && get_selection_to_column() == 0) { end_line--; } @@ -1622,18 +1924,24 @@ void TextEdit::indent_right() { for (int i = start_line; i <= end_line; i++) { String line_text = get_line(i); if (indent_using_spaces) { - line_text = space_indent + line_text; + // We don't really care where selection is - we just need to know indentation level at the beginning of the line. + int left = _find_first_non_whitespace_column_of_line(line_text); + int spaces_to_add = _calculate_spaces_till_next_right_indent(left); + // Since we will add this much spaces we want move whole selection and cursor by this much. + selection_offset = spaces_to_add; + for (int j = 0; j < spaces_to_add; j++) + line_text = ' ' + line_text; } else { line_text = '\t' + line_text; } set_line(i, line_text); } - // fix selection and cursor being off by one on the last line + // Fix selection and cursor being off after shifting selection right. if (is_selection_active()) { - select(selection.from_line, selection.from_column + 1, selection.to_line, selection.to_column + 1); + select(selection.from_line, selection.from_column + selection_offset, selection.to_line, selection.to_column + selection_offset); } - cursor_set_column(cursor.column + 1, false); + cursor_set_column(cursor.column + selection_offset, false); end_complex_operation(); update(); } @@ -1642,6 +1950,14 @@ void TextEdit::indent_left() { int start_line; int end_line; + + // Moving cursor and selection after unindenting can get tricky because + // changing content of line can move cursor and selection on it's own (if new line ends before previous position of either), + // therefore we just remember initial values and at the end of the operation offset them by number of removed characters. + int removed_characters = 0; + int initial_selection_end_column = selection.to_column; + int initial_cursor_column = cursor.column; + begin_complex_operation(); if (is_selection_active()) { @@ -1652,7 +1968,7 @@ void TextEdit::indent_left() { end_line = start_line; } - // ignore if the cursor is not past the first column + // Ignore if the cursor is not past the first column. if (is_selection_active() && get_selection_to_column() == 0) { end_line--; } @@ -1664,21 +1980,43 @@ void TextEdit::indent_left() { if (line_text.begins_with("\t")) { line_text = line_text.substr(1, line_text.length()); set_line(i, line_text); - } else if (line_text.begins_with(space_indent)) { - line_text = line_text.substr(indent_size, line_text.length()); + removed_characters = 1; + } else if (line_text.begins_with(" ")) { + // When unindenting we aim to remove spaces before line that has selection no matter what is selected, + // so we start of by finding first non whitespace character of line + int left = _find_first_non_whitespace_column_of_line(line_text); + + // Here we remove only enough spaces to align text to nearest full multiple of indentation_size. + // In case where selection begins at the start of indentation_size multiple we remove whole indentation level. + int spaces_to_remove = _calculate_spaces_till_next_left_indent(left); + + line_text = line_text.substr(spaces_to_remove, line_text.length()); set_line(i, line_text); + removed_characters = spaces_to_remove; } } - // fix selection and cursor being off by one on the last line + // 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 - 1, selection.to_line, selection.to_column - 1); + select(selection.from_line, selection.from_column - removed_characters, + selection.to_line, initial_selection_end_column - removed_characters); } - cursor_set_column(cursor.column - 1, false); + cursor_set_column(initial_cursor_column - removed_characters, false); end_complex_operation(); update(); } +int TextEdit::_calculate_spaces_till_next_left_indent(int column) { + int spaces_till_indent = column % indent_size; + if (spaces_till_indent == 0) + spaces_till_indent = indent_size; + return spaces_till_indent; +} + +int TextEdit::_calculate_spaces_till_next_right_indent(int column) { + return indent_size - column % indent_size; +} + void TextEdit::_get_mouse_pos(const Point2i &p_mouse, int &r_row, int &r_col) const { float rows = p_mouse.y; @@ -1699,7 +2037,7 @@ void TextEdit::_get_mouse_pos(const Point2i &p_mouse, int &r_row, int &r_col) co } if (row < 0) - row = 0; //todo + row = 0; // TODO. int col = 0; @@ -1709,15 +2047,15 @@ void TextEdit::_get_mouse_pos(const Point2i &p_mouse, int &r_row, int &r_col) co col = text[row].size(); } else { - int colx = p_mouse.x - (cache.style_normal->get_margin(MARGIN_LEFT) + cache.line_number_w + cache.breakpoint_gutter_width + cache.fold_gutter_width); + int colx = p_mouse.x - (cache.style_normal->get_margin(MARGIN_LEFT) + cache.line_number_w + cache.breakpoint_gutter_width + cache.fold_gutter_width + cache.info_gutter_width); colx += cursor.x_ofs; col = get_char_pos_for_line(colx, row, wrap_index); if (is_wrap_enabled() && wrap_index < times_line_wraps(row)) { - // move back one if we are at the end of the row - Vector<String> rows = get_wrap_rows_text(row); + // Move back one if we are at the end of the row. + Vector<String> rows2 = get_wrap_rows_text(row); int row_end_col = 0; for (int i = 0; i < wrap_index + 1; i++) { - row_end_col += rows[i].length(); + row_end_col += rows2[i].length(); } if (col >= row_end_col) col -= 1; @@ -1728,8 +2066,66 @@ void TextEdit::_get_mouse_pos(const Point2i &p_mouse, int &r_row, int &r_col) co r_col = col; } +void TextEdit::_get_minimap_mouse_row(const Point2i &p_mouse, int &r_row) const { + + float rows = p_mouse.y; + rows -= cache.style_normal->get_margin(MARGIN_TOP); + rows /= (minimap_char_size.y + minimap_line_spacing); + rows += get_v_scroll_offset(); + + // calculate visible lines + int minimap_visible_lines = _get_minimap_visible_rows(); + int visible_rows = get_visible_rows() + 1; + int first_visible_line = get_first_visible_line() - 1; + int draw_amount = visible_rows + (smooth_scroll_enabled ? 1 : 0); + draw_amount += times_line_wraps(first_visible_line + 1); + int minimap_line_height = (minimap_char_size.y + minimap_line_spacing); + + // calculate viewport size and y offset + int viewport_height = (draw_amount - 1) * minimap_line_height; + int control_height = _get_control_height() - viewport_height; + int viewport_offset_y = round(get_scroll_pos_for_line(first_visible_line) * control_height) / ((v_scroll->get_max() <= minimap_visible_lines) ? (minimap_visible_lines - draw_amount) : (v_scroll->get_max() - draw_amount)); + + // calculate the first line. + int num_lines_before = round((viewport_offset_y) / minimap_line_height); + int wi; + int minimap_line = (v_scroll->get_max() <= minimap_visible_lines) ? -1 : first_visible_line; + if (first_visible_line > 0 && minimap_line >= 0) { + minimap_line -= num_lines_from_rows(first_visible_line, 0, -num_lines_before, wi); + minimap_line -= (smooth_scroll_enabled ? 1 : 0); + } else { + minimap_line = 0; + } + + int row = minimap_line + Math::floor(rows); + int wrap_index = 0; + + if (is_wrap_enabled() || is_hiding_enabled()) { + + int f_ofs = num_lines_from_rows(minimap_line, cursor.wrap_ofs, rows + (1 * SGN(rows)), wrap_index) - 1; + if (rows < 0) { + row = minimap_line - f_ofs; + } else { + row = minimap_line + f_ofs; + } + } + + if (row < 0) { + row = 0; + } + + if (row >= text.size()) { + row = text.size() - 1; + } + + r_row = row; +} + void TextEdit::_gui_input(const Ref<InputEvent> &p_gui_input) { + 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()) { @@ -1804,21 +2200,31 @@ void TextEdit::_gui_input(const Ref<InputEvent> &p_gui_input) { return; } - // toggle breakpoint on gutter click + // Toggle breakpoint on gutter click. if (draw_breakpoint_gutter) { int gutter = cache.style_normal->get_margin(MARGIN_LEFT); - if (mb->get_position().x > gutter && mb->get_position().x <= gutter + cache.breakpoint_gutter_width + 3) { + if (mb->get_position().x > gutter - 6 && mb->get_position().x <= gutter + cache.breakpoint_gutter_width - 3) { set_line_as_breakpoint(row, !is_line_set_as_breakpoint(row)); emit_signal("breakpoint_toggled", row); return; } } - // toggle fold on gutter click if can + // Emit info clicked. + if (draw_info_gutter && text.has_info_icon(row)) { + int left_margin = cache.style_normal->get_margin(MARGIN_LEFT); + int gutter_left = left_margin + cache.breakpoint_gutter_width; + if (mb->get_position().x > gutter_left - 6 && mb->get_position().x <= gutter_left + cache.info_gutter_width - 3) { + emit_signal("info_clicked", row, text.get_info(row)); + return; + } + } + + // Toggle fold on gutter click if can. if (draw_fold_gutter) { int left_margin = cache.style_normal->get_margin(MARGIN_LEFT); - int gutter_left = left_margin + cache.breakpoint_gutter_width + cache.line_number_w; + int gutter_left = left_margin + cache.breakpoint_gutter_width + cache.line_number_w + cache.info_gutter_width; if (mb->get_position().x > gutter_left - 6 && mb->get_position().x <= gutter_left + cache.fold_gutter_width - 3) { if (is_folded(row)) { unfold_line(row); @@ -1829,16 +2235,24 @@ void TextEdit::_gui_input(const Ref<InputEvent> &p_gui_input) { } } - // unfold on folded icon click + // Unfold on folded icon click. if (is_folded(row)) { int line_width = text.get_line_width(row); - line_width += cache.style_normal->get_margin(MARGIN_LEFT) + cache.line_number_w + cache.breakpoint_gutter_width + cache.fold_gutter_width - cursor.x_ofs; + line_width += cache.style_normal->get_margin(MARGIN_LEFT) + cache.line_number_w + cache.breakpoint_gutter_width + cache.info_gutter_width + cache.fold_gutter_width - cursor.x_ofs; if (mb->get_position().x > line_width - 3 && mb->get_position().x <= line_width + cache.folded_eol_icon->get_width() + 3) { unfold_line(row); return; } } + // minimap + if (draw_minimap) { + _update_minimap_click(); + if (dragging_minimap) { + return; + } + } + int prev_col = cursor.column; int prev_line = cursor.line; @@ -1896,9 +2310,6 @@ void TextEdit::_gui_input(const Ref<InputEvent> &p_gui_input) { } else { - //if sel active and dblick last time < something - - //else selection.active = false; selection.selecting_mode = Selection::MODE_POINTER; selection.selecting_line = row; @@ -1906,14 +2317,14 @@ void TextEdit::_gui_input(const Ref<InputEvent> &p_gui_input) { } if (!mb->is_doubleclick() && (OS::get_singleton()->get_ticks_msec() - last_dblclk) < 600 && cursor.line == prev_line) { - //tripleclick select line + + // Triple-click select line. selection.selecting_mode = Selection::MODE_LINE; _update_selection_mode_line(); last_dblclk = 0; - } else if (mb->is_doubleclick() && text[cursor.line].length()) { - //doubleclick select word + // Double-click select word. selection.selecting_mode = Selection::MODE_WORD; _update_selection_mode_word(); last_dblclk = OS::get_singleton()->get_ticks_msec(); @@ -1938,7 +2349,7 @@ void TextEdit::_gui_input(const Ref<InputEvent> &p_gui_input) { int to_column = get_selection_to_column(); if (row < from_line || row > to_line || (row == from_line && col < from_column) || (row == to_line && col > to_column)) { - // Right click is outside the selected text + // Right click is outside the selected text. deselect(); } } @@ -1950,15 +2361,20 @@ void TextEdit::_gui_input(const Ref<InputEvent> &p_gui_input) { menu->set_position(get_global_transform().xform(get_local_mouse_position())); menu->set_size(Vector2(1, 1)); + menu->set_scale(get_global_transform().get_scale()); menu->popup(); grab_focus(); } } else { - if (mb->get_button_index() == BUTTON_LEFT) + if (mb->get_button_index() == BUTTON_LEFT) { + dragging_minimap = false; + dragging_selection = false; + can_drag_minimap = false; click_select_held->stop(); + } - // notify to show soft keyboard + // Notify to show soft keyboard. notification(NOTIFICATION_FOCUS_ENTER); } } @@ -1973,6 +2389,9 @@ void TextEdit::_gui_input(const Ref<InputEvent> &p_gui_input) { _scroll_down(delta); } h_scroll->set_value(h_scroll->get_value() + pan_gesture->get_delta().x * 100); + if (v_scroll->get_value() != prev_v_scroll || h_scroll->get_value() != prev_h_scroll) + accept_event(); // Accept event if scroll changed. + return; } @@ -1996,31 +2415,40 @@ 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() & BUTTON_MASK_LEFT && get_viewport()->gui_get_drag_data() == Variant()) { // Ignore if dragging. _reset_caret_blink_timer(); - switch (selection.selecting_mode) { - case Selection::MODE_POINTER: { - _update_selection_mode_pointer(); - } break; - case Selection::MODE_WORD: { - _update_selection_mode_word(); - } break; - case Selection::MODE_LINE: { - _update_selection_mode_line(); - } break; - default: { - break; + if (draw_minimap && !dragging_selection) { + _update_minimap_drag(); + } + + if (!dragging_minimap) { + switch (selection.selecting_mode) { + case Selection::MODE_POINTER: { + _update_selection_mode_pointer(); + } break; + case Selection::MODE_WORD: { + _update_selection_mode_word(); + } break; + case Selection::MODE_LINE: { + _update_selection_mode_line(); + } break; + default: { + break; + } } } } } + if (v_scroll->get_value() != prev_v_scroll || h_scroll->get_value() != prev_h_scroll) + accept_event(); // Accept event if scroll changed. + Ref<InputEventKey> k = p_gui_input; if (k.is_valid()) { - k = k->duplicate(); //it will be modified later on + k = k->duplicate(); // It will be modified later on. #ifdef OSX_ENABLED if (k->get_scancode() == KEY_META) { @@ -2156,11 +2584,11 @@ void TextEdit::_gui_input(const Ref<InputEvent> &p_gui_input) { _consume_pair_symbol(chr[0]); } else { - // remove the old character if in insert mode + // Remove the old character if in insert mode. if (insert_mode) { begin_complex_operation(); - // make sure we don't try and remove empty space + // Make sure we don't try and remove empty space. if (cursor.column < get_line(cursor.line).length()) { _remove_text(cursor.line, cursor.column, cursor.line, cursor.column + 1); } @@ -2182,9 +2610,9 @@ void TextEdit::_gui_input(const Ref<InputEvent> &p_gui_input) { _cancel_completion(); } - /* TEST CONTROL FIRST!! */ + /* TEST CONTROL FIRST! */ - // some remaps for duplicate functions.. + // Some remaps for duplicate functions. if (k->get_command() && !k->get_shift() && !k->get_alt() && !k->get_metakey() && k->get_scancode() == KEY_INSERT) { k->set_scancode(KEY_C); @@ -2195,17 +2623,44 @@ void TextEdit::_gui_input(const Ref<InputEvent> &p_gui_input) { k->set_command(true); k->set_shift(false); } +#ifdef APPLE_STYLE_KEYS + if (k->get_control() && !k->get_shift() && !k->get_alt() && !k->get_command()) { + uint32_t remap_key = KEY_UNKNOWN; + switch (k->get_scancode()) { + case KEY_F: { + remap_key = KEY_RIGHT; + } break; + case KEY_B: { + remap_key = KEY_LEFT; + } break; + case KEY_P: { + remap_key = KEY_UP; + } break; + case KEY_N: { + remap_key = KEY_DOWN; + } break; + case KEY_D: { + remap_key = KEY_DELETE; + } break; + case KEY_H: { + remap_key = KEY_BACKSPACE; + } break; + } + + if (remap_key != KEY_UNKNOWN) { + k->set_scancode(remap_key); + k->set_control(false); + } + } +#endif _reset_caret_blink_timer(); - // save here for insert mode, just in case it is cleared in the following section + // Save here for insert mode, just in case it is cleared in the following section. bool had_selection = selection.active; - // stuff to do when selection is active.. - if (selection.active) { - - if (readonly) - return; + // Stuff to do when selection is active. + if (!readonly && selection.active) { bool clear = false; bool unselect = false; @@ -2224,7 +2679,7 @@ void TextEdit::_gui_input(const Ref<InputEvent> &p_gui_input) { } break; case KEY_X: case KEY_C: - //special keys often used with control, wait... + // Special keys often used with control, wait. clear = (!k->get_command() || k->get_shift() || k->get_alt()); break; case KEY_DELETE: @@ -2249,7 +2704,7 @@ void TextEdit::_gui_input(const Ref<InputEvent> &p_gui_input) { case KEY_PAGEDOWN: case KEY_HOME: case KEY_END: - // ignore arrows if any modifiers are held (shift = selecting, others may be used for editor hotkeys) + // Ignore arrows if any modifiers are held (shift = selecting, others may be used for editor hotkeys). if (k->get_command() || k->get_shift() || k->get_alt()) break; unselect = true; @@ -2287,7 +2742,7 @@ void TextEdit::_gui_input(const Ref<InputEvent> &p_gui_input) { bool scancode_handled = true; - // special scancode test... + // Special scancode test. switch (k->get_scancode()) { @@ -2299,7 +2754,7 @@ void TextEdit::_gui_input(const Ref<InputEvent> &p_gui_input) { String ins = "\n"; - //keep indentation + // Keep indentation. int space_count = 0; for (int i = 0; i < cursor.column; i++) { if (text[cursor.line][i] == '\t') { @@ -2330,10 +2785,10 @@ void TextEdit::_gui_input(const Ref<InputEvent> &p_gui_input) { bool brace_indent = false; - // no need to indent if we are going upwards. + // No need to indent if we are going upwards. if (auto_indent && !(k->get_command() && k->get_shift())) { - // indent once again if previous line will end with ':' or '{' and the line is not a comment - // (i.e. colon/brace precedes current cursor position) + // Indent once again if previous line will end with ':' or '{' and the line is not a comment + // (i.e. colon/brace precedes current cursor position). if (cursor.column > 0 && (text[cursor.line][cursor.column - 1] == ':' || text[cursor.line][cursor.column - 1] == '{') && !is_line_comment(cursor.line)) { if (indent_using_spaces) { ins += space_indent; @@ -2341,7 +2796,7 @@ void TextEdit::_gui_input(const Ref<InputEvent> &p_gui_input) { ins += "\t"; } - // no need to move the brace below if we are not taking the text with us. + // No need to move the brace below if we are not taking the text with us. if (text[cursor.line][cursor.column] == '}' && !k->get_command()) { brace_indent = true; ins += "\n" + ins.substr(1, ins.length() - 2); @@ -2383,7 +2838,7 @@ void TextEdit::_gui_input(const Ref<InputEvent> &p_gui_input) { } } break; case KEY_TAB: { - if (k->get_command()) break; // avoid tab when command + if (k->get_command()) break; // Avoid tab when command. if (readonly) break; @@ -2397,15 +2852,11 @@ void TextEdit::_gui_input(const Ref<InputEvent> &p_gui_input) { } else { if (k->get_shift()) { - //simple unindent + // Simple unindent. int cc = cursor.column; - - const int len = text[cursor.line].length(); const String &line = text[cursor.line]; - int left = 0; // number of whitespace chars at beginning of line - while (left < len && (line[left] == '\t' || line[left] == ' ')) - left++; + int left = _find_first_non_whitespace_column_of_line(line); cc = MIN(cc, left); while (cc < indent_size && cc < left && line[cc] == ' ') @@ -2413,24 +2864,18 @@ void TextEdit::_gui_input(const Ref<InputEvent> &p_gui_input) { if (cc > 0 && cc <= text[cursor.line].length()) { if (text[cursor.line][cc - 1] == '\t') { + // Tabs unindentation. _remove_text(cursor.line, cc - 1, cursor.line, cc); if (cursor.column >= left) cursor_set_column(MAX(0, cursor.column - 1)); update(); } else { - int n = 0; - - for (int i = 1; i <= MIN(cc, indent_size); i++) { - if (line[cc - i] != ' ') { - break; - } - n++; - } - - if (n > 0) { - _remove_text(cursor.line, cc - n, cursor.line, cc); - if (cursor.column > left - n) // inside text? - cursor_set_column(MAX(0, cursor.column - n)); + // Spaces unindentation. + int spaces_to_remove = _calculate_spaces_till_next_left_indent(cc); + if (spaces_to_remove > 0) { + _remove_text(cursor.line, cc - spaces_to_remove, cursor.line, cc); + if (cursor.column > left - spaces_to_remove) // Inside text? + cursor_set_column(MAX(0, cursor.column - spaces_to_remove)); update(); } } @@ -2439,9 +2884,14 @@ void TextEdit::_gui_input(const Ref<InputEvent> &p_gui_input) { update(); } } else { - //simple indent + // Simple indent. if (indent_using_spaces) { - _insert_text_at_cursor(space_indent); + // Insert only as much spaces as needed till next indentation level. + int spaces_to_add = _calculate_spaces_till_next_right_indent(cursor.column); + String indent_to_insert = String(); + for (int i = 0; i < spaces_to_add; i++) + indent_to_insert = ' ' + indent_to_insert; + _insert_text_at_cursor(indent_to_insert); } else { _insert_text_at_cursor("\t"); } @@ -2464,20 +2914,20 @@ void TextEdit::_gui_input(const Ref<InputEvent> &p_gui_input) { int line = cursor.line; int column = cursor.column; - // check if we are removing a single whitespace, if so remove it and the next char type - // else we just remove the whitespace + // Check if we are removing a single whitespace, if so remove it and the next char type, + // else we just remove the whitespace. bool only_whitespace = false; if (_is_whitespace(text[line][column - 1]) && _is_whitespace(text[line][column - 2])) { only_whitespace = true; } else if (_is_whitespace(text[line][column - 1])) { - // remove the single whitespace + // Remove the single whitespace. column--; } - // check if its a text char + // Check if its a text char. bool only_char = (_is_text_char(text[line][column - 1]) && !only_whitespace); - // if its not whitespace or char then symbol. + // If its not whitespace or char then symbol. bool only_symbols = !(only_whitespace || only_char); while (column > 0) { @@ -2517,7 +2967,7 @@ void TextEdit::_gui_input(const Ref<InputEvent> &p_gui_input) { scancode_handled = false; break; } - // numlock disabled. fallthrough to key_left + FALLTHROUGH; } case KEY_LEFT: { @@ -2532,9 +2982,22 @@ void TextEdit::_gui_input(const Ref<InputEvent> &p_gui_input) { #ifdef APPLE_STYLE_KEYS if (k->get_command()) { - cursor_set_column(0); + // Start at first column (it's slightly faster that way) and look for the first non-whitespace character. + int new_cursor_pos = 0; + for (int i = 0; i < text[cursor.line].length(); ++i) { + if (!_is_whitespace(text[cursor.line][i])) { + new_cursor_pos = i; + break; + } + } + if (new_cursor_pos == cursor.column) { + // We're already at the first text character, so move to the very beginning of the line. + cursor_set_column(0); + } else { + // We're somewhere to the right of the first text character; move to the first one. + cursor_set_column(new_cursor_pos); + } } else if (k->get_alt()) { - #else if (k->get_alt()) { scancode_handled = false; @@ -2580,7 +3043,7 @@ void TextEdit::_gui_input(const Ref<InputEvent> &p_gui_input) { scancode_handled = false; break; } - // numlock disabled. fallthrough to key_right + FALLTHROUGH; } case KEY_RIGHT: { @@ -2641,28 +3104,30 @@ void TextEdit::_gui_input(const Ref<InputEvent> &p_gui_input) { scancode_handled = false; break; } - // numlock disabled. fallthrough to key_up + FALLTHROUGH; } case KEY_UP: { - if (k->get_shift()) - _pre_shift_selection(); if (k->get_alt()) { scancode_handled = false; break; } #ifndef APPLE_STYLE_KEYS if (k->get_command()) { - _scroll_lines_up(); - break; - } #else if (k->get_command() && k->get_alt()) { +#endif _scroll_lines_up(); break; } + if (k->get_shift()) { + _pre_shift_selection(); + } + +#ifdef APPLE_STYLE_KEYS if (k->get_command()) { + cursor_set_line(0); } else #endif @@ -2692,28 +3157,28 @@ void TextEdit::_gui_input(const Ref<InputEvent> &p_gui_input) { scancode_handled = false; break; } - // numlock disabled. fallthrough to key_down + FALLTHROUGH; } case KEY_DOWN: { - if (k->get_shift()) - _pre_shift_selection(); if (k->get_alt()) { scancode_handled = false; break; } #ifndef APPLE_STYLE_KEYS if (k->get_command()) { - _scroll_lines_down(); - break; - } - #else if (k->get_command() && k->get_alt()) { +#endif _scroll_lines_down(); break; } + if (k->get_shift()) { + _pre_shift_selection(); + } + +#ifdef APPLE_STYLE_KEYS if (k->get_command()) { cursor_set_line(get_last_unhidden_line(), true, false, 9999); } else @@ -2748,7 +3213,7 @@ void TextEdit::_gui_input(const Ref<InputEvent> &p_gui_input) { int curline_len = text[cursor.line].length(); if (cursor.line == text.size() - 1 && cursor.column == curline_len) - break; //nothing to do + break; // Nothing to do. int next_line = cursor.column < curline_len ? cursor.line : cursor.line + 1; int next_column; @@ -2765,20 +3230,20 @@ void TextEdit::_gui_input(const Ref<InputEvent> &p_gui_input) { int line = cursor.line; int column = cursor.column; - // check if we are removing a single whitespace, if so remove it and the next char type - // else we just remove the whitespace + // Check if we are removing a single whitespace, if so remove it and the next char type, + // else we just remove the whitespace. bool only_whitespace = false; if (_is_whitespace(text[line][column]) && _is_whitespace(text[line][column + 1])) { only_whitespace = true; } else if (_is_whitespace(text[line][column])) { - // remove the single whitespace + // Remove the single whitespace. column++; } - // check if its a text char + // Check if its a text char. bool only_char = (_is_text_char(text[line][column]) && !only_whitespace); - // if its not whitespace or char then symbol. + // If its not whitespace or char then symbol. bool only_symbols = !(only_whitespace || only_char); while (column < curline_len) { @@ -2815,11 +3280,10 @@ void TextEdit::_gui_input(const Ref<InputEvent> &p_gui_input) { scancode_handled = false; break; } - // numlock disabled. fallthrough to key_home + FALLTHROUGH; } -#ifdef APPLE_STYLE_KEYS case KEY_HOME: { - +#ifdef APPLE_STYLE_KEYS if (k->get_shift()) _pre_shift_selection(); @@ -2829,11 +3293,7 @@ void TextEdit::_gui_input(const Ref<InputEvent> &p_gui_input) { _post_shift_selection(); else if (k->get_command() || k->get_control()) deselect(); - - } break; #else - case KEY_HOME: { - if (k->get_shift()) _pre_shift_selection(); @@ -2842,7 +3302,7 @@ void TextEdit::_gui_input(const Ref<InputEvent> &p_gui_input) { cursor_set_column(0); } else { - // move cursor column to start of wrapped row and then to start of text + // Move cursor column to start of wrapped row and then to start of text. Vector<String> rows = get_wrap_rows_text(cursor.line); int wi = get_cursor_wrap_index(); int row_start_col = 0; @@ -2850,7 +3310,7 @@ void TextEdit::_gui_input(const Ref<InputEvent> &p_gui_input) { row_start_col += rows[i].length(); } if (cursor.column == row_start_col || wi == 0) { - // compute whitespace symbols seq length + // Compute whitespace symbols seq length. int current_line_whitespace_len = 0; while (current_line_whitespace_len < text[cursor.line].length()) { CharType c = text[cursor.line][current_line_whitespace_len]; @@ -2874,19 +3334,17 @@ void TextEdit::_gui_input(const Ref<InputEvent> &p_gui_input) { deselect(); _cancel_completion(); completion_hint = ""; - - } break; #endif + } break; case KEY_KP_1: { if (k->get_unicode() != 0) { scancode_handled = false; break; } - // numlock disabled. fallthrough to key_end + FALLTHROUGH; } -#ifdef APPLE_STYLE_KEYS case KEY_END: { - +#ifdef APPLE_STYLE_KEYS if (k->get_shift()) _pre_shift_selection(); @@ -2896,18 +3354,14 @@ void TextEdit::_gui_input(const Ref<InputEvent> &p_gui_input) { _post_shift_selection(); else if (k->get_command() || k->get_control()) deselect(); - - } break; #else - case KEY_END: { - if (k->get_shift()) _pre_shift_selection(); if (k->get_command()) cursor_set_line(get_last_unhidden_line(), true, false, 9999); - // move cursor column to end of wrapped row and then to end of text + // Move cursor column to end of wrapped row and then to end of text. Vector<String> rows = get_wrap_rows_text(cursor.line); int wi = get_cursor_wrap_index(); int row_end_col = -1; @@ -2927,15 +3381,14 @@ void TextEdit::_gui_input(const Ref<InputEvent> &p_gui_input) { _cancel_completion(); completion_hint = ""; - - } break; #endif + } break; case KEY_KP_9: { if (k->get_unicode() != 0) { scancode_handled = false; break; } - // numlock disabled. fallthrough to key_pageup + FALLTHROUGH; } case KEY_PAGEUP: { @@ -2958,7 +3411,7 @@ void TextEdit::_gui_input(const Ref<InputEvent> &p_gui_input) { scancode_handled = false; break; } - // numlock disabled. fallthrough to key_pagedown + FALLTHROUGH; } case KEY_PAGEDOWN: { @@ -3061,6 +3514,10 @@ void TextEdit::_gui_input(const Ref<InputEvent> &p_gui_input) { } break; case KEY_Z: { + if (readonly) { + break; + } + if (!k->get_command()) { scancode_handled = false; break; @@ -3073,6 +3530,10 @@ void TextEdit::_gui_input(const Ref<InputEvent> &p_gui_input) { } break; case KEY_Y: { + if (readonly) { + break; + } + if (!k->get_command()) { scancode_handled = false; break; @@ -3094,7 +3555,7 @@ void TextEdit::_gui_input(const Ref<InputEvent> &p_gui_input) { } break; case KEY_SPACE: { #ifdef OSX_ENABLED - if (completion_enabled && k->get_metakey()) { //cmd-space is spotlight shortcut in OSX + if (completion_enabled && k->get_metakey()) { // cmd-space is spotlight shortcut in OSX #else if (completion_enabled && k->get_command()) { #endif @@ -3107,28 +3568,6 @@ void TextEdit::_gui_input(const Ref<InputEvent> &p_gui_input) { } break; - case KEY_U: { - if (!k->get_command() || k->get_shift()) { - scancode_handled = false; - break; - } else { - if (selection.active) { - int ini = selection.from_line; - int end = selection.to_line; - - for (int i = ini; i <= end; i++) { - _uncomment_line(i); - } - } else { - _uncomment_line(cursor.line); - if (cursor.column >= get_line(cursor.line).length()) { - cursor.column = MAX(0, get_line(cursor.line).length() - 1); - } - } - update(); - } - } break; - default: { scancode_handled = false; @@ -3137,39 +3576,25 @@ void TextEdit::_gui_input(const Ref<InputEvent> &p_gui_input) { if (scancode_handled) accept_event(); - /* - if (!scancode_handled && !k->get_command() && !k->get_alt()) { - if (k->get_unicode()>=32) { - - if (readonly) - break; - - accept_event(); - } else { - - break; - } - } -*/ if (k->get_scancode() == KEY_INSERT) { set_insert_mode(!insert_mode); accept_event(); return; } - if (!scancode_handled && !k->get_command()) { //for German kbds + if (!scancode_handled && !k->get_command()) { // For German keyboards. if (k->get_unicode() >= 32) { if (readonly) return; - // remove the old character if in insert mode and no selection + // Remove the old character if in insert mode and no selection. if (insert_mode && !had_selection) { begin_complex_operation(); - // make sure we don't try and remove empty space + // Make sure we don't try and remove empty space. if (cursor.column < get_line(cursor.line).length()) { _remove_text(cursor.line, cursor.column, cursor.line, cursor.column + 1); } @@ -3194,7 +3619,6 @@ void TextEdit::_gui_input(const Ref<InputEvent> &p_gui_input) { end_complex_operation(); } accept_event(); - } else { } } @@ -3202,28 +3626,12 @@ void TextEdit::_gui_input(const Ref<InputEvent> &p_gui_input) { } } -void TextEdit::_uncomment_line(int p_line) { - String line_text = get_line(p_line); - for (int i = 0; i < line_text.length(); i++) { - if (line_text[i] == '#') { - _remove_text(p_line, i, p_line, i + 1); - if (p_line == selection.to_line && selection.to_column > line_text.length() - 1) { - selection.to_column -= 1; - if (selection.to_column >= selection.from_column) { - selection.active = false; - } - } - return; - } else if (line_text[i] != '\t' && line_text[i] != ' ') { - return; - } - } -} - void TextEdit::_scroll_up(real_t p_delta) { - if (scrolling && smooth_scroll_enabled && SGN(target_v_scroll - v_scroll->get_value()) != SGN(-p_delta)) + if (scrolling && smooth_scroll_enabled && SGN(target_v_scroll - v_scroll->get_value()) != SGN(-p_delta)) { scrolling = false; + minimap_clicked = false; + } if (scrolling) { target_v_scroll = (target_v_scroll - p_delta); @@ -3248,8 +3656,10 @@ void TextEdit::_scroll_up(real_t p_delta) { void TextEdit::_scroll_down(real_t p_delta) { - if (scrolling && smooth_scroll_enabled && SGN(target_v_scroll - v_scroll->get_value()) != SGN(p_delta)) + if (scrolling && smooth_scroll_enabled && SGN(target_v_scroll - v_scroll->get_value()) != SGN(p_delta)) { scrolling = false; + minimap_clicked = false; + } if (scrolling) { target_v_scroll = (target_v_scroll + p_delta); @@ -3298,11 +3708,12 @@ void TextEdit::_post_shift_selection() { void TextEdit::_scroll_lines_up() { scrolling = false; + minimap_clicked = false; - // adjust the vertical scroll + // Adjust the vertical scroll. set_v_scroll(get_v_scroll() - 1); - // adjust the cursor to viewport + // Adjust the cursor to viewport. if (!selection.active) { int cur_line = cursor.line; int cur_wrap = get_cursor_wrap_index(); @@ -3317,11 +3728,12 @@ void TextEdit::_scroll_lines_up() { void TextEdit::_scroll_lines_down() { scrolling = false; + minimap_clicked = false; - // adjust the vertical scroll + // Adjust the vertical scroll. set_v_scroll(get_v_scroll() + 1); - // adjust the cursor to viewport + // Adjust the cursor to viewport. if (!selection.active) { int cur_line = cursor.line; int cur_wrap = get_cursor_wrap_index(); @@ -3338,15 +3750,15 @@ void TextEdit::_scroll_lines_down() { void TextEdit::_base_insert_text(int p_line, int p_char, const String &p_text, int &r_end_line, int &r_end_column) { - //save for undo... + // Save for undo. ERR_FAIL_INDEX(p_line, text.size()); ERR_FAIL_COND(p_char < 0); - /* STEP 1 remove \r from source text and separate in substrings */ + /* STEP 1: Remove \r from source text and separate in substrings. */ Vector<String> substrings = p_text.replace("\r", "").split("\n"); - /* STEP 2 fire breakpoint_toggled signals */ + /* STEP 2: Fire breakpoint_toggled signals. */ // Is this just a new empty line? bool shift_first_line = p_char == 0 && p_text.replace("\r", "") == "\n"; @@ -3362,39 +3774,42 @@ void TextEdit::_base_insert_text(int p_line, int p_char, const String &p_text, i } } - /* STEP 3 add spaces if the char is greater than the end of the line */ + /* STEP 3: Add spaces if the char is greater than the end of the line. */ while (p_char > text[p_line].length()) { text.set(p_line, text[p_line] + String::chr(' ')); } - /* STEP 4 separate dest string in pre and post text */ + /* STEP 4: Separate dest string in pre and post text. */ String preinsert_text = text[p_line].substr(0, p_char); String postinsert_text = text[p_line].substr(p_char, text[p_line].size()); - for (int i = 0; i < substrings.size(); i++) { - //insert the substrings + for (int j = 0; j < substrings.size(); j++) { + // Insert the substrings. - if (i == 0) { + if (j == 0) { - text.set(p_line, preinsert_text + substrings[i]); + text.set(p_line, preinsert_text + substrings[j]); } else { - text.insert(p_line + i, substrings[i]); + text.insert(p_line + j, substrings[j]); } - if (i == substrings.size() - 1) { + if (j == substrings.size() - 1) { - text.set(p_line + i, text[p_line + i] + postinsert_text); + text.set(p_line + j, text[p_line + j] + postinsert_text); } } if (shift_first_line) { text.set_breakpoint(p_line + 1, text.is_breakpoint(p_line)); text.set_hidden(p_line + 1, text.is_hidden(p_line)); + text.set_info_icon(p_line + 1, text.get_info_icon(p_line), text.get_info(p_line)); + text.set_breakpoint(p_line, false); text.set_hidden(p_line, false); + text.set_info_icon(p_line, NULL, ""); } text.set_line_wrap_amount(p_line, -1); @@ -3416,8 +3831,8 @@ String TextEdit::_base_get_text(int p_from_line, int p_from_column, int p_to_lin ERR_FAIL_INDEX_V(p_from_column, text[p_from_line].length() + 1, String()); ERR_FAIL_INDEX_V(p_to_line, text.size(), String()); ERR_FAIL_INDEX_V(p_to_column, text[p_to_line].length() + 1, String()); - ERR_FAIL_COND_V(p_to_line < p_from_line, String()); // from > to - ERR_FAIL_COND_V(p_to_line == p_from_line && p_to_column < p_from_column, String()); // from > to + ERR_FAIL_COND_V(p_to_line < p_from_line, String()); // 'from > to'. + ERR_FAIL_COND_V(p_to_line == p_from_line && p_to_column < p_from_column, String()); // 'from > to'. String ret; @@ -3440,8 +3855,8 @@ void TextEdit::_base_remove_text(int p_from_line, int p_from_column, int p_to_li ERR_FAIL_INDEX(p_from_column, text[p_from_line].length() + 1); ERR_FAIL_INDEX(p_to_line, text.size()); ERR_FAIL_INDEX(p_to_column, text[p_to_line].length() + 1); - ERR_FAIL_COND(p_to_line < p_from_line); // from > to - ERR_FAIL_COND(p_to_line == p_from_line && p_to_column < p_from_column); // from > to + ERR_FAIL_COND(p_to_line < p_from_line); // 'from > to'. + ERR_FAIL_COND(p_to_line == p_from_line && p_to_column < p_from_column); // 'from > to'. String pre_text = text[p_from_line].substr(0, p_from_column); String post_text = text[p_to_line].substr(p_to_column, text[p_to_line].length()); @@ -3503,22 +3918,22 @@ void TextEdit::_insert_text(int p_line, int p_char, const String &p_text, int *r op.chain_forward = false; op.chain_backward = false; - //see if it shold just be set as current op + // See if it should just be set as current op. if (current_op.type != op.type) { op.prev_version = get_version(); _push_current_op(); current_op = op; - return; //set as current op, return + return; // Set as current op, return. } - //see if it can be merged + // See if it can be merged. if (current_op.to_line != p_line || current_op.to_column != p_char) { op.prev_version = get_version(); _push_current_op(); current_op = op; - return; //set as current op, return + return; // Set as current op, return. } - //merge current op + // Merge current op. current_op.text += p_text; current_op.to_column = retchar; @@ -3542,7 +3957,7 @@ void TextEdit::_remove_text(int p_from_line, int p_from_column, int p_to_line, i if (!undo_enabled) return; - /* UNDO!! */ + /* UNDO! */ TextOperation op; op.type = TextOperation::TYPE_REMOVE; op.from_line = p_from_line; @@ -3554,27 +3969,20 @@ void TextEdit::_remove_text(int p_from_line, int p_from_column, int p_to_line, i op.chain_forward = false; op.chain_backward = false; - //see if it shold just be set as current op + // See if it should just be set as current op. if (current_op.type != op.type) { op.prev_version = get_version(); _push_current_op(); current_op = op; - return; //set as current op, return + return; // Set as current op, return. } - //see if it can be merged + // See if it can be merged. if (current_op.from_line == p_to_line && current_op.from_column == p_to_column) { - //basckace or similar + // Backspace or similar. current_op.text = text + current_op.text; current_op.from_line = p_from_line; current_op.from_column = p_from_column; - return; //update current op - } - if (current_op.from_line == p_from_line && current_op.from_column == p_from_column) { - - //current_op.text=text+current_op.text; - //current_op.from_line=p_from_line; - //current_op.from_column=p_from_column; - //return; //update current op + return; // Update current op. } op.prev_version = get_version(); @@ -3597,6 +4005,15 @@ void TextEdit::_line_edited_from(int p_line) { for (int i = p_line; i < cache_size; i++) { color_region_cache.erase(i); } + + if (syntax_highlighting_cache.size() > 0) { + cache_size = syntax_highlighting_cache.back()->key(); + for (int i = p_line - 1; i <= cache_size; i++) { + if (syntax_highlighting_cache.has(i)) { + syntax_highlighting_cache.erase(i); + } + } + } } int TextEdit::get_char_count() { @@ -3606,11 +4023,11 @@ int TextEdit::get_char_count() { for (int i = 0; i < text.size(); i++) { if (i > 0) - totalsize++; // incliude \n + totalsize++; // Include \n. totalsize += text[i].length(); } - return totalsize; // omit last \n + return totalsize; // Omit last \n. } Size2 TextEdit::get_minimum_size() const { @@ -3618,19 +4035,26 @@ Size2 TextEdit::get_minimum_size() const { return cache.style_normal->get_minimum_size(); } +int TextEdit::_get_control_height() const { + int control_height = get_size().height; + control_height -= cache.style_normal->get_minimum_size().height; + if (h_scroll->is_visible_in_tree()) { + control_height -= h_scroll->get_size().height; + } + return control_height; +} + int TextEdit::get_visible_rows() const { + return _get_control_height() / get_row_height(); +} - int total = get_size().height; - total -= cache.style_normal->get_minimum_size().height; - if (h_scroll->is_visible_in_tree()) - total -= h_scroll->get_size().height; - total /= get_row_height(); - return total; +int TextEdit::_get_minimap_visible_rows() const { + return _get_control_height() / (minimap_char_size.y + minimap_line_spacing); } int TextEdit::get_total_visible_rows() const { - // returns the total amount of rows we need in the editor. + // Returns the total amount of rows we need in the editor. // This skips hidden lines and counts each wrapping of a line. if (!is_hiding_enabled() && !is_wrap_enabled()) return text.size(); @@ -3647,12 +4071,12 @@ int TextEdit::get_total_visible_rows() const { void TextEdit::_update_wrap_at() { - wrap_at = get_size().width - cache.style_normal->get_minimum_size().width - cache.line_number_w - cache.breakpoint_gutter_width - cache.fold_gutter_width - wrap_right_offset; + wrap_at = get_size().width - cache.style_normal->get_minimum_size().width - cache.line_number_w - cache.breakpoint_gutter_width - cache.fold_gutter_width - cache.info_gutter_width - cache.minimap_width - wrap_right_offset; update_cursor_wrap_offset(); text.clear_wrap_cache(); for (int i = 0; i < text.size(); i++) { - // update all values that wrap + // Update all values that wrap. if (!line_wraps(i)) continue; Vector<String> rows = get_wrap_rows_text(i); @@ -3662,8 +4086,9 @@ void TextEdit::_update_wrap_at() { void TextEdit::adjust_viewport_to_cursor() { - // make sure cursor is visible on the screen + // Make sure cursor is visible on the screen. scrolling = false; + minimap_clicked = false; int cur_line = cursor.line; int cur_wrap = get_cursor_wrap_index(); @@ -3674,20 +4099,20 @@ void TextEdit::adjust_viewport_to_cursor() { int last_vis_wrap = get_last_visible_line_wrap_index(); if (cur_line < first_vis_line || (cur_line == first_vis_line && cur_wrap < first_vis_wrap)) { - // cursor is above screen + // Cursor is above screen. set_line_as_first_visible(cur_line, cur_wrap); } else if (cur_line > last_vis_line || (cur_line == last_vis_line && cur_wrap > last_vis_wrap)) { - // cursor is below screen + // Cursor is below screen. set_line_as_last_visible(cur_line, cur_wrap); } - int visible_width = get_size().width - cache.style_normal->get_minimum_size().width - cache.line_number_w - cache.breakpoint_gutter_width - cache.fold_gutter_width; + int visible_width = get_size().width - cache.style_normal->get_minimum_size().width - cache.line_number_w - cache.breakpoint_gutter_width - cache.fold_gutter_width - cache.info_gutter_width - cache.minimap_width; if (v_scroll->is_visible_in_tree()) visible_width -= v_scroll->get_combined_minimum_size().width; - visible_width -= 20; // give it a little more space + visible_width -= 20; // Give it a little more space. if (!is_wrap_enabled()) { - // adjust x offset + // Adjust x offset. int cursor_x = get_column_x_offset(cursor.column, text[cursor.line]); if (cursor_x > (cursor.x_ofs + visible_width)) @@ -3705,20 +4130,21 @@ void TextEdit::adjust_viewport_to_cursor() { void TextEdit::center_viewport_to_cursor() { - // move viewport so the cursor is in the center of the screen + // Move viewport so the cursor is in the center of the screen. scrolling = false; + minimap_clicked = false; if (is_line_hidden(cursor.line)) unfold_line(cursor.line); set_line_as_center_visible(cursor.line, get_cursor_wrap_index()); - int visible_width = get_size().width - cache.style_normal->get_minimum_size().width - cache.line_number_w - cache.breakpoint_gutter_width - cache.fold_gutter_width; + int visible_width = get_size().width - cache.style_normal->get_minimum_size().width - cache.line_number_w - cache.breakpoint_gutter_width - cache.fold_gutter_width - cache.info_gutter_width - cache.minimap_width; if (v_scroll->is_visible_in_tree()) visible_width -= v_scroll->get_combined_minimum_size().width; - visible_width -= 20; // give it a little more space + visible_width -= 20; // Give it a little more space. if (is_wrap_enabled()) { - // center x offset + // Center x offset. int cursor_x = get_column_x_offset_for_line(cursor.column, cursor.line); if (cursor_x > (cursor.x_ofs + visible_width)) @@ -3760,7 +4186,7 @@ int TextEdit::times_line_wraps(int line) const { int wrap_amount = text.get_line_wrap_amount(line); if (wrap_amount == -1) { - // update the value + // Update the value. Vector<String> rows = get_wrap_rows_text(line); wrap_amount = rows.size() - 1; text.set_line_wrap_amount(line, wrap_amount); @@ -3789,6 +4215,9 @@ Vector<String> TextEdit::get_wrap_rows_text(int p_line) const { int cur_wrap_index = 0; int tab_offset_px = get_indent_level(p_line) * cache.font->get_char_size(' ').width; + if (tab_offset_px >= wrap_at) { + tab_offset_px = 0; + } while (col < line_text.length()) { CharType c = line_text[col]; @@ -3796,37 +4225,44 @@ Vector<String> TextEdit::get_wrap_rows_text(int p_line) const { int indent_ofs = (cur_wrap_index != 0 ? tab_offset_px : 0); - word_str += c; - word_px += w; - if (c == ' ') { - // end of a word; add this word to the substring + if (indent_ofs + word_px + w > wrap_at) { + // Not enough space to add this char; start next line. wrap_substring += word_str; - px += word_px; - word_str = ""; - word_px = 0; - } + lines.push_back(wrap_substring); + cur_wrap_index++; + wrap_substring = ""; + px = 0; - if ((indent_ofs + px + word_px) > wrap_at) { - // do not want to add this word - if (indent_ofs + word_px > wrap_at) { - // not enough space; add it anyway + word_str = ""; + word_str += c; + word_px = w; + } else { + word_str += c; + word_px += w; + if (c == ' ') { + // End of a word; add this word to the substring. wrap_substring += word_str; + px += word_px; word_str = ""; word_px = 0; } - lines.push_back(wrap_substring); - // reset for next wrap - cur_wrap_index++; - wrap_substring = ""; - px = 0; + + if (indent_ofs + px + word_px > wrap_at) { + // This word will be moved to the next line. + lines.push_back(wrap_substring); + // Reset for next wrap. + cur_wrap_index++; + wrap_substring = ""; + px = 0; + } } col++; } - // line ends before hit wrap_at; add this word to the substring + // Line ends before hit wrap_at; add this word to the substring. wrap_substring += word_str; lines.push_back(wrap_substring); - // update cache + // Update cache. text.set_line_wrap_amount(p_line, lines.size() - 1); return lines; @@ -3844,7 +4280,7 @@ int TextEdit::get_line_wrap_index_at_col(int p_line, int p_column) const { if (!line_wraps(p_line)) return 0; - // loop through wraps in the line text until we get to the column + // Loop through wraps in the line text until we get to the column. int wrap_index = 0; int col = 0; Vector<String> rows = get_wrap_rows_text(p_line); @@ -3909,7 +4345,7 @@ void TextEdit::cursor_set_line(int p_row, bool p_adjust_viewport, bool p_can_be_ cursor.line = p_row; int n_col = get_char_pos_for_line(cursor.last_fit_x, p_row, p_wrap_index); - if (is_wrap_enabled() && p_wrap_index < times_line_wraps(p_row)) { + if (n_col != 0 && is_wrap_enabled() && p_wrap_index < times_line_wraps(p_row)) { Vector<String> rows = get_wrap_rows_text(p_row); int row_end_col = 0; for (int i = 0; i < p_wrap_index + 1; i++) { @@ -3985,6 +4421,7 @@ bool TextEdit::is_right_click_moving_caret() const { void TextEdit::_v_scroll_input() { scrolling = false; + minimap_clicked = false; } void TextEdit::_scroll_moved(double p_to_val) { @@ -3996,11 +4433,11 @@ void TextEdit::_scroll_moved(double p_to_val) { cursor.x_ofs = h_scroll->get_value(); if (v_scroll->is_visible_in_tree()) { - // set line ofs and wrap ofs + // Set line ofs and wrap ofs. int v_scroll_i = floor(get_v_scroll()); int sc = 0; int n_line; - for (n_line = 0; n_line < text.size(); n_line++) { + for (n_line = 0; n_line < text.size() - 1; n_line++) { if (!is_line_hidden(n_line)) { sc++; sc += times_line_wraps(n_line); @@ -4031,6 +4468,9 @@ int TextEdit::get_char_pos_for_line(int p_px, int p_line, int p_wrap_index) cons int line_wrap_amount = times_line_wraps(p_line); int wrap_offset_px = get_indent_level(p_line) * cache.font->get_char_size(' ').width; + if (wrap_offset_px >= wrap_at) { + wrap_offset_px = 0; + } if (p_wrap_index > line_wrap_amount) p_wrap_index = line_wrap_amount; if (p_wrap_index > 0) @@ -4072,6 +4512,9 @@ int TextEdit::get_column_x_offset_for_line(int p_char, int p_line) const { int px = get_column_x_offset(n_char, rows[wrap_index]); int wrap_offset_px = get_indent_level(p_line) * cache.font->get_char_size(' ').width; + if (wrap_offset_px >= wrap_at) { + wrap_offset_px = 0; + } if (wrap_index != 0) px += wrap_offset_px; @@ -4135,7 +4578,7 @@ Control::CursorShape TextEdit::get_cursor_shape(const Point2 &p_pos) const { if (highlighted_word != String()) return CURSOR_POINTING_HAND; - int gutter = cache.style_normal->get_margin(MARGIN_LEFT) + cache.line_number_w + cache.breakpoint_gutter_width + cache.fold_gutter_width; + int gutter = cache.style_normal->get_margin(MARGIN_LEFT) + cache.line_number_w + cache.breakpoint_gutter_width + cache.fold_gutter_width + cache.info_gutter_width; if ((completion_active && completion_rect.has_point(p_pos))) { return CURSOR_ARROW; } @@ -4145,27 +4588,41 @@ Control::CursorShape TextEdit::get_cursor_shape(const Point2 &p_pos) const { _get_mouse_pos(p_pos, row, col); int left_margin = cache.style_normal->get_margin(MARGIN_LEFT); - // breakpoint icon - if (draw_breakpoint_gutter && p_pos.x > left_margin && p_pos.x <= left_margin + cache.breakpoint_gutter_width + 3) { + // Breakpoint icon. + if (draw_breakpoint_gutter && p_pos.x > left_margin - 6 && p_pos.x <= left_margin + cache.breakpoint_gutter_width - 3) { return CURSOR_POINTING_HAND; } - // fold icon - int gutter_left = left_margin + cache.breakpoint_gutter_width + cache.line_number_w; - if (draw_fold_gutter && p_pos.x > gutter_left - 6 && p_pos.x <= gutter_left + cache.fold_gutter_width - 3) { + // Info icons. + int gutter_left = left_margin + cache.breakpoint_gutter_width + cache.info_gutter_width; + if (draw_info_gutter && p_pos.x > left_margin + cache.breakpoint_gutter_width - 6 && p_pos.x <= gutter_left - 3) { + if (text.has_info_icon(row)) { + return CURSOR_POINTING_HAND; + } + return CURSOR_ARROW; + } + + // Fold icon. + if (draw_fold_gutter && p_pos.x > gutter_left + cache.line_number_w - 6 && p_pos.x <= gutter_left + cache.line_number_w + cache.fold_gutter_width - 3) { if (is_folded(row) || can_fold(row)) return CURSOR_POINTING_HAND; else return CURSOR_ARROW; } + return CURSOR_ARROW; } else { + int xmargin_end = get_size().width - cache.style_normal->get_margin(MARGIN_RIGHT); + if (p_pos.x > xmargin_end - minimap_width && p_pos.x <= xmargin_end) { + return CURSOR_ARROW; + } + int row, col; _get_mouse_pos(p_pos, row, col); - // eol fold icon + // EOL fold icon. if (is_folded(row)) { int line_width = text.get_line_width(row); - line_width += cache.style_normal->get_margin(MARGIN_LEFT) + cache.line_number_w + cache.breakpoint_gutter_width + cache.fold_gutter_width - cursor.x_ofs; + line_width += cache.style_normal->get_margin(MARGIN_LEFT) + cache.line_number_w + cache.breakpoint_gutter_width + cache.fold_gutter_width + cache.info_gutter_width - cursor.x_ofs; if (p_pos.x > line_width - 3 && p_pos.x <= line_width + cache.folded_eol_icon->get_width() + 3) { return CURSOR_POINTING_HAND; } @@ -4178,21 +4635,24 @@ Control::CursorShape TextEdit::get_cursor_shape(const Point2 &p_pos) const { void TextEdit::set_text(String p_text) { setting_text = true; - _clear(); - _insert_text_at_cursor(p_text); - clear_undo_history(); - cursor.column = 0; - cursor.line = 0; - cursor.x_ofs = 0; - cursor.line_ofs = 0; - cursor.wrap_ofs = 0; - cursor.last_fit_x = 0; - cursor_set_line(0); - cursor_set_column(0); + if (!undo_enabled) { + _clear(); + _insert_text_at_cursor(p_text); + } + + if (undo_enabled) { + cursor_set_line(0); + cursor_set_column(0); + + begin_complex_operation(); + _remove_text(0, 0, MAX(0, get_line_count() - 1), MAX(get_line(MAX(get_line_count() - 1, 0)).size() - 1, 0)); + _insert_text_at_cursor(p_text); + end_complex_operation(); + selection.active = false; + } + update(); setting_text = false; - - //get_range()->set(0); }; String TextEdit::get_text() { @@ -4219,7 +4679,7 @@ String TextEdit::get_text_for_lookup_completion() { if (i == row) { longthing += text[i].substr(0, col); - longthing += String::chr(0xFFFF); //not unicode, represents the cursor + longthing += String::chr(0xFFFF); // Not unicode, represents the cursor. longthing += text[i].substr(col, text[i].size()); } else { @@ -4241,7 +4701,7 @@ String TextEdit::get_text_for_completion() { if (i == cursor.line) { longthing += text[i].substr(0, cursor.column); - longthing += String::chr(0xFFFF); //not unicode, represents the cursor + longthing += String::chr(0xFFFF); // Not unicode, represents the cursor. longthing += text[i].substr(cursor.column, text[i].size()); } else { @@ -4285,7 +4745,37 @@ void TextEdit::clear() { void TextEdit::set_readonly(bool p_readonly) { + if (readonly == p_readonly) + return; + readonly = p_readonly; + + // Reorganize context menu. + menu->clear(); + + if (!readonly) { + menu->add_item(RTR("Undo"), MENU_UNDO, KEY_MASK_CMD | KEY_Z); + menu->add_item(RTR("Redo"), MENU_REDO, KEY_MASK_CMD | KEY_MASK_SHIFT | KEY_Z); + } + + if (!readonly) { + menu->add_separator(); + menu->add_item(RTR("Cut"), MENU_CUT, KEY_MASK_CMD | KEY_X); + } + + menu->add_item(RTR("Copy"), MENU_COPY, KEY_MASK_CMD | KEY_C); + + if (!readonly) { + menu->add_item(RTR("Paste"), MENU_PASTE, KEY_MASK_CMD | KEY_V); + } + + menu->add_separator(); + menu->add_item(RTR("Select All"), MENU_SELECT_ALL, KEY_MASK_CMD | KEY_A); + + if (!readonly) { + menu->add_item(RTR("Clear"), MENU_CLEAR); + } + update(); } @@ -4345,7 +4835,8 @@ void TextEdit::_update_caches() { cache.line_number_color = get_color("line_number_color"); cache.safe_line_number_color = get_color("safe_line_number_color"); cache.font_color = get_color("font_color"); - cache.font_selected_color = get_color("font_selected_color"); + cache.font_color_selected = get_color("font_color_selected"); + cache.font_color_readonly = get_color("font_color_readonly"); cache.keyword_color = get_color("keyword_color"); cache.function_color = get_color("function_color"); cache.member_variable_color = get_color("member_variable_color"); @@ -4354,7 +4845,9 @@ void TextEdit::_update_caches() { cache.mark_color = get_color("mark_color"); cache.current_line_color = get_color("current_line_color"); cache.line_length_guideline_color = get_color("line_length_guideline_color"); + cache.bookmark_color = get_color("bookmark_color"); cache.breakpoint_color = get_color("breakpoint_color"); + cache.executing_line_color = get_color("executing_line_color"); cache.code_folding_color = get_color("code_folding_color"); cache.brace_mismatch_color = get_color("brace_mismatch_color"); cache.word_highlighted_color = get_color("word_highlighted_color"); @@ -4369,9 +4862,11 @@ void TextEdit::_update_caches() { #endif cache.row_height = cache.font->get_height() + cache.line_spacing; cache.tab_icon = get_icon("tab"); - cache.folded_icon = get_icon("GuiTreeArrowRight", "EditorIcons"); - cache.can_fold_icon = get_icon("GuiTreeArrowDown", "EditorIcons"); + cache.space_icon = get_icon("space"); + cache.folded_icon = get_icon("folded"); + cache.can_fold_icon = get_icon("fold"); cache.folded_eol_icon = get_icon("GuiEllipsis", "EditorIcons"); + cache.executing_icon = get_icon("MainPlay", "EditorIcons"); text.set_font(cache.font); if (syntax_highlighter) { @@ -4389,17 +4884,18 @@ void TextEdit::_set_syntax_highlighting(SyntaxHighlighter *p_syntax_highlighter) syntax_highlighter->set_text_editor(this); syntax_highlighter->_update_cache(); } + syntax_highlighting_cache.clear(); update(); } int TextEdit::_is_line_in_region(int p_line) { - // do we have in cache? + // Do we have in cache? if (color_region_cache.has(p_line)) { return color_region_cache[p_line]; } - // if not find the closest line we have + // If not find the closest line we have. int previous_line = p_line - 1; for (; previous_line > -1; previous_line--) { if (color_region_cache.has(p_line)) { @@ -4407,7 +4903,7 @@ int TextEdit::_is_line_in_region(int p_line) { } } - // calculate up to line we need and update the cache along the way. + // Calculate up to line we need and update the cache along the way. int in_region = color_region_cache[previous_line]; if (previous_line == -1) { in_region = -1; @@ -4455,6 +4951,7 @@ void TextEdit::clear_colors() { keywords.clear(); color_regions.clear(); color_region_cache.clear(); + syntax_highlighting_cache.clear(); text.clear_width_cache(); } @@ -4469,6 +4966,8 @@ bool TextEdit::has_keyword_color(String p_keyword) const { } Color TextEdit::get_keyword_color(String p_keyword) const { + + ERR_FAIL_COND_V(!keywords.has(p_keyword), Color()); return keywords[p_keyword]; } @@ -4533,7 +5032,7 @@ void TextEdit::cut() { OS::get_singleton()->set_clipboard(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); // Set afterwards else it causes the view to be offset. cursor_set_column(selection.from_column); selection.active = false; @@ -4612,14 +5111,18 @@ void TextEdit::deselect() { void TextEdit::select(int p_from_line, int p_from_column, int p_to_line, int p_to_column) { - if (p_from_line >= text.size()) + if (p_from_line < 0) + p_from_line = 0; + else if (p_from_line >= text.size()) p_from_line = text.size() - 1; if (p_from_column >= text[p_from_line].length()) p_from_column = text[p_from_line].length(); if (p_from_column < 0) p_from_column = 0; - if (p_to_line >= text.size()) + if (p_to_line < 0) + p_to_line = 0; + else if (p_to_line >= text.size()) p_to_line = text.size() - 1; if (p_to_column >= text[p_to_line].length()) p_to_column = text[p_to_line].length(); @@ -4758,7 +5261,7 @@ int TextEdit::_get_column_pos_of_word(const String &p_key, const String &p_searc col = p_search.findn(p_key, p_from_column); } - // whole words only + // Whole words only. if (col != -1 && p_search_flags & SEARCH_WHOLE_WORDS) { p_from_column = col; @@ -4798,14 +5301,12 @@ bool TextEdit::search(const String &p_key, uint32_t p_search_flags, int p_from_l ERR_FAIL_INDEX_V(p_from_line, text.size(), false); ERR_FAIL_INDEX_V(p_from_column, text[p_from_line].length() + 1, false); - //search through the whole document, but start by current line + // Search through the whole document, but start by current line. int line = p_from_line; int pos = -1; for (int i = 0; i < text.size() + 1; i++) { - //backwards is broken... - //int idx=(p_search_flags&SEARCH_BACKWARDS)?(text.size()-i):i; //do backwards seearch if (line < 0) { line = text.size() - 1; @@ -4819,7 +5320,7 @@ bool TextEdit::search(const String &p_key, uint32_t p_search_flags, int p_from_l if (line == p_from_line) { if (i == text.size()) { - //wrapped + // Wrapped. if (p_search_flags & SEARCH_BACKWARDS) { from_column = text_line.length(); @@ -4867,7 +5368,7 @@ bool TextEdit::search(const String &p_key, uint32_t p_search_flags, int p_from_l bool is_match = true; if (pos != -1 && (p_search_flags & SEARCH_WHOLE_WORDS)) { - //validate for whole words + // Validate for whole words. if (pos > 0 && _is_text_char(text_line[pos - 1])) is_match = false; else if (pos + p_key.length() < text_line.length() && _is_text_char(text_line[pos + p_key.length()])) @@ -4937,6 +5438,48 @@ bool TextEdit::is_line_set_as_safe(int p_line) const { return text.is_safe(p_line); } +void TextEdit::set_executing_line(int p_line) { + ERR_FAIL_INDEX(p_line, text.size()); + executing_line = p_line; + update(); +} + +void TextEdit::clear_executing_line() { + executing_line = -1; + update(); +} + +bool TextEdit::is_line_set_as_bookmark(int p_line) const { + + ERR_FAIL_INDEX_V(p_line, text.size(), false); + return text.is_bookmark(p_line); +} + +void TextEdit::set_line_as_bookmark(int p_line, bool p_bookmark) { + + ERR_FAIL_INDEX(p_line, text.size()); + text.set_bookmark(p_line, p_bookmark); + update(); +} + +void TextEdit::get_bookmarks(List<int> *p_bookmarks) const { + + for (int i = 0; i < text.size(); i++) { + if (text.is_bookmark(i)) + p_bookmarks->push_back(i); + } +} + +Array TextEdit::get_bookmarks_array() const { + + Array arr; + for (int i = 0; i < text.size(); i++) { + if (text.is_bookmark(i)) + arr.append(i); + } + return arr; +} + bool TextEdit::is_line_set_as_breakpoint(int p_line) const { ERR_FAIL_INDEX_V(p_line, text.size(), false); @@ -4976,6 +5519,19 @@ void TextEdit::remove_breakpoints() { } } +void TextEdit::set_line_info_icon(int p_line, Ref<Texture> p_icon, String p_info) { + ERR_FAIL_INDEX(p_line, text.size()); + text.set_info_icon(p_line, p_icon, p_info); + update(); +} + +void TextEdit::clear_info_icons() { + for (int i = 0; i < text.size(); i++) { + text.set_info_icon(i, NULL, ""); + } + update(); +} + void TextEdit::set_line_as_hidden(int p_line, bool p_hidden) { ERR_FAIL_INDEX(p_line, text.size()); @@ -5010,7 +5566,7 @@ void TextEdit::unhide_all_lines() { int TextEdit::num_lines_from(int p_line_from, int visible_amount) const { - // returns the number of lines (hidden and unhidden) from p_line_from to (p_line_from + visible_amount of unhidden lines) + // Returns the number of lines (hidden and unhidden) from p_line_from to (p_line_from + visible_amount of unhidden lines). ERR_FAIL_INDEX_V(p_line_from, text.size(), ABS(visible_amount)); if (!is_hiding_enabled()) @@ -5043,8 +5599,8 @@ int TextEdit::num_lines_from(int p_line_from, int visible_amount) const { int TextEdit::num_lines_from_rows(int p_line_from, int p_wrap_index_from, int visible_amount, int &wrap_index) const { - // returns the number of lines (hidden and unhidden) from (p_line_from + p_wrap_index_from) row to (p_line_from + visible_amount of unhidden and wrapped rows) - // wrap index is set to the wrap index of the last line + // Returns the number of lines (hidden and unhidden) from (p_line_from + p_wrap_index_from) row to (p_line_from + visible_amount of unhidden and wrapped rows). + // Wrap index is set to the wrap index of the last line. wrap_index = 0; ERR_FAIL_INDEX_V(p_line_from, text.size(), ABS(visible_amount)); @@ -5090,7 +5646,7 @@ int TextEdit::num_lines_from_rows(int p_line_from, int p_wrap_index_from, int vi int TextEdit::get_last_unhidden_line() const { - // returns the last line in the text that is not hidden + // Returns the last line in the text that is not hidden. if (!is_hiding_enabled()) return text.size() - 1; @@ -5107,7 +5663,7 @@ int TextEdit::get_indent_level(int p_line) const { ERR_FAIL_INDEX_V(p_line, text.size(), 0); - // counts number of tabs and spaces before line starts + // Counts number of tabs and spaces before line starts. int tab_count = 0; int whitespace_count = 0; int line_length = text[p_line].size(); @@ -5125,7 +5681,7 @@ int TextEdit::get_indent_level(int p_line) const { bool TextEdit::is_line_comment(int p_line) const { - // checks to see if this line is the start of a comment + // Checks to see if this line is the start of a comment. ERR_FAIL_INDEX_V(p_line, text.size(), false); const Map<int, Text::ColorRegionInfo> &cri_map = text.get_color_region_info(p_line); @@ -5134,11 +5690,7 @@ bool TextEdit::is_line_comment(int p_line) const { for (int i = 0; i < line_length - 1; i++) { if (_is_symbol(text[p_line][i]) && cri_map.has(i)) { const Text::ColorRegionInfo &cri = cri_map[i]; - if (color_regions[cri.region].begin_key == "#" || color_regions[cri.region].begin_key == "//") { - return true; - } else { - return false; - } + return color_regions[cri.region].begin_key == "#" || color_regions[cri.region].begin_key == "//"; } else if (_is_whitespace(text[p_line][i])) { continue; } else { @@ -5187,9 +5739,18 @@ bool TextEdit::is_folded(int p_line) const { ERR_FAIL_INDEX_V(p_line, text.size(), false); if (p_line + 1 >= text.size()) return false; - if (!is_line_hidden(p_line) && is_line_hidden(p_line + 1)) - return true; - return false; + return !is_line_hidden(p_line) && is_line_hidden(p_line + 1); +} + +Vector<int> TextEdit::get_folded_lines() const { + Vector<int> folded_lines; + + for (int i = 0; i < text.size(); i++) { + if (is_folded(i)) { + folded_lines.push_back(i); + } + } + return folded_lines; } void TextEdit::fold_line(int p_line) { @@ -5200,7 +5761,7 @@ void TextEdit::fold_line(int p_line) { if (!can_fold(p_line)) return; - // hide lines below this one + // Hide lines below this one. int start_indent = get_indent_level(p_line); int last_line = start_indent; for (int i = p_line + 1; i < text.size(); i++) { @@ -5218,7 +5779,7 @@ void TextEdit::fold_line(int p_line) { set_line_as_hidden(i, true); } - // fix selection + // Fix selection. if (is_selection_active()) { if (is_line_hidden(selection.from_line) && is_line_hidden(selection.to_line)) { deselect(); @@ -5229,7 +5790,7 @@ void TextEdit::fold_line(int p_line) { } } - // reset cursor + // Reset cursor. if (is_line_hidden(cursor.line)) { cursor_set_line(p_line, false, false); cursor_set_column(get_line(p_line).length(), false); @@ -5290,8 +5851,8 @@ void TextEdit::_do_text_op(const TextOperation &p_op, bool p_reverse) { int check_line; int check_column; _base_insert_text(p_op.from_line, p_op.from_column, p_op.text, check_line, check_column); - ERR_FAIL_COND(check_line != p_op.to_line); // BUG - ERR_FAIL_COND(check_column != p_op.to_column); // BUG + ERR_FAIL_COND(check_line != p_op.to_line); // BUG. + ERR_FAIL_COND(check_column != p_op.to_column); // BUG. } else { _base_remove_text(p_op.from_line, p_op.from_column, p_op.to_line, p_op.to_column); @@ -5301,7 +5862,7 @@ void TextEdit::_do_text_op(const TextOperation &p_op, bool p_reverse) { void TextEdit::_clear_redo() { if (undo_stack_pos == NULL) - return; //nothing to clear + return; // Nothing to clear. _push_current_op(); @@ -5319,12 +5880,12 @@ void TextEdit::undo() { if (undo_stack_pos == NULL) { if (!undo_stack.size()) - return; //nothing to undo + return; // Nothing to undo. undo_stack_pos = undo_stack.back(); } else if (undo_stack_pos == undo_stack.front()) - return; // at the bottom of the undo stack + return; // At the bottom of the undo stack. else undo_stack_pos = undo_stack_pos->prev(); @@ -5332,6 +5893,9 @@ void TextEdit::undo() { TextOperation op = undo_stack_pos->get(); _do_text_op(op, true); + if (op.type != TextOperation::TYPE_INSERT && (op.from_line != op.to_line || op.to_column != op.from_column + 1)) + select(op.from_line, op.from_column, op.to_line, op.to_column); + current_op.version = op.prev_version; if (undo_stack_pos->get().chain_backward) { while (true) { @@ -5362,7 +5926,7 @@ void TextEdit::redo() { _push_current_op(); if (undo_stack_pos == NULL) - return; //nothing to do. + return; // Nothing to do. deselect(); @@ -5416,7 +5980,7 @@ void TextEdit::end_complex_operation() { void TextEdit::_push_current_op() { if (current_op.type == TextOperation::TYPE_NONE) - return; // do nothing + return; // Nothing to do. if (next_operation_is_complex) { current_op.chain_forward = true; @@ -5458,6 +6022,7 @@ int TextEdit::get_indent_size() { void TextEdit::set_draw_tabs(bool p_draw) { draw_tabs = p_draw; + update(); } bool TextEdit::is_drawing_tabs() const { @@ -5465,6 +6030,16 @@ bool TextEdit::is_drawing_tabs() const { return draw_tabs; } +void TextEdit::set_draw_spaces(bool p_draw) { + + draw_spaces = p_draw; +} + +bool TextEdit::is_drawing_spaces() const { + + return draw_spaces; +} + void TextEdit::set_override_selected_font_color(bool p_override_selected_font_color) { override_selected_font_color = p_override_selected_font_color; } @@ -5505,7 +6080,7 @@ double TextEdit::get_scroll_pos_for_line(int p_line, int p_wrap_index) const { if (!is_wrap_enabled() && !is_hiding_enabled()) return p_line; - // count the number of visible lines up to this line + // Count the number of visible lines up to this line. double new_line_scroll_pos = 0; int to = CLAMP(p_line, 0, text.size() - 1); for (int i = 0; i < to; i++) { @@ -5565,10 +6140,7 @@ int TextEdit::get_last_visible_line_wrap_index() const { double TextEdit::get_visible_rows_offset() const { - double total = get_size().height; - total -= cache.style_normal->get_minimum_size().height; - if (h_scroll->is_visible_in_tree()) - total -= h_scroll->get_size().height; + double total = _get_control_height(); total /= (double)get_row_height(); total = total - floor(total); total = -CLAMP(total, 0.001, 1) + 1; @@ -5642,21 +6214,31 @@ void TextEdit::_confirm_completion() { _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_at_cursor(completion_current.insert_text); - // When inserted into the middle of an existing string, don't add an unnecessary quote + // When inserted into the middle of an existing string/method, don't add an unnecessary quote/bracket. String line = text[cursor.line]; CharType next_char = line[cursor.column]; - CharType last_completion_char = completion_current[completion_current.length() - 1]; + CharType last_completion_char = completion_current.insert_text[completion_current.insert_text.length() - 1]; - if ((last_completion_char == '"' || last_completion_char == '\'') && - last_completion_char == next_char) { + if ((last_completion_char == '"' || last_completion_char == '\'') && last_completion_char == next_char) { _base_remove_text(cursor.line, cursor.column, cursor.line, cursor.column + 1); } - if (last_completion_char == '(' && auto_brace_completion_enabled) { - insert_text_at_cursor(")"); - cursor.column--; + 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(); @@ -5696,10 +6278,11 @@ void TextEdit::_update_completion_candidates() { String s; - //look for keywords first + // Look for keywords first. bool inquote = false; int first_quote = -1; + int restore_quotes = -1; int c = cofs - 1; while (c >= 0) { @@ -5707,6 +6290,11 @@ void TextEdit::_update_completion_candidates() { 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--; } @@ -5715,7 +6303,7 @@ void TextEdit::_update_completion_candidates() { bool cancel = false; if (!inquote && first_quote == cofs - 1) { - //no completion here + // No completion here. cancel = true; } else if (inquote && first_quote != -1) { @@ -5753,11 +6341,12 @@ void TextEdit::_update_completion_candidates() { bool prev_is_prefix = false; if (cofs > 0 && completion_prefixes.has(String::chr(l[cofs - 1]))) prev_is_prefix = true; - if (cofs > 1 && l[cofs - 1] == ' ' && completion_prefixes.has(String::chr(l[cofs - 2]))) //check with one space before prefix, to allow indent + // 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 + // None to complete, cancel. _cancel_completion(); return; } @@ -5767,51 +6356,58 @@ void TextEdit::_update_completion_candidates() { completion_base = s; Vector<float> sim_cache; bool single_quote = s.begins_with("'"); - Vector<String> completion_options_casei; + Vector<ScriptCodeCompletionOption> completion_options_casei; + + 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("'"); + } - for (int i = 0; i < completion_strings.size(); i++) { - if (single_quote && completion_strings[i].is_quoted()) { - completion_strings.write[i] = completion_strings[i].unquote().quote("'"); + if (inquote && restore_quotes == 1 && !option.display.is_quoted()) { + String quote = single_quote ? "'" : "\""; + option.display = option.display.quote(quote); } - if (completion_strings[i].begins_with(s)) { - completion_options.push_back(completion_strings[i]); - } else if (completion_strings[i].to_lower().begins_with(s.to_lower())) { - completion_options_casei.push_back(completion_strings[i]); + 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); } } completion_options.append_array(completion_options_casei); if (completion_options.size() == 0) { - for (int i = 0; i < completion_strings.size(); i++) { - if (s.is_subsequence_of(completion_strings[i])) { - completion_options.push_back(completion_strings[i]); + for (int i = 0; i < completion_sources.size(); i++) { + if (s.is_subsequence_of(completion_sources[i].display)) { + completion_options.push_back(completion_sources[i]); } } } if (completion_options.size() == 0) { - for (int i = 0; i < completion_strings.size(); i++) { - if (s.is_subsequence_ofi(completion_strings[i])) { - completion_options.push_back(completion_strings[i]); + for (int i = 0; i < completion_sources.size(); i++) { + if (s.is_subsequence_ofi(completion_sources[i].display)) { + completion_options.push_back(completion_sources[i]); } } } if (completion_options.size() == 0) { - //no options to complete, cancel + // No options to complete, cancel. _cancel_completion(); return; } - if (completion_options.size() == 1 && s == completion_options[0]) { - // A perfect match, stop completion + 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 + // The top of the list is the best match. completion_current = completion_options[0]; completion_enabled = true; } @@ -5830,10 +6426,30 @@ void TextEdit::query_code_comple() { c--; } - 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"); + bool ignored = completion_active && !completion_options.empty(); + if (ignored) { + ScriptCodeCompletionOption::Kind kind = ScriptCodeCompletionOption::KIND_PLAIN_TEXT; + const ScriptCodeCompletionOption *previous_option = NULL; + 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) { @@ -5843,15 +6459,14 @@ void TextEdit::set_code_hint(const String &p_hint) { update(); } -void TextEdit::code_complete(const Vector<String> &p_strings, bool p_forced) { +void TextEdit::code_complete(const List<ScriptCodeCompletionOption> &p_strings, bool p_forced) { - completion_strings = p_strings; + completion_sources = p_strings; completion_active = true; completion_forced = p_forced; - completion_current = ""; + completion_current = ScriptCodeCompletionOption(); completion_index = 0; _update_completion_candidates(); - // } String TextEdit::get_word_at_pos(const Vector2 &p_pos) const { @@ -5935,9 +6550,21 @@ void TextEdit::set_line(int line, String new_text) { } void TextEdit::insert_at(const String &p_text, int at) { - cursor_set_column(0); - cursor_set_line(at, false, true); _insert_text(at, 0, p_text + "\n"); + if (cursor.line >= at) { + // offset cursor when located after inserted line + ++cursor.line; + } + if (is_selection_active()) { + if (selection.from_line >= at) { + // offset selection when located after inserted line + ++selection.from_line; + ++selection.to_line; + } else if (selection.to_line >= at) { + // extend selection that includes inserted line + ++selection.to_line; + } + } } void TextEdit::set_show_line_numbers(bool p_show) { @@ -5966,6 +6593,15 @@ void TextEdit::set_line_length_guideline_column(int p_column) { update(); } +void TextEdit::set_bookmark_gutter_enabled(bool p_draw) { + draw_bookmark_gutter = p_draw; + update(); +} + +bool TextEdit::is_bookmark_gutter_enabled() const { + return draw_bookmark_gutter; +} + void TextEdit::set_breakpoint_gutter_enabled(bool p_draw) { draw_breakpoint_gutter = p_draw; update(); @@ -6002,14 +6638,50 @@ int TextEdit::get_fold_gutter_width() const { return cache.fold_gutter_width; } -void TextEdit::set_hiding_enabled(int p_enabled) { +void TextEdit::set_draw_info_gutter(bool p_draw) { + draw_info_gutter = p_draw; + update(); +} + +bool TextEdit::is_drawing_info_gutter() const { + return draw_info_gutter; +} + +void TextEdit::set_info_gutter_width(int p_gutter_width) { + info_gutter_width = p_gutter_width; + update(); +} + +int TextEdit::get_info_gutter_width() const { + return info_gutter_width; +} + +void TextEdit::set_draw_minimap(bool p_draw) { + draw_minimap = p_draw; + update(); +} + +bool TextEdit::is_drawing_minimap() const { + return draw_minimap; +} + +void TextEdit::set_minimap_width(int p_minimap_width) { + minimap_width = p_minimap_width; + update(); +} + +int TextEdit::get_minimap_width() const { + return minimap_width; +} + +void TextEdit::set_hiding_enabled(bool p_enabled) { if (!p_enabled) unhide_all_lines(); hiding_enabled = p_enabled; update(); } -int TextEdit::is_hiding_enabled() const { +bool TextEdit::is_hiding_enabled() const { return hiding_enabled; } @@ -6110,6 +6782,7 @@ void TextEdit::_bind_methods() { 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("center_viewport_to_cursor"), &TextEdit::center_viewport_to_cursor); 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)); @@ -6130,8 +6803,6 @@ void TextEdit::_bind_methods() { ClassDB::bind_method(D_METHOD("set_wrap_enabled", "enable"), &TextEdit::set_wrap_enabled); ClassDB::bind_method(D_METHOD("is_wrap_enabled"), &TextEdit::is_wrap_enabled); - // ClassDB::bind_method(D_METHOD("set_max_chars", "amount"), &TextEdit::set_max_chars); - // ClassDB::bind_method(D_METHOD("get_max_char"), &TextEdit::get_max_chars); ClassDB::bind_method(D_METHOD("set_context_menu_enabled", "enable"), &TextEdit::set_context_menu_enabled); ClassDB::bind_method(D_METHOD("is_context_menu_enabled"), &TextEdit::is_context_menu_enabled); @@ -6158,8 +6829,14 @@ void TextEdit::_bind_methods() { ClassDB::bind_method(D_METHOD("set_show_line_numbers", "enable"), &TextEdit::set_show_line_numbers); ClassDB::bind_method(D_METHOD("is_show_line_numbers_enabled"), &TextEdit::is_show_line_numbers_enabled); + ClassDB::bind_method(D_METHOD("set_draw_tabs"), &TextEdit::set_draw_tabs); + ClassDB::bind_method(D_METHOD("is_drawing_tabs"), &TextEdit::is_drawing_tabs); + ClassDB::bind_method(D_METHOD("set_draw_spaces"), &TextEdit::set_draw_spaces); + ClassDB::bind_method(D_METHOD("is_drawing_spaces"), &TextEdit::is_drawing_spaces); ClassDB::bind_method(D_METHOD("set_breakpoint_gutter_enabled", "enable"), &TextEdit::set_breakpoint_gutter_enabled); ClassDB::bind_method(D_METHOD("is_breakpoint_gutter_enabled"), &TextEdit::is_breakpoint_gutter_enabled); + ClassDB::bind_method(D_METHOD("set_draw_fold_gutter"), &TextEdit::set_draw_fold_gutter); + ClassDB::bind_method(D_METHOD("is_drawing_fold_gutter"), &TextEdit::is_drawing_fold_gutter); ClassDB::bind_method(D_METHOD("set_hiding_enabled", "enable"), &TextEdit::set_hiding_enabled); ClassDB::bind_method(D_METHOD("is_hiding_enabled"), &TextEdit::is_hiding_enabled); @@ -6201,12 +6878,20 @@ void TextEdit::_bind_methods() { ClassDB::bind_method(D_METHOD("get_breakpoints"), &TextEdit::get_breakpoints_array); ClassDB::bind_method(D_METHOD("remove_breakpoints"), &TextEdit::remove_breakpoints); + ClassDB::bind_method(D_METHOD("draw_minimap", "draw"), &TextEdit::set_draw_minimap); + ClassDB::bind_method(D_METHOD("is_drawing_minimap"), &TextEdit::is_drawing_minimap); + ClassDB::bind_method(D_METHOD("set_minimap_width", "width"), &TextEdit::set_minimap_width); + 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::BOOL, "readonly"), "set_readonly", "is_readonly"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "highlight_current_line"), "set_highlight_current_line", "is_highlight_current_line_enabled"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "syntax_highlighting"), "set_syntax_coloring", "is_syntax_coloring_enabled"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "show_line_numbers"), "set_show_line_numbers", "is_show_line_numbers_enabled"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "draw_tabs"), "set_draw_tabs", "is_drawing_tabs"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "draw_spaces"), "set_draw_spaces", "is_drawing_spaces"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "breakpoint_gutter"), "set_breakpoint_gutter_enabled", "is_breakpoint_gutter_enabled"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "fold_gutter"), "set_draw_fold_gutter", "is_drawing_fold_gutter"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "highlight_all_occurrences"), "set_highlight_all_occurrences", "is_highlight_all_occurrences_enabled"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "override_selected_font_color"), "set_override_selected_font_color", "is_overriding_selected_font_color"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "context_menu_enabled"), "set_context_menu_enabled", "is_context_menu_enabled"); @@ -6214,7 +6899,10 @@ void TextEdit::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::REAL, "v_scroll_speed"), "set_v_scroll_speed", "get_v_scroll_speed"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "hiding_enabled"), "set_hiding_enabled", "is_hiding_enabled"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "wrap_enabled"), "set_wrap_enabled", "is_wrap_enabled"); - // ADD_PROPERTY(PropertyInfo(Variant::BOOL, "max_chars"), "set_max_chars", "get_max_chars"); + + ADD_GROUP("Minimap", "minimap_"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "minimap_draw"), "draw_minimap", "is_drawing_minimap"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "minimap_width"), "set_minimap_width", "get_minimap_width"); ADD_GROUP("Caret", "caret_"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "caret_block_mode"), "cursor_set_block_mode", "cursor_is_block_mode"); @@ -6227,6 +6915,7 @@ void TextEdit::_bind_methods() { ADD_SIGNAL(MethodInfo("request_completion")); ADD_SIGNAL(MethodInfo("breakpoint_toggled", PropertyInfo(Variant::INT, "row"))); ADD_SIGNAL(MethodInfo("symbol_lookup", PropertyInfo(Variant::STRING, "symbol"), PropertyInfo(Variant::INT, "row"), PropertyInfo(Variant::INT, "column"))); + ADD_SIGNAL(MethodInfo("info_clicked", PropertyInfo(Variant::INT, "row"), PropertyInfo(Variant::STRING, "info"))); BIND_ENUM_CONSTANT(MENU_CUT); BIND_ENUM_CONSTANT(MENU_COPY); @@ -6238,14 +6927,14 @@ void TextEdit::_bind_methods() { BIND_ENUM_CONSTANT(MENU_MAX); GLOBAL_DEF("gui/timers/text_edit_idle_detect_sec", 3); - ProjectSettings::get_singleton()->set_custom_property_info("gui/timers/text_edit_idle_detect_sec", PropertyInfo(Variant::REAL, "gui/timers/text_edit_idle_detect_sec", PROPERTY_HINT_RANGE, "0,10,0.01,or_greater")); // No negative numbers + ProjectSettings::get_singleton()->set_custom_property_info("gui/timers/text_edit_idle_detect_sec", PropertyInfo(Variant::REAL, "gui/timers/text_edit_idle_detect_sec", PROPERTY_HINT_RANGE, "0,10,0.01,or_greater")); // No negative numbers. } TextEdit::TextEdit() { - readonly = false; setting_row = false; draw_tabs = false; + draw_spaces = false; override_selected_font_color = false; draw_caret = true; max_chars = 0; @@ -6262,13 +6951,13 @@ TextEdit::TextEdit() { breakpoint_gutter_width = 0; cache.fold_gutter_width = 0; fold_gutter_width = 0; + info_gutter_width = 0; + cache.info_gutter_width = 0; set_default_cursor_shape(CURSOR_IBEAM); indent_size = 4; text.set_indent_size(indent_size); text.clear(); - //text.insert(1,"Mongolia..."); - //text.insert(2,"PAIS GENEROSO!!"); text.set_color_regions(&color_regions); h_scroll = memnew(HScrollBar); @@ -6332,8 +7021,10 @@ TextEdit::TextEdit() { line_numbers_zero_padded = false; line_length_guideline = false; line_length_guideline_col = 80; + draw_bookmark_gutter = false; draw_breakpoint_gutter = false; draw_fold_gutter = false; + draw_info_gutter = false; hiding_enabled = false; next_operation_is_complex = false; scroll_past_end_of_file_enabled = false; @@ -6349,22 +7040,28 @@ TextEdit::TextEdit() { select_identifiers_enabled = false; smooth_scroll_enabled = false; scrolling = false; + minimap_clicked = false; + dragging_minimap = false; + can_drag_minimap = false; + minimap_scroll_ratio = 0; + minimap_scroll_click_pos = 0; + dragging_selection = false; target_v_scroll = 0; v_scroll_speed = 80; + draw_minimap = false; + minimap_width = 80; + minimap_char_size = Point2(1, 2); + minimap_line_spacing = 1; context_menu_enabled = true; menu = memnew(PopupMenu); add_child(menu); - menu->add_item(RTR("Cut"), MENU_CUT, KEY_MASK_CMD | KEY_X); - menu->add_item(RTR("Copy"), MENU_COPY, KEY_MASK_CMD | KEY_C); - menu->add_item(RTR("Paste"), MENU_PASTE, KEY_MASK_CMD | KEY_V); - menu->add_separator(); - menu->add_item(RTR("Select All"), MENU_SELECT_ALL, KEY_MASK_CMD | KEY_A); - menu->add_item(RTR("Clear"), MENU_CLEAR); - menu->add_separator(); - menu->add_item(RTR("Undo"), MENU_UNDO, KEY_MASK_CMD | KEY_Z); - menu->add_item(RTR("Redo"), MENU_REDO, KEY_MASK_CMD | KEY_MASK_SHIFT | KEY_Z); + readonly = true; // Initialise to opposite first, so we get past the early-out in set_readonly. + set_readonly(false); menu->connect("id_pressed", this, "menu_option"); + first_draw = true; + + executing_line = -1; } TextEdit::~TextEdit() { @@ -6373,8 +7070,14 @@ TextEdit::~TextEdit() { /////////////////////////////////////////////////////////////////////////////// Map<int, TextEdit::HighlighterInfo> TextEdit::_get_line_syntax_highlighting(int p_line) { + if (syntax_highlighting_cache.has(p_line)) { + return syntax_highlighting_cache[p_line]; + } + if (syntax_highlighter != NULL) { - return syntax_highlighter->_get_line_syntax_highlighting(p_line); + Map<int, HighlighterInfo> color_map = syntax_highlighter->_get_line_syntax_highlighting(p_line); + syntax_highlighting_cache[p_line] = color_map; + return color_map; } Map<int, HighlighterInfo> color_map; @@ -6420,14 +7123,14 @@ Map<int, TextEdit::HighlighterInfo> TextEdit::_get_line_syntax_highlighting(int bool is_symbol = _is_symbol(str[j]); bool is_number = _is_number(str[j]); - // allow ABCDEF in hex notation + // Allow ABCDEF in hex notation. if (is_hex_notation && (_is_hex_symbol(str[j]) || is_number)) { is_number = true; } else { is_hex_notation = false; } - // check for dot or underscore or 'x' for hex notation in floating point number or 'e' for scientific notation + // Check for dot or underscore or 'x' for hex notation in floating point number or 'e' for scientific notation. if ((str[j] == '.' || str[j] == 'x' || str[j] == '_' || str[j] == 'f' || str[j] == 'e') && !in_word && prev_is_number && !is_number) { is_number = true; is_symbol = false; @@ -6457,7 +7160,7 @@ Map<int, TextEdit::HighlighterInfo> TextEdit::_get_line_syntax_highlighting(int if (!cri.end) { in_region = cri.region; } - } else if (in_region == cri.region && !color_regions[cri.region].line_only) { //ignore otherwise + } else if (in_region == cri.region && !color_regions[cri.region].line_only) { // Ignore otherwise. if (cri.end || color_regions[cri.region].eq) { deregion = color_regions[cri.region].eq ? color_regions[cri.region].begin_key.length() : color_regions[cri.region].end_key.length(); } @@ -6485,7 +7188,7 @@ Map<int, TextEdit::HighlighterInfo> TextEdit::_get_line_syntax_highlighting(int if (col) { for (int k = j - 1; k >= 0; k--) { if (str[k] == '.') { - col = NULL; //member indexing not allowed + col = NULL; // Member indexing not allowed. break; } else if (str[k] > 32) { break; @@ -6507,7 +7210,7 @@ Map<int, TextEdit::HighlighterInfo> TextEdit::_get_line_syntax_highlighting(int k++; } - // check for space between name and bracket + // Check for space between name and bracket. while (k < str.length() && (str[k] == '\t' || str[k] == ' ')) { k++; } @@ -6556,6 +7259,7 @@ Map<int, TextEdit::HighlighterInfo> TextEdit::_get_line_syntax_highlighting(int } } + syntax_highlighting_cache[p_line] = color_map; return color_map; } diff --git a/scene/gui/text_edit.h b/scene/gui/text_edit.h index 95f1fbbee5..9c568acd93 100644 --- a/scene/gui/text_edit.h +++ b/scene/gui/text_edit.h @@ -40,7 +40,7 @@ class SyntaxHighlighter; class TextEdit : public Control { - GDCLASS(TextEdit, Control) + GDCLASS(TextEdit, Control); public: struct HighlighterInfo { @@ -75,10 +75,13 @@ public: int width_cache : 24; bool marked : 1; bool breakpoint : 1; + bool bookmark : 1; bool hidden : 1; bool safe : 1; int wrap_amount_cache : 24; Map<int, ColorRegionInfo> region_info; + Ref<Texture> info_icon; + String info; String data; }; @@ -103,12 +106,21 @@ public: void set(int p_line, const String &p_text); void set_marked(int p_line, bool p_marked) { text.write[p_line].marked = p_marked; } bool is_marked(int p_line) const { return text[p_line].marked; } + void set_bookmark(int p_line, bool p_bookmark) { text.write[p_line].bookmark = p_bookmark; } + bool is_bookmark(int p_line) const { return text[p_line].bookmark; } void set_breakpoint(int p_line, bool p_breakpoint) { text.write[p_line].breakpoint = p_breakpoint; } bool is_breakpoint(int p_line) const { return text[p_line].breakpoint; } void set_hidden(int p_line, bool p_hidden) { text.write[p_line].hidden = p_hidden; } bool is_hidden(int p_line) const { return text[p_line].hidden; } void set_safe(int p_line, bool p_safe) { text.write[p_line].safe = p_safe; } bool is_safe(int p_line) const { return text[p_line].safe; } + void set_info_icon(int p_line, Ref<Texture> p_icon, String p_info) { + text.write[p_line].info_icon = p_icon; + text.write[p_line].info = p_info; + } + bool has_info_icon(int p_line) const { return text[p_line].info_icon.is_valid(); } + const Ref<Texture> &get_info_icon(int p_line) const { return text[p_line].info_icon; } + const String &get_info(int p_line) const { return text[p_line].info; } void insert(int p_at, const String &p_text); void remove(int p_at); int size() const { return text.size(); } @@ -154,9 +166,11 @@ private: struct Cache { Ref<Texture> tab_icon; + Ref<Texture> space_icon; Ref<Texture> can_fold_icon; Ref<Texture> folded_icon; Ref<Texture> folded_eol_icon; + Ref<Texture> executing_icon; Ref<StyleBox> style_normal; Ref<StyleBox> style_focus; Ref<StyleBox> style_readonly; @@ -170,14 +184,17 @@ private: Color line_number_color; Color safe_line_number_color; Color font_color; - Color font_selected_color; + Color font_color_selected; + Color font_color_readonly; Color keyword_color; Color number_color; Color function_color; Color member_variable_color; Color selection_color; Color mark_color; + Color bookmark_color; Color breakpoint_color; + Color executing_line_color; Color code_folding_color; Color current_line_color; Color line_length_guideline_color; @@ -193,9 +210,12 @@ private: int line_number_w; int breakpoint_gutter_width; int fold_gutter_width; + int info_gutter_width; + int minimap_width; } cache; Map<int, int> color_region_cache; + Map<int, Map<int, HighlighterInfo> > syntax_highlighting_cache; struct TextOperation { @@ -237,11 +257,11 @@ private: Set<String> completion_prefixes; bool completion_enabled; - Vector<String> completion_strings; - Vector<String> completion_options; + List<ScriptCodeCompletionOption> completion_sources; + Vector<ScriptCodeCompletionOption> completion_options; bool completion_active; bool completion_forced; - String completion_current; + ScriptCodeCompletionOption completion_current; String completion_base; int completion_index; Rect2i completion_rect; @@ -275,8 +295,10 @@ private: int wrap_at; int wrap_right_offset; + bool first_draw; bool setting_row; bool draw_tabs; + bool draw_spaces; bool override_selected_font_color; bool cursor_changed_dirty; bool text_changed_dirty; @@ -285,11 +307,18 @@ private: bool line_numbers_zero_padded; bool line_length_guideline; int line_length_guideline_col; + bool draw_bookmark_gutter; bool draw_breakpoint_gutter; int breakpoint_gutter_width; bool draw_fold_gutter; int fold_gutter_width; bool hiding_enabled; + bool draw_info_gutter; + int info_gutter_width; + bool draw_minimap; + int minimap_width; + Point2 minimap_char_size; + int minimap_line_spacing; bool highlight_all_occurrences; bool scroll_past_end_of_file_enabled; @@ -303,6 +332,12 @@ private: bool smooth_scroll_enabled; bool scrolling; + bool dragging_selection; + bool dragging_minimap; + bool can_drag_minimap; + bool minimap_clicked; + double minimap_scroll_ratio; + double minimap_scroll_click_pos; float target_v_scroll; float v_scroll_speed; @@ -332,9 +367,13 @@ private: bool context_menu_enabled; + int executing_line; + int get_visible_rows() const; int get_total_visible_rows() const; + int _get_minimap_visible_rows() const; + void update_cursor_wrap_offset(); void _update_wrap_at(); bool line_wraps(int line) const; @@ -370,7 +409,8 @@ private: void _update_selection_mode_word(); void _update_selection_mode_line(); - void _uncomment_line(int p_line); + void _update_minimap_click(); + void _update_minimap_drag(); void _scroll_up(real_t p_delta); void _scroll_down(real_t p_delta); @@ -382,6 +422,7 @@ private: //void mouse_motion(const Point& p_pos, const Point& p_rel, int p_button_mask); Size2 get_minimum_size() const; + int _get_control_height() const; int get_row_height() const; @@ -413,6 +454,9 @@ private: 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); + protected: virtual String get_tooltip(const Point2 &p_pos) const; @@ -457,6 +501,7 @@ public: virtual CursorShape get_cursor_shape(const Point2 &p_pos = Point2i()) const; void _get_mouse_pos(const Point2i &p_mouse, int &r_row, int &r_col) const; + void _get_minimap_mouse_row(const Point2i &p_mouse, int &r_row) const; //void delete_char(); //void delete_line(); @@ -471,14 +516,23 @@ public: void insert_at(const String &p_text, int at); int get_line_count() const; void set_line_as_marked(int p_line, bool p_marked); + void set_line_as_bookmark(int p_line, bool p_bookmark); + bool is_line_set_as_bookmark(int p_line) const; + void get_bookmarks(List<int> *p_bookmarks) const; + Array get_bookmarks_array() const; void set_line_as_breakpoint(int p_line, bool p_breakpoint); bool is_line_set_as_breakpoint(int p_line) const; + void set_executing_line(int p_line); + void clear_executing_line(); void set_line_as_safe(int p_line, bool p_safe); bool is_line_set_as_safe(int p_line) const; void get_breakpoints(List<int> *p_breakpoints) const; Array get_breakpoints_array() const; void remove_breakpoints(); + void set_line_info_icon(int p_line, Ref<Texture> p_icon, String p_info = ""); + void clear_info_icons(); + void set_line_as_hidden(int p_line, bool p_hidden); bool is_line_hidden(int p_line) const; void fold_all_lines(); @@ -489,6 +543,7 @@ public: bool can_fold(int p_line) const; bool is_folded(int p_line) const; + Vector<int> get_folded_lines() const; void fold_line(int p_line); void unfold_line(int p_line); void toggle_fold_line(int p_line); @@ -590,6 +645,8 @@ public: int get_indent_size(); void set_draw_tabs(bool p_draw); bool is_drawing_tabs() const; + void set_draw_spaces(bool p_draw); + bool is_drawing_spaces() const; void set_override_selected_font_color(bool p_override_selected_font_color); bool is_overriding_selected_font_color() const; @@ -637,6 +694,9 @@ public: void set_show_line_length_guideline(bool p_show); void set_line_length_guideline_column(int p_column); + void set_bookmark_gutter_enabled(bool p_draw); + bool is_bookmark_gutter_enabled() const; + void set_breakpoint_gutter_enabled(bool p_draw); bool is_breakpoint_gutter_enabled() const; @@ -649,13 +709,25 @@ public: void set_fold_gutter_width(int p_gutter_width); int get_fold_gutter_width() const; - void set_hiding_enabled(int p_enabled); - int is_hiding_enabled() const; + void set_draw_info_gutter(bool p_draw); + bool is_drawing_info_gutter() const; + + void set_info_gutter_width(int p_gutter_width); + int get_info_gutter_width() const; + + void set_draw_minimap(bool p_draw); + bool is_drawing_minimap() const; + + void set_minimap_width(int p_minimap_width); + int get_minimap_width() const; + + void set_hiding_enabled(bool p_enabled); + bool is_hiding_enabled() const; 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 Vector<String> &p_strings, bool p_forced = false); + void code_complete(const List<ScriptCodeCompletionOption> &p_strings, bool p_forced = false); void set_code_hint(const String &p_hint); void query_code_comple(); @@ -683,10 +755,11 @@ protected: TextEdit *text_editor; public: + virtual ~SyntaxHighlighter() {} virtual void _update_cache() = 0; virtual Map<int, TextEdit::HighlighterInfo> _get_line_syntax_highlighting(int p_line) = 0; - virtual String get_name() = 0; + virtual String get_name() const = 0; virtual List<String> get_supported_languages() = 0; void set_text_editor(TextEdit *p_text_editor); diff --git a/scene/gui/texture_button.cpp b/scene/gui/texture_button.cpp index 95aab95253..b5f949aeb7 100644 --- a/scene/gui/texture_button.cpp +++ b/scene/gui/texture_button.cpp @@ -64,7 +64,9 @@ bool TextureButton::has_point(const Point2 &p_point) const { Rect2 rect = Rect2(); Size2 mask_size = click_mask->get_size(); - if (_tile) { + if (_position_rect.has_no_area()) { + rect.size = mask_size; + } else if (_tile) { // if the stretch mode is tile we offset the point to keep it inside the mask size rect.size = mask_size; if (_position_rect.has_point(point)) { @@ -206,8 +208,8 @@ void TextureButton::_notification(int p_what) { Size2 scaleSize(size.width / tex_size.width, size.height / tex_size.height); float scale = scaleSize.width > scaleSize.height ? scaleSize.width : scaleSize.height; Size2 scaledTexSize = tex_size * scale; - Point2 ofs = ((scaledTexSize - size) / scale).abs() / 2.0f; - _texture_region = Rect2(ofs, size / scale); + Point2 ofs2 = ((scaledTexSize - size) / scale).abs() / 2.0f; + _texture_region = Rect2(ofs2, size / scale); } break; } } @@ -216,11 +218,12 @@ void TextureButton::_notification(int p_what) { draw_texture_rect(texdraw, _position_rect, _tile); else draw_texture_rect_region(texdraw, _position_rect, _texture_region); + } else { + _position_rect = Rect2(); } if (has_focus() && focused.is_valid()) { - Rect2 drect(Point2(), get_size()); - draw_texture_rect(focused, drect, false); + draw_texture_rect(focused, _position_rect, false); }; } break; } diff --git a/scene/gui/texture_button.h b/scene/gui/texture_button.h index 4dc0de5358..d9224de686 100644 --- a/scene/gui/texture_button.h +++ b/scene/gui/texture_button.h @@ -32,7 +32,7 @@ #define TEXTURE_BUTTON_H #include "scene/gui/base_button.h" -#include "scene/resources/bit_mask.h" +#include "scene/resources/bit_map.h" class TextureButton : public BaseButton { GDCLASS(TextureButton, BaseButton); diff --git a/scene/gui/texture_progress.cpp b/scene/gui/texture_progress.cpp index 778d86d546..c534df5cbe 100644 --- a/scene/gui/texture_progress.cpp +++ b/scene/gui/texture_progress.cpp @@ -160,23 +160,27 @@ Point2 TextureProgress::unit_val_to_uv(float val) { if (edge == 0) { if (dir.x > 0) continue; - cp = -dir.x; cq = -(edgeLeft - p.x); + dir.x *= 2.0 * cq; + cp = -dir.x; } else if (edge == 1) { if (dir.x < 0) continue; - cp = dir.x; cq = (edgeRight - p.x); + dir.x *= 2.0 * cq; + cp = dir.x; } else if (edge == 2) { if (dir.y > 0) continue; - cp = -dir.y; cq = -(edgeBottom - p.y); + dir.y *= 2.0 * cq; + cp = -dir.y; } else if (edge == 3) { if (dir.y < 0) continue; - cp = dir.y; cq = (edgeTop - p.y); + dir.y *= 2.0 * cq; + cp = dir.y; } cr = cq / cp; if (cr >= 0 && cr < t1) @@ -247,12 +251,14 @@ void TextureProgress::draw_nine_patch_stretched(const Ref<Texture> &p_texture, F middle_section_size *= MIN(1.0, (MAX(0.0, width_filled - first_section_size) / MAX(1.0, width_total - first_section_size - last_section_size))); last_section_size = MAX(0.0, last_section_size - (width_total - width_filled)); + first_section_size = MIN(first_section_size, width_filled); width_texture = MIN(width_texture, first_section_size + middle_section_size + last_section_size); switch (mode) { case FILL_LEFT_TO_RIGHT: { src_rect.size.x = width_texture; dst_rect.size.x = width_filled; + topleft.x = first_section_size; bottomright.x = last_section_size; } break; case FILL_RIGHT_TO_LEFT: { @@ -261,11 +267,13 @@ void TextureProgress::draw_nine_patch_stretched(const Ref<Texture> &p_texture, F dst_rect.position.x += width_total - width_filled; dst_rect.size.x = width_filled; topleft.x = last_section_size; + bottomright.x = first_section_size; } break; case FILL_TOP_TO_BOTTOM: { src_rect.size.y = width_texture; dst_rect.size.y = width_filled; bottomright.y = last_section_size; + topleft.y = first_section_size; } break; case FILL_BOTTOM_TO_TOP: { src_rect.position.y += src_rect.size.y - width_texture; @@ -273,6 +281,7 @@ void TextureProgress::draw_nine_patch_stretched(const Ref<Texture> &p_texture, F dst_rect.position.y += width_total - width_filled; dst_rect.size.y = width_filled; topleft.y = last_section_size; + bottomright.y = first_section_size; } break; case FILL_BILINEAR_LEFT_AND_RIGHT: { // TODO: Implement diff --git a/scene/gui/texture_rect.cpp b/scene/gui/texture_rect.cpp index caae48336b..423794d8ba 100644 --- a/scene/gui/texture_rect.cpp +++ b/scene/gui/texture_rect.cpp @@ -38,30 +38,32 @@ void TextureRect::_notification(int p_what) { if (texture.is_null()) return; + Size2 size; + Point2 offset; + Rect2 region; + bool tile = false; + switch (stretch_mode) { case STRETCH_SCALE_ON_EXPAND: { - Size2 s = expand ? get_size() : texture->get_size(); - draw_texture_rect(texture, Rect2(Point2(), s), false); + size = expand ? get_size() : texture->get_size(); } break; case STRETCH_SCALE: { - draw_texture_rect(texture, Rect2(Point2(), get_size()), false); + size = get_size(); } break; case STRETCH_TILE: { - draw_texture_rect(texture, Rect2(Point2(), get_size()), true); + size = get_size(); + tile = true; } break; case STRETCH_KEEP: { - draw_texture_rect(texture, Rect2(Point2(), texture->get_size()), false); - + size = texture->get_size(); } break; case STRETCH_KEEP_CENTERED: { - - Vector2 ofs = (get_size() - texture->get_size()) / 2; - draw_texture_rect(texture, Rect2(ofs, texture->get_size()), false); + offset = (get_size() - texture->get_size()) / 2; + size = texture->get_size(); } break; case STRETCH_KEEP_ASPECT_CENTERED: case STRETCH_KEEP_ASPECT: { - - Size2 size = get_size(); + size = get_size(); int tex_width = texture->get_width() * size.height / texture->get_height(); int tex_height = size.height; @@ -70,26 +72,35 @@ void TextureRect::_notification(int p_what) { tex_height = texture->get_height() * tex_width / texture->get_width(); } - int ofs_x = 0; - int ofs_y = 0; - if (stretch_mode == STRETCH_KEEP_ASPECT_CENTERED) { - ofs_x += (size.width - tex_width) / 2; - ofs_y += (size.height - tex_height) / 2; + offset.x += (size.width - tex_width) / 2; + offset.y += (size.height - tex_height) / 2; } - draw_texture_rect(texture, Rect2(ofs_x, ofs_y, tex_width, tex_height)); + size.width = tex_width; + size.height = tex_height; } break; case STRETCH_KEEP_ASPECT_COVERED: { - Size2 size = get_size(); + size = get_size(); + Size2 tex_size = texture->get_size(); Size2 scaleSize(size.width / tex_size.width, size.height / tex_size.height); float scale = scaleSize.width > scaleSize.height ? scaleSize.width : scaleSize.height; Size2 scaledTexSize = tex_size * scale; - Point2 ofs = ((scaledTexSize - size) / scale).abs() / 2.0f; - draw_texture_rect_region(texture, Rect2(Point2(), size), Rect2(ofs, size / scale)); + + region.position = ((scaledTexSize - size) / scale).abs() / 2.0f; + region.size = size / scale; } break; } + + size.width *= hflip ? -1.0f : 1.0f; + size.height *= vflip ? -1.0f : 1.0f; + + if (region.has_no_area()) { + draw_texture_rect(texture, Rect2(offset, size), tile); + } else { + draw_texture_rect_region(texture, Rect2(offset, size), region); + } } } @@ -106,12 +117,18 @@ void TextureRect::_bind_methods() { ClassDB::bind_method(D_METHOD("get_texture"), &TextureRect::get_texture); ClassDB::bind_method(D_METHOD("set_expand", "enable"), &TextureRect::set_expand); ClassDB::bind_method(D_METHOD("has_expand"), &TextureRect::has_expand); + ClassDB::bind_method(D_METHOD("set_flip_h", "enable"), &TextureRect::set_flip_h); + ClassDB::bind_method(D_METHOD("is_flipped_h"), &TextureRect::is_flipped_h); + ClassDB::bind_method(D_METHOD("set_flip_v", "enable"), &TextureRect::set_flip_v); + ClassDB::bind_method(D_METHOD("is_flipped_v"), &TextureRect::is_flipped_v); ClassDB::bind_method(D_METHOD("set_stretch_mode", "stretch_mode"), &TextureRect::set_stretch_mode); ClassDB::bind_method(D_METHOD("get_stretch_mode"), &TextureRect::get_stretch_mode); ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "texture", PROPERTY_HINT_RESOURCE_TYPE, "Texture"), "set_texture", "get_texture"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "expand"), "set_expand", "has_expand"); ADD_PROPERTY(PropertyInfo(Variant::INT, "stretch_mode", PROPERTY_HINT_ENUM, "Scale On Expand (Compat),Scale,Tile,Keep,Keep Centered,Keep Aspect,Keep Aspect Centered,Keep Aspect Covered"), "set_stretch_mode", "get_stretch_mode"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "flip_h"), "set_flip_h", "is_flipped_h"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "flip_v"), "set_flip_v", "is_flipped_v"); BIND_ENUM_CONSTANT(STRETCH_SCALE_ON_EXPAND); BIND_ENUM_CONSTANT(STRETCH_SCALE); @@ -161,9 +178,31 @@ TextureRect::StretchMode TextureRect::get_stretch_mode() const { return stretch_mode; } +void TextureRect::set_flip_h(bool p_flip) { + + hflip = p_flip; + update(); +} +bool TextureRect::is_flipped_h() const { + + return hflip; +} + +void TextureRect::set_flip_v(bool p_flip) { + + vflip = p_flip; + update(); +} +bool TextureRect::is_flipped_v() const { + + return vflip; +} + TextureRect::TextureRect() { expand = false; + hflip = false; + vflip = false; set_mouse_filter(MOUSE_FILTER_PASS); stretch_mode = STRETCH_SCALE_ON_EXPAND; } diff --git a/scene/gui/texture_rect.h b/scene/gui/texture_rect.h index ddd101573b..1c5bd9d99c 100644 --- a/scene/gui/texture_rect.h +++ b/scene/gui/texture_rect.h @@ -32,9 +32,7 @@ #define TEXTURE_FRAME_H #include "scene/gui/control.h" -/** - @author Juan Linietsky <reduzio@gmail.com> -*/ + class TextureRect : public Control { GDCLASS(TextureRect, Control); @@ -53,6 +51,8 @@ public: private: bool expand; + bool hflip; + bool vflip; Ref<Texture> texture; StretchMode stretch_mode; @@ -71,6 +71,12 @@ public: void set_stretch_mode(StretchMode p_mode); StretchMode get_stretch_mode() const; + void set_flip_h(bool p_flip); + bool is_flipped_h() const; + + void set_flip_v(bool p_flip); + bool is_flipped_v() const; + TextureRect(); ~TextureRect(); }; diff --git a/scene/gui/tree.cpp b/scene/gui/tree.cpp index 514be00227..b7451faad3 100644 --- a/scene/gui/tree.cpp +++ b/scene/gui/tree.cpp @@ -29,7 +29,6 @@ /*************************************************************************/ #include "tree.h" -#include <limits.h> #include "core/math/math_funcs.h" #include "core/os/input.h" @@ -40,9 +39,11 @@ #include "scene/main/viewport.h" #ifdef TOOLS_ENABLED -#include "editor/editor_node.h" +#include "editor/editor_scale.h" #endif +#include <limits.h> + void TreeItem::move_to_top() { if (!parent || parent->children == this) @@ -223,14 +224,14 @@ Rect2 TreeItem::get_icon_region(int p_column) const { return cells[p_column].icon_region; } -void TreeItem::set_icon_color(int p_column, const Color &p_icon_color) { +void TreeItem::set_icon_modulate(int p_column, const Color &p_modulate) { ERR_FAIL_INDEX(p_column, cells.size()); - cells.write[p_column].icon_color = p_icon_color; + cells.write[p_column].icon_color = p_modulate; _changed_notify(p_column); } -Color TreeItem::get_icon_color(int p_column) const { +Color TreeItem::get_icon_modulate(int p_column) const { ERR_FAIL_INDEX_V(p_column, cells.size(), Color()); return cells[p_column].icon_color; @@ -317,7 +318,7 @@ void TreeItem::set_custom_draw(int p_column, Object *p_object, const StringName void TreeItem::set_collapsed(bool p_collapsed) { - if (collapsed == p_collapsed) + if (collapsed == p_collapsed || !tree) return; collapsed = p_collapsed; TreeItem *ci = tree->selected_item; @@ -343,8 +344,7 @@ void TreeItem::set_collapsed(bool p_collapsed) { } _changed_notify(); - if (tree) - tree->emit_signal("item_collapsed", this); + tree->emit_signal("item_collapsed", this); } bool TreeItem::is_collapsed() { @@ -388,7 +388,7 @@ TreeItem *TreeItem::get_children() { return children; } -TreeItem *TreeItem::get_prev_visible() { +TreeItem *TreeItem::get_prev_visible(bool p_wrap) { TreeItem *current = this; @@ -397,8 +397,20 @@ TreeItem *TreeItem::get_prev_visible() { if (!prev) { current = current->parent; - if (!current || (current == tree->root && tree->hide_root)) + if (current == tree->root && tree->hide_root) { return NULL; + } else if (!current) { + if (p_wrap) { + current = this; + TreeItem *temp = this->get_next_visible(); + while (temp) { + current = temp; + temp = temp->get_next_visible(); + } + } else { + return NULL; + } + } } else { current = prev; @@ -414,7 +426,7 @@ TreeItem *TreeItem::get_prev_visible() { return current; } -TreeItem *TreeItem::get_next_visible() { +TreeItem *TreeItem::get_next_visible(bool p_wrap) { TreeItem *current = this; @@ -432,10 +444,14 @@ TreeItem *TreeItem::get_next_visible() { current = current->parent; } - if (current == NULL) - return NULL; - else + if (!current) { + if (p_wrap) + return tree->root; + else + return NULL; + } else { current = current->next; + } } return current; @@ -556,13 +572,6 @@ int TreeItem::get_button_by_id(int p_column, int p_id) const { return -1; } -bool TreeItem::is_button_disabled(int p_column, int p_idx) const { - - ERR_FAIL_INDEX_V(p_column, cells.size(), false); - ERR_FAIL_INDEX_V(p_idx, cells[p_column].buttons.size(), false); - - return cells[p_column].buttons[p_idx].disabled; -} void TreeItem::set_button(int p_column, int p_idx, const Ref<Texture> &p_button) { ERR_FAIL_COND(p_button.is_null()); @@ -580,6 +589,23 @@ void TreeItem::set_button_color(int p_column, int p_idx, const Color &p_color) { _changed_notify(p_column); } +void TreeItem::set_button_disabled(int p_column, int p_idx, bool p_disabled) { + + ERR_FAIL_INDEX(p_column, cells.size()); + ERR_FAIL_INDEX(p_idx, cells[p_column].buttons.size()); + + cells.write[p_column].buttons.write[p_idx].disabled = p_disabled; + _changed_notify(p_column); +} + +bool TreeItem::is_button_disabled(int p_column, int p_idx) const { + + ERR_FAIL_INDEX_V(p_column, cells.size(), false); + ERR_FAIL_INDEX_V(p_idx, cells[p_column].buttons.size(), false); + + return cells[p_column].buttons[p_idx].disabled; +} + void TreeItem::set_editable(int p_column, bool p_editable) { ERR_FAIL_INDEX(p_column, cells.size()); @@ -718,6 +744,9 @@ void TreeItem::_bind_methods() { ClassDB::bind_method(D_METHOD("set_icon_max_width", "column", "width"), &TreeItem::set_icon_max_width); ClassDB::bind_method(D_METHOD("get_icon_max_width", "column"), &TreeItem::get_icon_max_width); + ClassDB::bind_method(D_METHOD("set_icon_modulate", "column", "modulate"), &TreeItem::set_icon_modulate); + ClassDB::bind_method(D_METHOD("get_icon_modulate", "column"), &TreeItem::get_icon_modulate); + ClassDB::bind_method(D_METHOD("set_range", "column", "value"), &TreeItem::set_range); ClassDB::bind_method(D_METHOD("get_range", "column"), &TreeItem::get_range); ClassDB::bind_method(D_METHOD("set_range_config", "column", "min", "max", "step", "expr"), &TreeItem::set_range_config, DEFVAL(false)); @@ -739,8 +768,8 @@ void TreeItem::_bind_methods() { 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"), &TreeItem::get_next_visible); - ClassDB::bind_method(D_METHOD("get_prev_visible"), &TreeItem::get_prev_visible); + 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); @@ -769,6 +798,7 @@ void TreeItem::_bind_methods() { ClassDB::bind_method(D_METHOD("get_button", "column", "button_idx"), &TreeItem::get_button); ClassDB::bind_method(D_METHOD("set_button", "column", "button_idx", "button"), &TreeItem::set_button); ClassDB::bind_method(D_METHOD("erase_button", "column", "button_idx"), &TreeItem::erase_button); + 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); @@ -940,6 +970,7 @@ int Tree::compute_item_height(TreeItem *p_item) const { int check_icon_h = cache.checked->get_height(); if (height < check_icon_h) height = check_icon_h; + FALLTHROUGH; } case TreeItem::CELL_MODE_STRING: case TreeItem::CELL_MODE_CUSTOM: @@ -960,7 +991,8 @@ int Tree::compute_item_height(TreeItem *p_item) const { } } break; - default: {} + default: { + } } } int item_min_height = p_item->get_custom_minimum_height(); @@ -1071,11 +1103,11 @@ int Tree::draw_item(const Point2i &p_pos, const Point2 &p_draw_ofs, const Size2 int font_ascent = font->get_ascent(); int ofs = p_pos.x + ((p_item->disable_folding || hide_folding) ? cache.hseparation : cache.item_margin); - int skip = 0; + int skip2 = 0; for (int i = 0; i < columns.size(); i++) { - if (skip) { - skip--; + if (skip2) { + skip2--; continue; } @@ -1102,7 +1134,7 @@ int Tree::draw_item(const Point2i &p_pos, const Point2 &p_draw_ofs, const Size2 while (i + plus < columns.size() && !p_item->cells[i + plus].editable && p_item->cells[i + plus].mode == TreeItem::CELL_MODE_STRING && p_item->cells[i + plus].text == "" && p_item->cells[i + plus].icon.is_null()) { w += get_column_width(i + plus); plus++; - skip++; + skip2++; } } @@ -1110,6 +1142,8 @@ int Tree::draw_item(const Point2i &p_pos, const Point2 &p_draw_ofs, const Size2 for (int j = p_item->cells[i].buttons.size() - 1; j >= 0; j--) { Ref<Texture> b = p_item->cells[i].buttons[j].texture; Size2 s = b->get_size() + cache.button_pressed->get_minimum_size(); + if (s.height < label_h) + s.height = label_h; Point2i o = Point2i(ofs + w - s.width, p_pos.y) - cache.offset + p_draw_ofs; @@ -1165,8 +1199,8 @@ int Tree::draw_item(const Point2i &p_pos, const Point2 &p_draw_ofs, const Size2 cache.selected->draw(ci, r); } if (text_editor->is_visible_in_tree()) { - Vector2 ofs(0, (text_editor->get_size().height - r.size.height) / 2); - text_editor->set_position(get_global_position() + r.position - ofs); + Vector2 ofs2(0, (text_editor->get_size().height - r.size.height) / 2); + text_editor->set_position(get_global_position() + r.position - ofs2); } } @@ -1228,10 +1262,9 @@ int Tree::draw_item(const Point2i &p_pos, const Point2 &p_draw_ofs, const Size2 check_ofs.y += Math::floor((real_t)(item_rect.size.y - checked->get_height()) / 2); if (p_item->cells[i].checked) { - - checked->draw(ci, check_ofs, icon_col); + checked->draw(ci, check_ofs); } else { - unchecked->draw(ci, check_ofs, icon_col); + unchecked->draw(ci, check_ofs); } int check_w = checked->get_width() + cache.hseparation; @@ -1243,8 +1276,6 @@ int Tree::draw_item(const Point2i &p_pos, const Point2 &p_draw_ofs, const Size2 draw_item_rect(p_item->cells[i], item_rect, col, icon_col); - //font->draw( ci, text_pos, p_item->cells[i].text, col,item_rect.size.x-check_w ); - } break; case TreeItem::CELL_MODE_RANGE: { if (p_item->cells[i].text != "") { @@ -1274,18 +1305,16 @@ int Tree::draw_item(const Point2i &p_pos, const Point2 &p_draw_ofs, const Size2 font->draw(ci, text_pos, s, col, item_rect.size.x - downarrow->get_width()); - //? Point2i arrow_pos = item_rect.position; arrow_pos.x += item_rect.size.x - downarrow->get_width(); arrow_pos.y += Math::floor(((item_rect.size.y - downarrow->get_height())) / 2.0); - downarrow->draw(ci, arrow_pos, icon_col); + downarrow->draw(ci, arrow_pos); } else { Ref<Texture> updown = cache.updown; - String valtext = String::num(p_item->cells[i].val, Math::step_decimals(p_item->cells[i].step)); - //String valtext = rtos( p_item->cells[i].val ); + String valtext = String::num(p_item->cells[i].val, Math::range_step_decimals(p_item->cells[i].step)); if (p_item->cells[i].suffix != String()) valtext += " " + p_item->cells[i].suffix; @@ -1299,7 +1328,7 @@ int Tree::draw_item(const Point2i &p_pos, const Point2 &p_draw_ofs, const Size2 updown_pos.x += item_rect.size.x - updown->get_width(); updown_pos.y += Math::floor(((item_rect.size.y - updown->get_height())) / 2.0); - updown->draw(ci, updown_pos, icon_col); + updown->draw(ci, updown_pos); } } break; @@ -1317,13 +1346,10 @@ int Tree::draw_item(const Point2i &p_pos, const Point2 &p_draw_ofs, const Size2 icon_ofs += item_rect.position; draw_texture_rect(p_item->cells[i].icon, Rect2(icon_ofs, icon_size), false, icon_col); - //p_item->cells[i].icon->draw(ci, icon_ofs); } break; case TreeItem::CELL_MODE_CUSTOM: { - //int option = (int)p_item->cells[i].val; - if (p_item->cells[i].custom_draw_obj) { Object *cdo = ObjectDB::get_instance(p_item->cells[i].custom_draw_obj); @@ -1398,10 +1424,6 @@ int Tree::draw_item(const Point2i &p_pos, const Point2 &p_draw_ofs, const Size2 arrow->draw(ci, p_pos + p_draw_ofs + Point2i(0, (label_h - arrow->get_height()) / 2) - cache.offset); } - //separator - //get_painter()->draw_fill_rect( Point2i(0,pos.y),Size2i(get_size().width,1),color( COLOR_TREE_GRID) ); - - //pos=p_pos; //reset pos } Point2 children_pos = p_pos; @@ -1416,12 +1438,15 @@ int Tree::draw_item(const Point2i &p_pos, const Point2 &p_draw_ofs, const Size2 TreeItem *c = p_item->children; + int prev_ofs = children_pos.y - cache.offset.y + p_draw_ofs.y; + while (c) { if (cache.draw_relationship_lines > 0 && (!hide_root || c->parent != root)) { int root_ofs = children_pos.x + ((p_item->disable_folding || hide_folding) ? cache.hseparation : cache.item_margin); int parent_ofs = p_pos.x + ((p_item->disable_folding || hide_folding) ? cache.hseparation : cache.item_margin); Point2i root_pos = Point2i(root_ofs, children_pos.y + label_h / 2) - cache.offset + p_draw_ofs; + if (c->get_children() != NULL) root_pos -= Point2i(cache.arrow->get_width(), 0); @@ -1434,12 +1459,13 @@ int Tree::draw_item(const Point2i &p_pos, const Point2 &p_draw_ofs, const Size2 if (root_pos.y + line_width >= 0) { VisualServer::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); - VisualServer::get_singleton()->canvas_item_add_line(ci, Point2i(parent_pos.x, root_pos.y), parent_pos, cache.relationship_line_color, line_width); + VisualServer::get_singleton()->canvas_item_add_line(ci, Point2i(parent_pos.x, root_pos.y), Point2i(parent_pos.x, prev_ofs), cache.relationship_line_color, line_width); } if (htotal < 0) { return -1; } + prev_ofs = root_pos.y; } if (htotal >= 0) { @@ -1601,7 +1627,7 @@ void Tree::_range_click_timeout() { mb.instance(); ; - propagate_mouse_activated = false; //done from outside, so signal handler cant clear the tree in the middle of emit(which is a common case) + 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); blocked--; @@ -1891,7 +1917,7 @@ int Tree::propagate_mouse_event(const Point2i &p_pos, int x_ofs, int y_ofs, bool } else { - editor_text = String::num(p_item->cells[col].val, Math::step_decimals(p_item->cells[col].step)); + editor_text = String::num(p_item->cells[col].val, Math::range_step_decimals(p_item->cells[col].step)); if (select_mode == SELECT_MULTI && get_tree()->get_event_count() == focus_in_id) bring_up_editor = false; } @@ -1907,8 +1933,6 @@ int Tree::propagate_mouse_event(const Point2i &p_pos, int x_ofs, int y_ofs, bool edited_col = col; bool on_arrow = x > col_width - cache.select_arrow->get_width(); - bring_up_editor = false; - custom_popup_rect = Rect2i(get_global_position() + Point2i(col_ofs, _get_title_button_height() + y_ofs + item_h - cache.offset.y), Size2(get_column_width(col), item_h)); if (on_arrow || !p_item->cells[col].custom_button) { @@ -1963,6 +1987,9 @@ 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) { + emit_signal("empty_rmb", get_local_mouse_position()); + } } return item_h; // nothing found @@ -2014,7 +2041,9 @@ void Tree::text_editor_enter(String p_text) { //popup_edited_item->edited_signal.call( popup_edited_item_col ); } break; - default: { ERR_FAIL(); } + default: { + ERR_FAIL(); + } } item_edited(popup_edited_item_col, popup_edited_item); @@ -2257,7 +2286,7 @@ void Tree::_gui_input(Ref<InputEvent> p_event) { next = _n; } else { - return; + break; } } if (next == selected_item) @@ -2295,7 +2324,7 @@ void Tree::_gui_input(Ref<InputEvent> p_event) { prev = _n; } else { - return; + break; } } if (prev == selected_item) @@ -2662,12 +2691,10 @@ void Tree::_gui_input(Ref<InputEvent> p_event) { bool Tree::edit_selected() { TreeItem *s = get_selected(); - ERR_EXPLAIN("No item selected!"); - ERR_FAIL_COND_V(!s, false); + ERR_FAIL_COND_V_MSG(!s, false, "No item selected."); ensure_cursor_is_visible(); int col = get_selected_column(); - ERR_EXPLAIN("No item column selected!"); - ERR_FAIL_INDEX_V(col, columns.size(), false); + ERR_FAIL_INDEX_V_MSG(col, columns.size(), false, "No item column selected."); if (!s->cells[col].editable) return false; @@ -2698,8 +2725,8 @@ bool Tree::edit_selected() { popup_menu->clear(); for (int i = 0; i < c.text.get_slice_count(","); i++) { - String s = c.text.get_slicec(',', i); - popup_menu->add_item(s.get_slicec(':', 0), s.get_slicec(':', 1).empty() ? i : s.get_slicec(':', 1).to_int()); + String s2 = c.text.get_slicec(',', i); + popup_menu->add_item(s2.get_slicec(':', 0), s2.get_slicec(':', 1).empty() ? i : s2.get_slicec(':', 1).to_int()); } popup_menu->set_size(Size2(rect.size.width, 0)); @@ -2717,7 +2744,7 @@ bool Tree::edit_selected() { text_editor->set_position(textedpos); text_editor->set_size(rect.size); text_editor->clear(); - text_editor->set_text(c.mode == TreeItem::CELL_MODE_STRING ? c.text : String::num(c.val, Math::step_decimals(c.step))); + text_editor->set_text(c.mode == TreeItem::CELL_MODE_STRING ? c.text : String::num(c.val, Math::range_step_decimals(c.step))); text_editor->select_all(); if (c.mode == TreeItem::CELL_MODE_RANGE) { @@ -2841,7 +2868,7 @@ void Tree::_notification(int p_what) { if (p_what == NOTIFICATION_DRAG_BEGIN) { single_select_defer = NULL; - if (cache.scroll_speed > 0 && get_rect().has_point(get_viewport()->get_mouse_position() - get_global_position())) { + if (cache.scroll_speed > 0) { scrolling = true; set_physics_process_internal(true); } @@ -2883,27 +2910,25 @@ void Tree::_notification(int p_what) { drag_touching = false; drag_touching_deaccel = false; } - - } else { } } - if (scrolling) { - Point2 point = get_viewport()->get_mouse_position() - get_global_position(); - if (point.x < cache.scroll_border) { - point.x -= cache.scroll_border; - } else if (point.x > get_size().width - cache.scroll_border) { - point.x -= get_size().width - cache.scroll_border; - } else { - point.x = 0; + Point2 mouse_position = get_viewport()->get_mouse_position() - get_global_position(); + if (scrolling && get_rect().grow(cache.scroll_border).has_point(mouse_position)) { + Point2 point; + + if ((ABS(mouse_position.x) < ABS(mouse_position.x - get_size().width)) && (ABS(mouse_position.x) < cache.scroll_border)) { + point.x = mouse_position.x - cache.scroll_border; + } else if (ABS(mouse_position.x - get_size().width) < cache.scroll_border) { + point.x = mouse_position.x - (get_size().width - cache.scroll_border); } - if (point.y < cache.scroll_border) { - point.y -= cache.scroll_border; - } else if (point.y > get_size().height - cache.scroll_border) { - point.y -= get_size().height - cache.scroll_border; - } else { - point.y = 0; + + if ((ABS(mouse_position.y) < ABS(mouse_position.y - get_size().height)) && (ABS(mouse_position.y) < cache.scroll_border)) { + point.y = mouse_position.y - cache.scroll_border; + } else if (ABS(mouse_position.y - get_size().height) < cache.scroll_border) { + point.y = mouse_position.y - (get_size().height - cache.scroll_border); } + point *= cache.scroll_speed * get_physics_process_delta_time(); point += get_scroll(); h_scroll->set_value(point.x); @@ -2951,14 +2976,14 @@ void Tree::_notification(int p_what) { if (show_column_titles) { //title buttons - int ofs = cache.bg->get_margin(MARGIN_LEFT); + int ofs2 = cache.bg->get_margin(MARGIN_LEFT); for (int i = 0; i < columns.size(); i++) { Ref<StyleBox> sb = (cache.click_type == Cache::CLICK_TITLE && cache.click_index == i) ? cache.title_button_pressed : ((cache.hover_type == Cache::CLICK_TITLE && cache.hover_index == i) ? cache.title_button_hover : cache.title_button); Ref<Font> f = cache.tb_font; - Rect2 tbrect = Rect2(ofs - cache.offset.x, bg->get_margin(MARGIN_TOP), get_column_width(i), tbh); + Rect2 tbrect = Rect2(ofs2 - cache.offset.x, bg->get_margin(MARGIN_TOP), get_column_width(i), tbh); sb->draw(ci, tbrect); - ofs += tbrect.size.width; + ofs2 += tbrect.size.width; //text int clip_w = tbrect.size.width - sb->get_minimum_size().width; f->draw_halign(ci, tbrect.position + Point2i(sb->get_offset().x, (tbrect.size.height - f->get_height()) / 2 + f->get_ascent()), HALIGN_CENTER, clip_w, columns[i].title, cache.title_button_color); @@ -3136,10 +3161,7 @@ bool Tree::is_anything_selected() { void Tree::clear() { - if (blocked > 0) { - - ERR_FAIL_COND(blocked > 0); - } + ERR_FAIL_COND(blocked > 0); if (pressing_for_editor) { if (range_drag_enabled) { @@ -3379,10 +3401,13 @@ void Tree::ensure_cursor_is_visible() { int h = compute_item_height(selected) + cache.vseparation; int screenh = get_size().height - h_scroll->get_combined_minimum_size().height; - if (ofs + h > v_scroll->get_value() + screenh) + if (h > screenh) { //screen size is too small, maybe it was not resized yet. + v_scroll->set_value(ofs); + } else if (ofs + h > v_scroll->get_value() + screenh) { v_scroll->call_deferred("set_value", ofs - screenh + h); - else if (ofs < v_scroll->get_value()) + } else if (ofs < v_scroll->get_value()) { v_scroll->set_value(ofs); + } } int Tree::get_pressed_button() const { @@ -3476,6 +3501,7 @@ void Tree::scroll_to_item(TreeItem *p_item) { TreeItem *Tree::_search_item_text(TreeItem *p_at, const String &p_find, int *r_col, bool p_selectable, bool p_backwards) { + TreeItem *from = p_at; while (p_at) { for (int i = 0; i < columns.size(); i++) { @@ -3487,9 +3513,12 @@ TreeItem *Tree::_search_item_text(TreeItem *p_at, const String &p_find, int *r_c } if (p_backwards) - p_at = p_at->get_prev_visible(); + p_at = p_at->get_prev_visible(true); else - p_at = p_at->get_next_visible(); + p_at = p_at->get_next_visible(true); + + if ((p_at) == from) + break; } return NULL; @@ -3497,10 +3526,14 @@ TreeItem *Tree::_search_item_text(TreeItem *p_at, const String &p_find, int *r_c TreeItem *Tree::search_item_text(const String &p_find, int *r_col, bool p_selectable) { - if (!root) + TreeItem *from = get_selected(); + + if (!from) + from = root; + if (!from) return NULL; - return _search_item_text(root, p_find, r_col, p_selectable); + return _search_item_text(from->get_next_visible(true), p_find, r_col, p_selectable); } void Tree::_do_incr_search(const String &p_add) { @@ -3509,7 +3542,7 @@ void Tree::_do_incr_search(const String &p_add) { uint64_t diff = time - last_keypress; if (diff > uint64_t(GLOBAL_DEF("gui/timers/incremental_search_max_interval_msec", 2000))) incr_search = p_add; - else + else if (incr_search != p_add) incr_search += p_add; last_keypress = time; @@ -3682,6 +3715,10 @@ String Tree::get_tooltip(const Point2 &p_pos) const { const TreeItem::Cell &c = it->cells[col]; int col_width = get_column_width(col); + + for (int i = 0; i < col; i++) + pos.x -= get_column_width(i); + for (int j = c.buttons.size() - 1; j >= 0; j--) { Ref<Texture> b = c.buttons[j].texture; Size2 size = b->get_size() + cache.button_pressed->get_minimum_size(); @@ -3841,6 +3878,7 @@ void Tree::_bind_methods() { ADD_SIGNAL(MethodInfo("cell_selected")); ADD_SIGNAL(MethodInfo("multi_selected", PropertyInfo(Variant::OBJECT, "item", PROPERTY_HINT_RESOURCE_TYPE, "TreeItem"), PropertyInfo(Variant::INT, "column"), PropertyInfo(Variant::BOOL, "selected"))); ADD_SIGNAL(MethodInfo("item_rmb_selected", PropertyInfo(Variant::VECTOR2, "position"))); + ADD_SIGNAL(MethodInfo("empty_rmb", PropertyInfo(Variant::VECTOR2, "position"))); ADD_SIGNAL(MethodInfo("empty_tree_rmb_selected", PropertyInfo(Variant::VECTOR2, "position"))); ADD_SIGNAL(MethodInfo("item_edited")); ADD_SIGNAL(MethodInfo("item_rmb_edited")); @@ -3926,7 +3964,6 @@ Tree::Tree() { cache.click_item = NULL; cache.click_column = 0; cache.hover_cell = -1; - cache.hover_index = -1; last_keypress = 0; focus_in_id = 0; diff --git a/scene/gui/tree.h b/scene/gui/tree.h index 26d64baafb..fdc6da5055 100644 --- a/scene/gui/tree.h +++ b/scene/gui/tree.h @@ -37,10 +37,6 @@ #include "scene/gui/scroll_bar.h" #include "scene/gui/slider.h" -/** - @author Juan Linietsky <reduzio@gmail.com> -*/ - class Tree; class TreeItem : public Object { @@ -197,8 +193,8 @@ public: void set_icon_region(int p_column, const Rect2 &p_icon_region); Rect2 get_icon_region(int p_column) const; - void set_icon_color(int p_column, const Color &p_icon_color); - Color get_icon_color(int p_column) const; + void set_icon_modulate(int p_column, const Color &p_modulate); + Color get_icon_modulate(int p_column) const; void set_icon_max_width(int p_column, int p_max); int get_icon_max_width(int p_column) const; @@ -209,9 +205,10 @@ public: int get_button_id(int p_column, int p_idx) const; void erase_button(int p_column, int p_idx); int get_button_by_id(int p_column, int p_id) const; - bool is_button_disabled(int p_column, int p_idx) const; void set_button(int p_column, int p_idx, const Ref<Texture> &p_button); void set_button_color(int p_column, int p_idx, const Color &p_color); + void set_button_disabled(int p_column, int p_idx, bool p_disabled); + bool is_button_disabled(int p_column, int p_idx) const; /* range works for mode number or mode combo */ @@ -238,8 +235,8 @@ public: TreeItem *get_parent(); TreeItem *get_children(); - TreeItem *get_prev_visible(); - TreeItem *get_next_visible(); + TreeItem *get_prev_visible(bool p_wrap = false); + TreeItem *get_next_visible(bool p_wrap = false); void remove_child(TreeItem *p_item); |