diff options
Diffstat (limited to 'scene/gui')
-rw-r--r-- | scene/gui/check_button.cpp | 23 | ||||
-rw-r--r-- | scene/gui/check_button.h | 1 | ||||
-rw-r--r-- | scene/gui/control.cpp | 198 | ||||
-rw-r--r-- | scene/gui/control.h | 39 | ||||
-rw-r--r-- | scene/gui/item_list.cpp | 4 | ||||
-rw-r--r-- | scene/gui/line_edit.cpp | 17 | ||||
-rw-r--r-- | scene/gui/line_edit.h | 3 | ||||
-rw-r--r-- | scene/gui/scroll_container.cpp | 5 | ||||
-rw-r--r-- | scene/gui/text_edit.cpp | 617 | ||||
-rw-r--r-- | scene/gui/text_edit.h | 43 | ||||
-rw-r--r-- | scene/gui/video_player.cpp | 144 | ||||
-rw-r--r-- | scene/gui/video_player.h | 22 |
12 files changed, 954 insertions, 162 deletions
diff --git a/scene/gui/check_button.cpp b/scene/gui/check_button.cpp index e68159e27f..77fdedd5e5 100644 --- a/scene/gui/check_button.cpp +++ b/scene/gui/check_button.cpp @@ -32,6 +32,23 @@ #include "print_string.h" #include "servers/visual_server.h" +Size2 CheckButton::get_minimum_size() const { + + Size2 minsize = Button::get_minimum_size(); + + Ref<Texture> on = Control::get_icon("on"); + Ref<Texture> off = Control::get_icon("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())); + minsize += Size2(tex_size.width + get_constant("hseparation"), 0); + minsize.height = MAX(minsize.height, tex_size.height); + + return get_stylebox("normal")->get_minimum_size() + minsize; +} + void CheckButton::_notification(int p_what) { if (p_what == NOTIFICATION_DRAW) { @@ -41,9 +58,11 @@ void CheckButton::_notification(int p_what) { Ref<Texture> on = Control::get_icon("on"); Ref<Texture> off = Control::get_icon("off"); + Ref<StyleBox> sb = get_stylebox("normal"); + Size2 sb_ofs = Size2(sb->get_margin(MARGIN_RIGHT), sb->get_margin(MARGIN_TOP)); Vector2 ofs; - ofs.x = get_size().width - on->get_width(); - ofs.y = int((get_size().height - on->get_height()) / 2); + ofs.x = get_minimum_size().width - (on->get_width() + sb_ofs.width); + ofs.y = sb_ofs.height; if (is_pressed()) on->draw(ci, ofs); diff --git a/scene/gui/check_button.h b/scene/gui/check_button.h index af3b80fe04..eb68943fe7 100644 --- a/scene/gui/check_button.h +++ b/scene/gui/check_button.h @@ -39,6 +39,7 @@ class CheckButton : public Button { GDCLASS(CheckButton, Button); protected: + virtual Size2 get_minimum_size() const; void _notification(int p_what); public: diff --git a/scene/gui/control.cpp b/scene/gui/control.cpp index 54a58159ac..adca78d1d4 100644 --- a/scene/gui/control.cpp +++ b/scene/gui/control.cpp @@ -45,7 +45,7 @@ #endif #include <stdio.h> -Variant Control::edit_get_state() const { +Dictionary Control::_edit_get_state() const { Dictionary s; s["rect"] = get_rect(); @@ -59,22 +59,78 @@ Variant Control::edit_get_state() const { s["anchors"] = anchors; return s; } -void Control::edit_set_state(const Variant &p_state) { +void Control::_edit_set_state(const Dictionary &p_state) { - Dictionary s = p_state; + Dictionary state = p_state; - Rect2 state = s["rect"]; - set_position(state.position); - set_size(state.size); - set_rotation(s["rotation"]); - set_scale(s["scale"]); - Array anchors = s["anchors"]; + Rect2 rect = state["rect"]; + set_position(rect.position); + set_size(rect.size); + set_rotation(state["rotation"]); + set_scale(state["scale"]); + Array anchors = state["anchors"]; set_anchor(MARGIN_LEFT, anchors[0]); set_anchor(MARGIN_TOP, anchors[1]); set_anchor(MARGIN_RIGHT, anchors[2]); set_anchor(MARGIN_BOTTOM, anchors[3]); } +void Control::_edit_set_position(const Point2 &p_position) { + set_position(p_position); +}; + +Point2 Control::_edit_get_position() const { + return get_position(); +}; + +void Control::_edit_set_rect(const Rect2 &p_edit_rect) { + + Transform2D xform = _get_internal_transform(); + + Vector2 new_pos = xform.basis_xform(p_edit_rect.position); + + Vector2 pos = get_position() + new_pos; + + Rect2 new_rect = get_rect(); + new_rect.position = pos.snapped(Vector2(1, 1)); + new_rect.size = p_edit_rect.size.snapped(Vector2(1, 1)); + + set_position(new_rect.position); + set_size(new_rect.size); +} + +Rect2 Control::_edit_get_rect() const { + return Rect2(Point2(), get_size()); +} + +bool Control::_edit_use_rect() const { + return true; +} + +void Control::_edit_set_rotation(float p_rotation) { + set_rotation(p_rotation); +} + +float Control::_edit_get_rotation() const { + return get_rotation(); +} + +bool Control::_edit_use_rotation() const { + return true; +} + +void Control::_edit_set_pivot(const Point2 &p_pivot) { + set_pivot_offset(p_pivot); +} + +Point2 Control::_edit_get_pivot() const { + return get_pivot_offset(); +} + +bool Control::_edit_use_pivot() const { + return true; +} + void Control::set_custom_minimum_size(const Size2 &p_custom) { if (p_custom == data.custom_minimum_size) @@ -96,7 +152,7 @@ Size2 Control::get_combined_minimum_size() const { return minsize; } -Size2 Control::edit_get_minimum_size() const { +Size2 Control::_edit_get_minimum_size() const { return get_combined_minimum_size(); } @@ -110,23 +166,6 @@ Transform2D Control::_get_internal_transform() const { return offset.affine_inverse() * (rot_scale * offset); } -void Control::edit_set_rect(const Rect2 &p_edit_rect) { - - Transform2D xform = _get_internal_transform(); - - // xform[2] += get_position(); - - Vector2 new_pos = xform.basis_xform(p_edit_rect.position); - - Vector2 pos = get_position() + new_pos; - - Rect2 new_rect = get_rect(); - new_rect.position = pos.snapped(Vector2(1, 1)); - new_rect.size = p_edit_rect.size.snapped(Vector2(1, 1)); - - set_position(new_rect.position); - set_size(new_rect.size); -} bool Control::_set(const StringName &p_name, const Variant &p_value) { @@ -1210,7 +1249,7 @@ Size2 Control::get_parent_area_size() const { if (data.parent_canvas_item) { - parent_size = data.parent_canvas_item->get_item_rect().size; + parent_size = data.parent_canvas_item->_edit_get_rect().size; } else { parent_size = get_viewport()->get_visible_rect().size; @@ -1289,7 +1328,7 @@ float Control::_get_parent_range(int p_idx) const { } if (data.parent_canvas_item) { - return data.parent_canvas_item->get_item_rect().size[p_idx & 1]; + return data.parent_canvas_item->_edit_get_rect().size[p_idx & 1]; } else { return get_viewport()->get_visible_rect().size[p_idx & 1]; } @@ -1751,11 +1790,6 @@ Rect2 Control::get_rect() const { return Rect2(get_position(), get_size()); } -Rect2 Control::get_item_rect() const { - - return Rect2(Point2(), get_size()); -} - void Control::add_icon_override(const StringName &p_name, const Ref<Texture> &p_icon) { ERR_FAIL_COND(p_icon.is_null()); @@ -1847,6 +1881,25 @@ Control *Control::find_next_valid_focus() const { while (true) { + // If the focus property is manually overwritten, attempt to use it. + + if (!data.focus_next.is_empty()) { + 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); + } + } else { + return NULL; + } + if (from->is_visible() && from->get_focus_mode() != FOCUS_NONE) + return from; + } + // find next child Control *next_child = NULL; @@ -1926,6 +1979,25 @@ Control *Control::find_prev_valid_focus() const { while (true) { + // If the focus property is manually overwritten, attempt to use it. + + if (!data.focus_prev.is_empty()) { + Node *n = get_node(data.focus_prev); + if (n) { + from = Object::cast_to<Control>(n); + + if (!from) { + + ERR_EXPLAIN("Prev focus node is not a control: " + n->get_name()); + ERR_FAIL_V(NULL); + } + } else { + return NULL; + } + if (from->is_visible() && from->get_focus_mode() != FOCUS_NONE) + return from; + } + // find prev child Control *prev_child = NULL; @@ -2157,6 +2229,26 @@ NodePath Control::get_focus_neighbour(Margin p_margin) const { return data.focus_neighbour[p_margin]; } +void Control::set_focus_next(const NodePath &p_next) { + + data.focus_next = p_next; +} + +NodePath Control::get_focus_next() const { + + return data.focus_next; +} + +void Control::set_focus_previous(const NodePath &p_prev) { + + data.focus_prev = p_prev; +} + +NodePath Control::get_focus_previous() const { + + return data.focus_prev; +} + #define MAX_NEIGHBOUR_SEARCH_COUNT 512 Control *Control::_get_focus_neighbour(Margin p_margin, int p_count) { @@ -2172,7 +2264,7 @@ Control *Control::_get_focus_neighbour(Margin p_margin, int p_count) { if (!c) { - ERR_EXPLAIN("Next focus node is not a control: " + n->get_name()); + ERR_EXPLAIN("Neighbour focus node is not a control: " + n->get_name()); ERR_FAIL_V(NULL); } } else { @@ -2196,7 +2288,7 @@ Control *Control::_get_focus_neighbour(Margin p_margin, int p_count) { Point2 points[4]; Transform2D xform = get_global_transform(); - Rect2 rect = get_item_rect(); + Rect2 rect = _edit_get_rect(); points[0] = xform.xform(rect.position); points[1] = xform.xform(rect.position + Point2(rect.size.x, 0)); @@ -2255,7 +2347,7 @@ void Control::_window_find_focus_neighbour(const Vector2 &p_dir, Node *p_at, con Point2 points[4]; Transform2D xform = c->get_global_transform(); - Rect2 rect = c->get_item_rect(); + Rect2 rect = c->_edit_get_rect(); points[0] = xform.xform(rect.position); points[1] = xform.xform(rect.position + Point2(rect.size.x, 0)); @@ -2416,24 +2508,14 @@ float Control::get_rotation() const { return data.rotation; } -void Control::set_rotation_deg(float p_degrees) { +void Control::set_rotation_degrees(float p_degrees) { set_rotation(Math::deg2rad(p_degrees)); } -float Control::get_rotation_deg() const { +float Control::get_rotation_degrees() const { return Math::rad2deg(get_rotation()); } -// Kept for compatibility after rename to {s,g}et_rotation_deg. -// Could be removed after a couple releases. -void Control::_set_rotation_deg(float p_degrees) { - WARN_PRINT("Deprecated method Control._set_rotation_deg(): This method was renamed to set_rotation_deg. Please adapt your code accordingly, as the old method will be obsoleted."); - set_rotation_deg(p_degrees); -} -float Control::_get_rotation_deg() const { - WARN_PRINT("Deprecated method Control._get_rotation_deg(): This method was renamed to get_rotation_deg. Please adapt your code accordingly, as the old method will be obsoleted."); - return get_rotation_deg(); -} //needed to update the control if the font changes.. void Control::_ref_font(Ref<Font> p_sc) { @@ -2606,9 +2688,7 @@ void Control::_bind_methods() { 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_rotation", "radians"), &Control::set_rotation); - ClassDB::bind_method(D_METHOD("set_rotation_deg", "degrees"), &Control::set_rotation_deg); - // TODO: Obsolete this method (old name) properly (GH-4397) - ClassDB::bind_method(D_METHOD("_set_rotation_deg", "degrees"), &Control::_set_rotation_deg); + ClassDB::bind_method(D_METHOD("set_rotation_degrees", "degrees"), &Control::set_rotation_degrees); ClassDB::bind_method(D_METHOD("set_scale", "scale"), &Control::set_scale); ClassDB::bind_method(D_METHOD("set_pivot_offset", "pivot_offset"), &Control::set_pivot_offset); ClassDB::bind_method(D_METHOD("get_margin", "margin"), &Control::get_margin); @@ -2617,9 +2697,7 @@ void Control::_bind_methods() { ClassDB::bind_method(D_METHOD("get_position"), &Control::get_position); ClassDB::bind_method(D_METHOD("get_size"), &Control::get_size); ClassDB::bind_method(D_METHOD("get_rotation"), &Control::get_rotation); - ClassDB::bind_method(D_METHOD("get_rotation_deg"), &Control::get_rotation_deg); - // TODO: Obsolete this method (old name) properly (GH-4397) - ClassDB::bind_method(D_METHOD("_get_rotation_deg"), &Control::_get_rotation_deg); + ClassDB::bind_method(D_METHOD("get_rotation_degrees"), &Control::get_rotation_degrees); 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); @@ -2691,6 +2769,12 @@ void Control::_bind_methods() { ClassDB::bind_method(D_METHOD("set_focus_neighbour", "margin", "neighbour"), &Control::set_focus_neighbour); ClassDB::bind_method(D_METHOD("get_focus_neighbour", "margin"), &Control::get_focus_neighbour); + ClassDB::bind_method(D_METHOD("set_focus_next", "next"), &Control::set_focus_next); + ClassDB::bind_method(D_METHOD("get_focus_next"), &Control::get_focus_next); + + ClassDB::bind_method(D_METHOD("set_focus_previous", "previous"), &Control::set_focus_previous); + ClassDB::bind_method(D_METHOD("get_focus_previous"), &Control::get_focus_previous); + ClassDB::bind_method(D_METHOD("force_drag", "data", "preview"), &Control::force_drag); ClassDB::bind_method(D_METHOD("set_mouse_filter", "filter"), &Control::set_mouse_filter); @@ -2738,7 +2822,7 @@ void Control::_bind_methods() { ADD_PROPERTYNZ(PropertyInfo(Variant::VECTOR2, "rect_position", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_EDITOR), "set_position", "get_position"); ADD_PROPERTYNZ(PropertyInfo(Variant::VECTOR2, "rect_size", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_EDITOR), "set_size", "get_size"); ADD_PROPERTYNZ(PropertyInfo(Variant::VECTOR2, "rect_min_size"), "set_custom_minimum_size", "get_custom_minimum_size"); - ADD_PROPERTYNZ(PropertyInfo(Variant::REAL, "rect_rotation", PROPERTY_HINT_RANGE, "-1080,1080,0.01"), "set_rotation_deg", "get_rotation_deg"); + ADD_PROPERTYNZ(PropertyInfo(Variant::REAL, "rect_rotation", PROPERTY_HINT_RANGE, "-1080,1080,0.01"), "set_rotation_degrees", "get_rotation_degrees"); ADD_PROPERTYNO(PropertyInfo(Variant::VECTOR2, "rect_scale"), "set_scale", "get_scale"); ADD_PROPERTYNO(PropertyInfo(Variant::VECTOR2, "rect_pivot_offset"), "set_pivot_offset", "get_pivot_offset"); ADD_PROPERTYNO(PropertyInfo(Variant::BOOL, "rect_clip_content"), "set_clip_contents", "is_clipping_contents"); @@ -2751,6 +2835,8 @@ void Control::_bind_methods() { ADD_PROPERTYINZ(PropertyInfo(Variant::NODE_PATH, "focus_neighbour_top"), "set_focus_neighbour", "get_focus_neighbour", MARGIN_TOP); ADD_PROPERTYINZ(PropertyInfo(Variant::NODE_PATH, "focus_neighbour_right"), "set_focus_neighbour", "get_focus_neighbour", MARGIN_RIGHT); ADD_PROPERTYINZ(PropertyInfo(Variant::NODE_PATH, "focus_neighbour_bottom"), "set_focus_neighbour", "get_focus_neighbour", MARGIN_BOTTOM); + ADD_PROPERTYNZ(PropertyInfo(Variant::NODE_PATH, "focus_next"), "set_focus_next", "get_focus_next"); + ADD_PROPERTYNZ(PropertyInfo(Variant::NODE_PATH, "focus_previous"), "set_focus_previous", "get_focus_previous"); ADD_GROUP("Mouse", "mouse_"); ADD_PROPERTY(PropertyInfo(Variant::INT, "mouse_filter", PROPERTY_HINT_ENUM, "Stop,Pass,Ignore"), "set_mouse_filter", "get_mouse_filter"); diff --git a/scene/gui/control.h b/scene/gui/control.h index 5b146b4454..92d1c969fc 100644 --- a/scene/gui/control.h +++ b/scene/gui/control.h @@ -191,6 +191,8 @@ private: ObjectID modal_prev_focus_owner; NodePath focus_neighbour[4]; + NodePath focus_next; + NodePath focus_prev; HashMap<StringName, Ref<Texture>, StringNameHasher> icon_override; HashMap<StringName, Ref<Shader>, StringNameHasher> shader_override; @@ -226,10 +228,6 @@ private: void _size_changed(); String _get_tooltip() const; - // Deprecated, should be removed in a future version. - void _set_rotation_deg(float p_degrees); - float _get_rotation_deg() const; - void _ref_font(Ref<Font> p_sc); void _unref_font(Ref<Font> p_sc); void _font_changed(); @@ -273,10 +271,25 @@ public: }; - virtual Variant edit_get_state() const; - virtual void edit_set_state(const Variant &p_state); - virtual void edit_set_rect(const Rect2 &p_edit_rect); - virtual Size2 edit_get_minimum_size() const; + virtual Dictionary _edit_get_state() const; + virtual void _edit_set_state(const Dictionary &p_state); + + virtual void _edit_set_position(const Point2 &p_position); + virtual Point2 _edit_get_position() const; + + virtual void _edit_set_rect(const Rect2 &p_edit_rect); + virtual Rect2 _edit_get_rect() const; + virtual bool _edit_use_rect() const; + + virtual void _edit_set_rotation(float p_rotation); + virtual float _edit_get_rotation() const; + virtual bool _edit_use_rotation() const; + + virtual void _edit_set_pivot(const Point2 &p_pivot); + virtual Point2 _edit_get_pivot() const; + virtual bool _edit_use_pivot() const; + + virtual Size2 _edit_get_minimum_size() const; void accept_event(); @@ -332,9 +345,9 @@ public: Rect2 get_window_rect() const; ///< use with care, as it blocks waiting for the visual server void set_rotation(float p_radians); - void set_rotation_deg(float p_degrees); + void set_rotation_degrees(float p_degrees); float get_rotation() const; - float get_rotation_deg() const; + float get_rotation_degrees() const; void set_h_grow_direction(GrowDirection p_direction); GrowDirection get_h_grow_direction() const; @@ -378,6 +391,11 @@ public: void set_focus_neighbour(Margin p_margin, const NodePath &p_neighbour); NodePath get_focus_neighbour(Margin p_margin) const; + void set_focus_next(const NodePath &p_next); + NodePath get_focus_next() const; + void set_focus_previous(const NodePath &p_prev); + NodePath get_focus_previous() const; + Control *get_focus_owner() const; void set_mouse_filter(MouseFilter p_filter); @@ -424,7 +442,6 @@ public: CursorShape get_default_cursor_shape() const; virtual CursorShape get_cursor_shape(const Point2 &p_pos = Point2i()) const; - virtual Rect2 get_item_rect() const; virtual Transform2D get_transform() const; bool is_toplevel_control() const; diff --git a/scene/gui/item_list.cpp b/scene/gui/item_list.cpp index 623a110263..e9e9dcc859 100644 --- a/scene/gui/item_list.cpp +++ b/scene/gui/item_list.cpp @@ -754,7 +754,7 @@ void ItemList::_notification(int p_what) { int width = size.width - bg->get_minimum_size().width; if (scroll_bar->is_visible()) { - width -= mw + bg->get_margin(MARGIN_RIGHT); + width -= mw; } draw_style_box(bg, Rect2(Point2(), size)); @@ -1107,7 +1107,7 @@ void ItemList::_notification(int p_what) { } for (int i = 0; i < separators.size(); i++) { - draw_line(Vector2(bg->get_margin(MARGIN_LEFT), base_ofs.y + separators[i]), Vector2(size.width - bg->get_margin(MARGIN_RIGHT), base_ofs.y + separators[i]), guide_color); + draw_line(Vector2(bg->get_margin(MARGIN_LEFT), base_ofs.y + separators[i]), Vector2(width, base_ofs.y + separators[i]), guide_color); } } } diff --git a/scene/gui/line_edit.cpp b/scene/gui/line_edit.cpp index 40e2dba6c2..f7bf1cd9ea 100644 --- a/scene/gui/line_edit.cpp +++ b/scene/gui/line_edit.cpp @@ -48,7 +48,7 @@ void LineEdit::_gui_input(Ref<InputEvent> p_event) { if (b.is_valid()) { - if (b->is_pressed() && b->get_button_index() == BUTTON_RIGHT) { + 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->popup(); @@ -1286,6 +1286,14 @@ void LineEdit::menu_option(int p_option) { } } +void LineEdit::set_context_menu_enabled(bool p_enable) { + context_menu_enabled = p_enable; +} + +bool LineEdit::is_context_menu_enabled() { + return context_menu_enabled; +} + PopupMenu *LineEdit::get_menu() const { return menu; } @@ -1301,6 +1309,7 @@ void LineEdit::set_expand_to_text_length(bool p_enabled) { expand_to_text_length = p_enabled; minimum_size_changed(); + set_window_pos(0); } bool LineEdit::get_expand_to_text_length() const { @@ -1395,6 +1404,8 @@ void LineEdit::_bind_methods() { ClassDB::bind_method(D_METHOD("select", "from", "to"), &LineEdit::select, DEFVAL(0), DEFVAL(-1)); ClassDB::bind_method(D_METHOD("menu_option", "option"), &LineEdit::menu_option); ClassDB::bind_method(D_METHOD("get_menu"), &LineEdit::get_menu); + ClassDB::bind_method(D_METHOD("set_context_menu_enabled", "enable"), &LineEdit::set_context_menu_enabled); + ClassDB::bind_method(D_METHOD("is_context_menu_enabled"), &LineEdit::is_context_menu_enabled); ADD_SIGNAL(MethodInfo("text_changed", PropertyInfo(Variant::STRING, "text"))); ADD_SIGNAL(MethodInfo("text_entered", PropertyInfo(Variant::STRING, "text"))); @@ -1418,7 +1429,7 @@ void LineEdit::_bind_methods() { ADD_PROPERTYNZ(PropertyInfo(Variant::INT, "max_length"), "set_max_length", "get_max_length"); ADD_PROPERTYNO(PropertyInfo(Variant::BOOL, "editable"), "set_editable", "is_editable"); ADD_PROPERTYNZ(PropertyInfo(Variant::BOOL, "secret"), "set_secret", "is_secret"); - ADD_PROPERTYNO(PropertyInfo(Variant::BOOL, "expand_to_len"), "set_expand_to_text_length", "get_expand_to_text_length"); + ADD_PROPERTYNZ(PropertyInfo(Variant::BOOL, "expand_to_text_length"), "set_expand_to_text_length", "get_expand_to_text_length"); ADD_PROPERTY(PropertyInfo(Variant::INT, "focus_mode", PROPERTY_HINT_ENUM, "None,Click,All"), "set_focus_mode", "get_focus_mode"); ADD_GROUP("Placeholder", "placeholder_"); ADD_PROPERTYNZ(PropertyInfo(Variant::STRING, "placeholder_text"), "set_placeholder", "get_placeholder"); @@ -1426,6 +1437,7 @@ void LineEdit::_bind_methods() { ADD_GROUP("Caret", "caret_"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "caret_blink"), "cursor_set_blink_enabled", "cursor_get_blink_enabled"); ADD_PROPERTYNZ(PropertyInfo(Variant::REAL, "caret_blink_speed", PROPERTY_HINT_RANGE, "0.1,10,0.1"), "cursor_set_blink_speed", "cursor_get_blink_speed"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "context_menu_enabled"), "set_context_menu_enabled", "is_context_menu_enabled"); } LineEdit::LineEdit() { @@ -1455,6 +1467,7 @@ LineEdit::LineEdit() { caret_blink_timer->connect("timeout", this, "_toggle_draw_caret"); cursor_set_blink_enabled(false); + context_menu_enabled = true; menu = memnew(PopupMenu); add_child(menu); menu->add_item(TTR("Cut"), MENU_CUT, KEY_MASK_CMD | KEY_X); diff --git a/scene/gui/line_edit.h b/scene/gui/line_edit.h index bece29a37d..c3a299c2f5 100644 --- a/scene/gui/line_edit.h +++ b/scene/gui/line_edit.h @@ -74,6 +74,7 @@ private: String ime_text; Point2 ime_selection; + bool context_menu_enabled; PopupMenu *menu; int cursor_pos; @@ -150,6 +151,8 @@ public: virtual void drop_data(const Point2 &p_point, const Variant &p_data); void menu_option(int p_option); + void set_context_menu_enabled(bool p_enable); + bool is_context_menu_enabled(); PopupMenu *get_menu() const; void select_all(); diff --git a/scene/gui/scroll_container.cpp b/scene/gui/scroll_container.cpp index 1ad1e3f638..9022d67a4a 100644 --- a/scene/gui/scroll_container.cpp +++ b/scene/gui/scroll_container.cpp @@ -347,12 +347,11 @@ void ScrollContainer::update_scrollbars() { } else { v_scroll->show(); + v_scroll->set_max(min.height); + v_scroll->set_page(size.height - hmin.height); scroll.y = v_scroll->get_value(); } - v_scroll->set_max(min.height); - v_scroll->set_page(size.height - hmin.height); - if (!scroll_h || min.width <= size.width - vmin.width) { h_scroll->hide(); diff --git a/scene/gui/text_edit.cpp b/scene/gui/text_edit.cpp index 7d200a799b..c9af7eed0d 100644 --- a/scene/gui/text_edit.cpp +++ b/scene/gui/text_edit.cpp @@ -274,6 +274,7 @@ void TextEdit::Text::insert(int p_at, const String &p_text) { Line line; line.marked = false; line.breakpoint = false; + line.hidden = false; line.width_cache = -1; line.data = p_text; text.insert(p_at, line); @@ -297,9 +298,11 @@ void TextEdit::_update_scrollbars() { int hscroll_rows = ((hmin.height - 1) / get_row_height()) + 1; int visible_rows = get_visible_rows(); - int total_rows = text.size(); + int num_rows = MAX(visible_rows, num_lines_from(CLAMP(cursor.line_ofs, 0, text.size() - 1), MIN(visible_rows, text.size() - 1 - cursor.line_ofs))); + + int total_rows = (is_hiding_enabled() ? get_total_unhidden_rows() : text.size()); if (scroll_past_end_of_file_enabled) { - total_rows += get_visible_rows() - 1; + total_rows += visible_rows - 1; } int vscroll_pixels = v_scroll->get_combined_minimum_size().width; @@ -313,6 +316,10 @@ void TextEdit::_update_scrollbars() { total_width += cache.breakpoint_gutter_width; } + if (draw_fold_gutter) { + total_width += cache.fold_gutter_width; + } + bool use_hscroll = true; bool use_vscroll = true; @@ -347,12 +354,11 @@ void TextEdit::_update_scrollbars() { v_scroll->set_step(1); } - if (fabs(v_scroll->get_value() - (double)cursor.line_ofs) >= 1) { - v_scroll->set_value(cursor.line_ofs); - } - + update_line_scroll_pos(); } else { cursor.line_ofs = 0; + line_scroll_pos = 0; + v_scroll->set_value(0); v_scroll->hide(); } @@ -551,6 +557,13 @@ void TextEdit::_notification(int p_what) { cache.breakpoint_gutter_width = 0; } + if (draw_fold_gutter) { + fold_gutter_width = (get_row_height() * 55) / 100; + cache.fold_gutter_width = fold_gutter_width; + } else { + cache.fold_gutter_width = 0; + } + int line_number_char_count = 0; { @@ -573,7 +586,7 @@ 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; + 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 = cache.size.width - cache.style_normal->get_margin(MARGIN_RIGHT); //let's do it easy for now: cache.style_normal->draw(ci, Rect2(Point2(), cache.size)); @@ -780,10 +793,22 @@ void TextEdit::_notification(int p_what) { String highlighted_text = get_selection_text(); String line_num_padding = line_numbers_zero_padded ? "0" : " "; + update_line_scroll_pos(); + int line = cursor.line_ofs - 1; for (int i = 0; i < visible_rows; i++) { - int line = i + cursor.line_ofs; + line++; + + if (line < 0 || line >= (int)text.size()) + continue; + + while (is_line_hidden(line)) { + line++; + if (line < 0 || line >= (int)text.size()) { + break; + } + } if (line < 0 || line >= (int)text.size()) continue; @@ -794,7 +819,7 @@ void TextEdit::_notification(int p_what) { int char_ofs = 0; int ofs_y = (i * get_row_height() + cache.line_spacing / 2); if (smooth_scroll_enabled) { - ofs_y -= (v_scroll->get_value() - cursor.line_ofs) * get_row_height(); + ofs_y -= (v_scroll->get_value() - get_line_scroll_pos()) * get_row_height(); } bool prev_is_char = false; @@ -857,6 +882,21 @@ void TextEdit::_notification(int p_what) { } } + // 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; + if (is_folded(line)) { + int xofs = horizontal_gap - (cache.can_fold_icon->get_width()) / 2; + int yofs = (get_row_height() - cache.folded_icon->get_height()) / 2; + cache.folded_icon->draw(ci, Point2(gutter_left + xofs, ofs_y + yofs), Color(0.8f, 0.8f, 0.8f, 0.8f)); + } else if (can_fold(line)) { + int xofs = -cache.can_fold_icon->get_width() / 2 - horizontal_gap + 3; + int yofs = (get_row_height() - cache.can_fold_icon->get_height()) / 2; + cache.can_fold_icon->draw(ci, Point2(gutter_left + xofs, ofs_y + yofs), Color(0.8f, 0.8f, 0.8f, 0.8f)); + } + } + if (cache.line_number_w) { String fc = String::num(line + 1); while (fc.length() < line_number_char_count) { @@ -894,17 +934,18 @@ void TextEdit::_notification(int p_what) { is_hex_notation = false; } - // check for dot or 'x' for hex notation in floating point number - if ((str[j] == '.' || str[j] == 'x') && !in_word && prev_is_number && !is_number) { + // check for dot or underscore or 'x' for hex notation in floating point number + if ((str[j] == '.' || str[j] == 'x' || str[j] == '_') && !in_word && prev_is_number && !is_number) { is_number = true; is_symbol = false; + is_char = false; if (str[j] == 'x' && str[j - 1] == '0') { is_hex_notation = true; } } - if (!in_word && _is_char(str[j])) { + if (!in_word && _is_char(str[j]) && !is_number) { in_word = true; } @@ -1195,6 +1236,10 @@ void TextEdit::_notification(int p_what) { } char_ofs += char_w; + + if (j == str.length() - 1 && is_folded(line)) { + cache.folded_eol_icon->draw(ci, Point2(char_ofs + char_margin, ofs_y), Color(1, 1, 1, 1), true); + } } if (cursor.column == str.length() && cursor.line == line && (char_ofs + char_margin) >= xmargin_beg) { @@ -1537,6 +1582,12 @@ void TextEdit::backspace_at_cursor() { int prev_line = cursor.column ? cursor.line : cursor.line - 1; int prev_column = cursor.column ? (cursor.column - 1) : (text[cursor.line - 1].length()); + + if (is_line_hidden(cursor.line)) + set_line_as_hidden(prev_line, true); + if (is_line_set_as_breakpoint(cursor.line)) + set_line_as_breakpoint(prev_line, true); + if (auto_brace_completion_enabled && cursor.column > 0 && _is_pair_left_symbol(text[cursor.line][cursor.column - 1])) { @@ -1576,7 +1627,7 @@ void TextEdit::backspace_at_cursor() { } } - cursor_set_line(prev_line); + cursor_set_line(prev_line, true, true); cursor_set_column(prev_column); } @@ -1650,10 +1701,18 @@ void TextEdit::_get_mouse_pos(const Point2i &p_mouse, int &r_row, int &r_col) co float rows = p_mouse.y; rows -= cache.style_normal->get_margin(MARGIN_TOP); rows /= get_row_height(); - int row = cursor.line_ofs + (rows + (v_scroll->get_value() - cursor.line_ofs)); + int lsp = get_line_scroll_pos(true); + int row = cursor.line_ofs + (rows + (v_scroll->get_value() - lsp)); + + if (is_hiding_enabled()) { + // row will be offset by the hidden rows + int f_ofs = num_lines_from(CLAMP(cursor.line_ofs, 0, text.size() - 1), MIN(rows + 1, text.size() - cursor.line_ofs)) - 1; + row = cursor.line_ofs + (f_ofs + (v_scroll->get_value() - lsp)); + row = CLAMP(row, 0, text.size() - num_lines_from(text.size() - 1, -1)); + } if (row < 0) - row = 0; + row = 0; //todo int col = 0; @@ -1663,7 +1722,7 @@ void TextEdit::_get_mouse_pos(const Point2i &p_mouse, int &r_row, int &r_col) co col = text[row].size(); } else { - col = p_mouse.x - (cache.style_normal->get_margin(MARGIN_LEFT) + cache.line_number_w + cache.breakpoint_gutter_width); + col = p_mouse.x - (cache.style_normal->get_margin(MARGIN_LEFT) + cache.line_number_w + cache.breakpoint_gutter_width + cache.fold_gutter_width); col += cursor.x_ofs; col = get_char_pos_for(col, get_line(row)); } @@ -1716,10 +1775,11 @@ void TextEdit::_gui_input(const Ref<InputEvent> &p_gui_input) { if (mb->is_pressed()) { if (mb->get_button_index() == BUTTON_WHEEL_UP && !mb->get_command()) { + float scroll_factor = 3 * mb->get_factor(); if (scrolling) { - target_v_scroll = (target_v_scroll - (3 * mb->get_factor())); + target_v_scroll = (target_v_scroll - scroll_factor); } else { - target_v_scroll = (v_scroll->get_value() - (3 * mb->get_factor())); + target_v_scroll = (v_scroll->get_value() - scroll_factor); } if (smooth_scroll_enabled) { @@ -1733,16 +1793,18 @@ void TextEdit::_gui_input(const Ref<InputEvent> &p_gui_input) { } } if (mb->get_button_index() == BUTTON_WHEEL_DOWN && !mb->get_command()) { + float scroll_factor = 3 * mb->get_factor(); if (scrolling) { - target_v_scroll = (target_v_scroll + (3 * mb->get_factor())); + target_v_scroll = (target_v_scroll + scroll_factor); } else { - target_v_scroll = (v_scroll->get_value() + (3 * mb->get_factor())); + target_v_scroll = (v_scroll->get_value() + scroll_factor); } if (smooth_scroll_enabled) { - int max_v_scroll = get_line_count() - 1; + int max_v_scroll = get_total_unhidden_rows(); if (!scroll_past_end_of_file_enabled) { - max_v_scroll -= get_visible_rows() - 1; + max_v_scroll -= get_visible_rows(); + max_v_scroll = CLAMP(max_v_scroll, 0, get_total_unhidden_rows()); } if (target_v_scroll > max_v_scroll) { @@ -1765,6 +1827,7 @@ void TextEdit::_gui_input(const Ref<InputEvent> &p_gui_input) { _reset_caret_blink_timer(); int row, col; + update_line_scroll_pos(); _get_mouse_pos(Point2i(mb->get_position().x, mb->get_position().y), row, col); if (mb->get_command() && highlighted_word != String()) { @@ -1783,10 +1846,35 @@ void TextEdit::_gui_input(const Ref<InputEvent> &p_gui_input) { } } + // 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; + if (mb->get_position().x > gutter_left - 6 && mb->get_position().x <= gutter_left + cache.fold_gutter_width - 3) { + if (is_folded(row)) { + unfold_line(row); + } else if (can_fold(row)) { + fold_line(row); + } + return; + } + } + + // 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; + 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; + } + } + int prev_col = cursor.column; int prev_line = cursor.line; - cursor_set_line(row); + cursor_set_line(row, true, false); cursor_set_column(col); if (mb->get_shift() && (cursor.column != prev_col || cursor.line != prev_line)) { @@ -2025,17 +2113,6 @@ void TextEdit::_gui_input(const Ref<InputEvent> &p_gui_input) { return; } - if (k->get_scancode() == KEY_DOWN) { - - if (completion_index < completion_options.size() - 1) { - completion_index++; - completion_current = completion_options[completion_index]; - update(); - } - accept_event(); - return; - } - if (k->get_scancode() == KEY_KP_ENTER || k->get_scancode() == KEY_ENTER || k->get_scancode() == KEY_TAB) { _confirm_completion(); @@ -2189,7 +2266,7 @@ void TextEdit::_gui_input(const Ref<InputEvent> &p_gui_input) { selection.active = false; update(); _remove_text(selection.from_line, selection.from_column, selection.to_line, selection.to_column); - cursor_set_line(selection.from_line); + cursor_set_line(selection.from_line, true, false); cursor_set_column(selection.from_column); update(); } @@ -2239,6 +2316,9 @@ void TextEdit::_gui_input(const Ref<InputEvent> &p_gui_input) { } } + if (is_folded(cursor.line)) + unfold_line(cursor.line); + bool brace_indent = false; // no need to indent if we are going upwards. @@ -2389,6 +2469,8 @@ void TextEdit::_gui_input(const Ref<InputEvent> &p_gui_input) { cursor_set_column(column); } else { + if (cursor.line > 0 && is_line_hidden(cursor.line - 1)) + unfold_line(cursor.line - 1); backspace_at_cursor(); } @@ -2447,7 +2529,7 @@ void TextEdit::_gui_input(const Ref<InputEvent> &p_gui_input) { } else if (cursor.column == 0) { if (cursor.line > 0) { - cursor_set_line(cursor.line - 1); + cursor_set_line(cursor.line - num_lines_from(CLAMP(cursor.line - 1, 0, text.size() - 1), -1)); cursor_set_column(text[cursor.line].length()); } } else { @@ -2510,7 +2592,7 @@ void TextEdit::_gui_input(const Ref<InputEvent> &p_gui_input) { } else if (cursor.column == text[cursor.line].length()) { if (cursor.line < text.size() - 1) { - cursor_set_line(cursor.line + 1); + cursor_set_line(cursor_get_line() + num_lines_from(CLAMP(cursor.line + 1, 0, text.size() - 1), 1), true, false); cursor_set_column(0); } } else { @@ -2551,7 +2633,7 @@ void TextEdit::_gui_input(const Ref<InputEvent> &p_gui_input) { cursor_set_line(0); else #endif - cursor_set_line(cursor_get_line() - 1); + cursor_set_line(cursor_get_line() - num_lines_from(CLAMP(cursor.line - 1, 0, text.size() - 1), -1)); if (k->get_shift()) _post_shift_selection(); @@ -2585,10 +2667,10 @@ void TextEdit::_gui_input(const Ref<InputEvent> &p_gui_input) { } if (k->get_command()) - cursor_set_line(text.size() - 1); + cursor_set_line(text.size() - 1, true, false); else #endif - cursor_set_line(cursor_get_line() + 1); + cursor_set_line(cursor_get_line() + num_lines_from(CLAMP(cursor.line + 1, 0, text.size() - 1), 1), true, false); if (k->get_shift()) _post_shift_selection(); @@ -2735,7 +2817,7 @@ void TextEdit::_gui_input(const Ref<InputEvent> &p_gui_input) { if (k->get_shift()) _pre_shift_selection(); - cursor_set_line(text.size() - 1); + cursor_set_line(text.size() - 1, true, false); if (k->get_shift()) _post_shift_selection(); @@ -2750,7 +2832,7 @@ void TextEdit::_gui_input(const Ref<InputEvent> &p_gui_input) { _pre_shift_selection(); if (k->get_command()) - cursor_set_line(text.size() - 1); + cursor_set_line(text.size() - 1, true, false); cursor_set_column(text[cursor.line].length()); if (k->get_shift()) @@ -2775,7 +2857,7 @@ void TextEdit::_gui_input(const Ref<InputEvent> &p_gui_input) { if (k->get_shift()) _pre_shift_selection(); - cursor_set_line(cursor_get_line() - get_visible_rows()); + cursor_set_line(cursor_get_line() - num_lines_from(cursor.line, -get_visible_rows()), true, false); if (k->get_shift()) _post_shift_selection(); @@ -2796,7 +2878,7 @@ void TextEdit::_gui_input(const Ref<InputEvent> &p_gui_input) { if (k->get_shift()) _pre_shift_selection(); - cursor_set_line(cursor_get_line() + get_visible_rows()); + cursor_set_line(cursor_get_line() + num_lines_from(cursor.line, get_visible_rows()), true, false); if (k->get_shift()) _post_shift_selection(); @@ -3014,8 +3096,9 @@ void TextEdit::_scroll_lines_up() { } // adjust the cursor - if (cursor_get_line() >= (get_visible_rows() + get_v_scroll()) && !selection.active) { - cursor_set_line((get_visible_rows() + get_v_scroll()) - 1, false); + int num_lines = num_lines_from(CLAMP(cursor.line_ofs, 0, text.size() - 1), get_visible_rows()) - 1; + if (cursor.line >= cursor.line_ofs + num_lines && !selection.active) { + cursor_set_line(cursor.line_ofs + num_lines, false, false); } } @@ -3023,9 +3106,10 @@ void TextEdit::_scroll_lines_down() { scrolling = false; // calculate the maximum vertical scroll position - int max_v_scroll = get_line_count() - 1; + int max_v_scroll = get_total_unhidden_rows(); if (!scroll_past_end_of_file_enabled) { - max_v_scroll -= get_visible_rows() - 1; + max_v_scroll -= get_visible_rows(); + max_v_scroll = CLAMP(max_v_scroll, 0, get_total_unhidden_rows()); } // adjust the vertical scroll @@ -3034,8 +3118,8 @@ void TextEdit::_scroll_lines_down() { } // adjust the cursor - if ((cursor_get_line()) <= get_v_scroll() - 1 && !selection.active) { - cursor_set_line(get_v_scroll(), false); + if (cursor.line <= cursor.line_ofs - 1 && !selection.active) { + cursor_set_line(cursor.line_ofs, false, false); } } @@ -3080,6 +3164,15 @@ void TextEdit::_base_insert_text(int p_line, int p_char, const String &p_text, i } } + // if we are just making a new empty line, reset breakpoints and hidden status + if (p_char == 0 && p_text.replace("\r", "") == "\n") { + + text.set_breakpoint(p_line + 1, text.is_breakpoint(p_line)); + text.set_hidden(p_line + 1, text.is_hidden(p_line)); + text.set_breakpoint(p_line, false); + text.set_hidden(p_line, false); + } + r_end_line = p_line + substrings.size() - 1; r_end_column = text[r_end_line].length() - postinsert_text.length(); @@ -3278,6 +3371,7 @@ Size2 TextEdit::get_minimum_size() const { return cache.style_normal->get_minimum_size(); } + int TextEdit::get_visible_rows() const { int total = cache.size.height; @@ -3285,31 +3379,81 @@ int TextEdit::get_visible_rows() const { total /= get_row_height(); return total; } + +int TextEdit::get_total_unhidden_rows() const { + if (!is_hiding_enabled()) + return text.size(); + + int total_unhidden = 0; + for (int i = 0; i < text.size(); i++) { + if (!text.is_hidden(i)) + total_unhidden++; + } + return total_unhidden; +} + +double TextEdit::get_line_scroll_pos(bool p_recalculate) const { + + if (!is_hiding_enabled()) + return cursor.line_ofs; + if (!p_recalculate) + return line_scroll_pos; + + // count num unhidden lines to the cursor line ofs + double new_line_scroll_pos = 0; + int to = CLAMP(cursor.line_ofs, 0, text.size() - 1); + for (int i = 0; i < to; i++) { + if (!text.is_hidden(i)) + new_line_scroll_pos++; + } + return new_line_scroll_pos; +} + +void TextEdit::update_line_scroll_pos() { + + if (!is_hiding_enabled()) { + line_scroll_pos = cursor.line_ofs; + return; + } + + // count num unhidden lines to the cursor line ofs + double new_line_scroll_pos = 0; + int to = CLAMP(cursor.line_ofs, 0, text.size() - 1); + for (int i = 0; i < to; i++) { + if (!text.is_hidden(i)) + new_line_scroll_pos++; + } + line_scroll_pos = new_line_scroll_pos; +} + void TextEdit::adjust_viewport_to_cursor() { scrolling = false; - if (cursor.line_ofs > cursor.line) + if (cursor.line_ofs > cursor.line) { cursor.line_ofs = cursor.line; + } - int visible_width = cache.size.width - cache.style_normal->get_minimum_size().width - cache.line_number_w - cache.breakpoint_gutter_width; + int visible_width = cache.size.width - cache.style_normal->get_minimum_size().width - cache.line_number_w - cache.breakpoint_gutter_width - cache.fold_gutter_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 - //printf("rowofs %i, visrows %i, cursor.line %i\n",cursor.line_ofs,get_visible_rows(),cursor.line); - int visible_rows = get_visible_rows(); if (h_scroll->is_visible_in_tree()) visible_rows -= ((h_scroll->get_combined_minimum_size().height - 1) / get_row_height()); + int num_rows = num_lines_from(CLAMP(cursor.line_ofs, 0, text.size() - 1), MIN(visible_rows, text.size() - 1 - cursor.line_ofs)); - if (cursor.line >= (cursor.line_ofs + visible_rows)) - cursor.line_ofs = cursor.line - visible_rows; - if (cursor.line < cursor.line_ofs) + // if the cursor is off the screen + if (cursor.line >= (cursor.line_ofs + MAX(num_rows, visible_rows))) { + cursor.line_ofs = cursor.line - (num_lines_from(CLAMP(cursor.line, 0, text.size() - 1), -visible_rows) - 1); + } + if (cursor.line < cursor.line_ofs) { cursor.line_ofs = cursor.line; + } - if (cursor.line_ofs + visible_rows > text.size() && !scroll_past_end_of_file_enabled) { - cursor.line_ofs = text.size() - visible_rows; - v_scroll->set_value(text.size() - visible_rows); + // fixes deleting lines from moving the line ofs in a bad way + if (!scroll_past_end_of_file_enabled && get_total_unhidden_rows() > visible_rows && num_rows < visible_rows) { + cursor.line_ofs = text.size() - 1 - (num_lines_from(text.size() - 1, -visible_rows) - 1); } int cursor_x = get_column_x_offset(cursor.column, text[cursor.line]); @@ -3320,6 +3464,8 @@ void TextEdit::adjust_viewport_to_cursor() { if (cursor_x < cursor.x_ofs) cursor.x_ofs = cursor_x; + update_line_scroll_pos(); + v_scroll->set_value(get_line_scroll_pos() + 1); update(); /* get_range()->set_max(text.size()); @@ -3336,7 +3482,10 @@ void TextEdit::center_viewport_to_cursor() { if (cursor.line_ofs > cursor.line) cursor.line_ofs = cursor.line; - int visible_width = cache.size.width - cache.style_normal->get_minimum_size().width - cache.line_number_w - cache.breakpoint_gutter_width; + if (is_line_hidden(cursor.line)) + unfold_line(cursor.line); + + int visible_width = cache.size.width - cache.style_normal->get_minimum_size().width - cache.line_number_w - cache.breakpoint_gutter_width - cache.fold_gutter_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 @@ -3345,9 +3494,8 @@ void TextEdit::center_viewport_to_cursor() { if (h_scroll->is_visible_in_tree()) visible_rows -= ((h_scroll->get_combined_minimum_size().height - 1) / get_row_height()); - int max_ofs = text.size() - (scroll_past_end_of_file_enabled ? 1 : visible_rows); - cursor.line_ofs = CLAMP(cursor.line - (visible_rows / 2), 0, max_ofs); - + int max_ofs = text.size() - (scroll_past_end_of_file_enabled ? 1 : num_lines_from(text.size() - 1, -visible_rows)); + cursor.line_ofs = CLAMP(cursor.line - num_lines_from(cursor.line - visible_rows / 2, -visible_rows / 2), 0, max_ofs); int cursor_x = get_column_x_offset(cursor.column, text[cursor.line]); if (cursor_x > (cursor.x_ofs + visible_width)) @@ -3356,6 +3504,9 @@ void TextEdit::center_viewport_to_cursor() { if (cursor_x < cursor.x_ofs) cursor.x_ofs = cursor_x; + update_line_scroll_pos(); + v_scroll->set_value(get_line_scroll_pos()); + update(); } @@ -3380,7 +3531,7 @@ void TextEdit::cursor_set_column(int p_col, bool p_adjust_viewport) { } } -void TextEdit::cursor_set_line(int p_row, bool p_adjust_viewport) { +void TextEdit::cursor_set_line(int p_row, bool p_adjust_viewport, bool p_can_be_hidden) { if (setting_row) return; @@ -3392,6 +3543,21 @@ void TextEdit::cursor_set_line(int p_row, bool p_adjust_viewport) { if (p_row >= (int)text.size()) p_row = (int)text.size() - 1; + if (!p_can_be_hidden) { + if (is_line_hidden(CLAMP(p_row, 0, text.size() - 1))) { + int move_down = num_lines_from(p_row, 1) - 1; + if (p_row + move_down <= text.size() - 1 && !is_line_hidden(p_row + move_down)) { + p_row += move_down; + } else { + int move_up = num_lines_from(p_row, -1) - 1; + if (p_row - move_up > 0 && !is_line_hidden(p_row - move_up)) { + p_row -= move_up; + } else { + WARN_PRINTS(("Cursor set to hidden line " + itos(p_row) + " and there are no nonhidden lines.")); + } + } + } + } cursor.line = p_row; cursor.column = get_char_pos_for(cursor.last_fit_x, get_line(cursor.line)); @@ -3461,8 +3627,11 @@ void TextEdit::_scroll_moved(double p_to_val) { if (h_scroll->is_visible_in_tree()) cursor.x_ofs = h_scroll->get_value(); - if (v_scroll->is_visible_in_tree()) - cursor.line_ofs = v_scroll->get_value(); + if (v_scroll->is_visible_in_tree()) { + double val = v_scroll->get_value(); + cursor.line_ofs = num_lines_from(0, (int)floor(val)) - 1; + line_scroll_pos = (int)floor(val); + } update(); } @@ -3551,10 +3720,43 @@ 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; - if ((completion_active && completion_rect.has_point(p_pos)) || p_pos.x < gutter) { + int gutter = cache.style_normal->get_margin(MARGIN_LEFT) + cache.line_number_w + cache.breakpoint_gutter_width + cache.fold_gutter_width; + if ((completion_active && completion_rect.has_point(p_pos))) { return CURSOR_ARROW; } + if (p_pos.x < gutter) { + + int row, col; + _get_mouse_pos(p_pos, row, col); + int left_margin = cache.style_normal->get_margin(MARGIN_LEFT); + + // breakpoint icon + if (draw_breakpoint_gutter && p_pos.x > left_margin && 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) { + if (is_folded(row) || can_fold(row)) + return CURSOR_POINTING_HAND; + else + return CURSOR_ARROW; + } + return CURSOR_ARROW; + } else { + int row, col; + _get_mouse_pos(p_pos, row, col); + // eol fold icon + if (is_folded(row)) { + int line_width = text.get_line_width(row); + line_width += cache.style_normal->get_margin(MARGIN_LEFT) + cache.line_number_w + cache.breakpoint_gutter_width + cache.fold_gutter_width - 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; + } + } + } + return CURSOR_IBEAM; } @@ -3568,6 +3770,7 @@ void TextEdit::set_text(String p_text) { cursor.line = 0; cursor.x_ofs = 0; cursor.line_ofs = 0; + line_scroll_pos = 0; cursor.last_fit_x = 0; cursor_set_line(0); cursor_set_column(0); @@ -3653,6 +3856,7 @@ void TextEdit::_clear() { cursor.line = 0; cursor.x_ofs = 0; cursor.line_ofs = 0; + line_scroll_pos = 0; cursor.last_fit_x = 0; } @@ -3668,6 +3872,11 @@ void TextEdit::set_readonly(bool p_readonly) { readonly = p_readonly; } +bool TextEdit::is_readonly() const { + + return readonly; +} + void TextEdit::set_wrap(bool p_wrap) { wrap = p_wrap; @@ -3726,6 +3935,9 @@ void TextEdit::_update_caches() { cache.line_spacing = get_constant("line_spacing"); 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.folded_eol_icon = get_icon("GuiTabMenu", "EditorIcons"); text.set_font(cache.font); } @@ -3900,7 +4112,12 @@ void TextEdit::select(int p_from_line, int p_from_column, int p_to_line, int p_t update(); } - +void TextEdit::swap_lines(int line1, int line2) { + String tmp = get_line(line1); + String tmp2 = get_line(line2); + set_line(line2, tmp); + set_line(line1, tmp2); +} bool TextEdit::is_selection_active() const { return selection.active; @@ -4167,6 +4384,198 @@ void TextEdit::get_breakpoints(List<int> *p_breakpoints) const { } } +void TextEdit::set_line_as_hidden(int p_line, bool p_hidden) { + + ERR_FAIL_INDEX(p_line, text.size()); + if (is_hiding_enabled() || !p_hidden) + text.set_hidden(p_line, p_hidden); + update(); +} + +bool TextEdit::is_line_hidden(int p_line) const { + + ERR_FAIL_INDEX_V(p_line, text.size(), false); + return text.is_hidden(p_line); +} + +void TextEdit::fold_all_lines() { + + for (int i = 0; i < text.size(); i++) { + fold_line(i); + } + _update_scrollbars(); + update(); +} + +void TextEdit::unhide_all_lines() { + + for (int i = 0; i < text.size(); i++) { + text.set_hidden(i, false); + } + _update_scrollbars(); + update(); +} + +int TextEdit::num_lines_from(int p_line_from, int unhidden_amount) const { + + // returns the number of hidden and unhidden lines from p_line_from to p_line_from + amount of visible lines + ERR_FAIL_INDEX_V(p_line_from, text.size(), ABS(unhidden_amount)); + + if (!is_hiding_enabled()) + return unhidden_amount; + int num_visible = 0; + int num_total = 0; + if (unhidden_amount >= 0) { + for (int i = p_line_from; i < text.size(); i++) { + num_total++; + if (!is_line_hidden(i)) + num_visible++; + if (num_visible >= unhidden_amount) + break; + } + } else { + unhidden_amount = ABS(unhidden_amount); + for (int i = p_line_from; i >= 0; i--) { + num_total++; + if (!is_line_hidden(i)) + num_visible++; + if (num_visible >= unhidden_amount) + break; + } + } + return num_total; +} + +int TextEdit::get_whitespace_level(int p_line) const { + + ERR_FAIL_INDEX_V(p_line, text.size(), 0); + + // counts number of tabs and spaces before line starts + int whitespace_count = 0; + int line_length = text[p_line].size(); + for (int i = 0; i < line_length - 1; i++) { + if (text[p_line][i] == '\t') { + whitespace_count++; + } else if (text[p_line][i] == ' ') { + whitespace_count++; + } else if (text[p_line][i] == '#') { + break; + } else { + break; + } + } + return whitespace_count; +} + +bool TextEdit::can_fold(int p_line) const { + + ERR_FAIL_INDEX_V(p_line, text.size(), false); + if (!is_hiding_enabled()) + return false; + if (p_line + 1 >= text.size()) + return false; + if (text[p_line].size() == 0) + return false; + if (is_folded(p_line)) + return false; + if (is_line_hidden(p_line)) + return false; + + int start_indent = get_whitespace_level(p_line); + + for (int i = p_line + 1; i < text.size(); i++) { + if (text[i].size() == 0) + continue; + int next_indent = get_whitespace_level(i); + if (next_indent > start_indent) + return true; + else + return false; + } + + return false; +} + +bool TextEdit::is_folded(int p_line) const { + + ERR_FAIL_INDEX_V(p_line, text.size(), false); + if (p_line + 1 >= text.size() - 1) + return false; + if (!is_line_hidden(p_line) && is_line_hidden(p_line + 1)) + return true; + return false; +} + +void TextEdit::fold_line(int p_line) { + + ERR_FAIL_INDEX(p_line, text.size()); + if (!is_hiding_enabled()) + return; + if (!can_fold(p_line)) + return; + + // hide lines below this one + int start_indent = get_whitespace_level(p_line); + for (int i = p_line + 1; i < text.size(); i++) { + int cur_indent = get_whitespace_level(i); + if (text[i].size() == 0 || cur_indent > start_indent) { + set_line_as_hidden(i, true); + } else { + // exclude trailing empty lines + for (int trail_i = i - 1; trail_i > p_line; trail_i--) { + if (text[trail_i].size() == 0) + set_line_as_hidden(trail_i, false); + else + break; + } + break; + } + } + + // fix selection + if (is_selection_active()) { + if (is_line_hidden(selection.from_line) && is_line_hidden(selection.to_line)) { + deselect(); + } else if (is_line_hidden(selection.from_line)) { + select(p_line, 9999, selection.to_line, selection.to_column); + } else if (is_line_hidden(selection.to_line)) { + select(selection.from_line, selection.from_column, p_line, 9999); + } + } + + // reset cursor + if (is_line_hidden(cursor.line)) { + cursor_set_line(p_line, false, false); + cursor_set_column(get_line(p_line).length(), false); + } + _update_scrollbars(); + update(); +} + +void TextEdit::unfold_line(int p_line) { + + ERR_FAIL_INDEX(p_line, text.size()); + + if (!is_folded(p_line) && !is_line_hidden(p_line)) + return; + int fold_start = p_line; + for (fold_start = p_line; fold_start > 0; fold_start--) { + if (is_folded(fold_start)) + break; + } + fold_start = is_folded(fold_start) ? fold_start : p_line; + + for (int i = fold_start + 1; i < text.size(); i++) { + if (is_line_hidden(i)) { + set_line_as_hidden(i, false); + } else { + break; + } + } + _update_scrollbars(); + update(); +} + int TextEdit::get_line_count() const { return text.size(); @@ -4393,12 +4802,14 @@ void TextEdit::set_v_scroll(int p_scroll) { p_scroll = 0; } if (!scroll_past_end_of_file_enabled) { - if (p_scroll + get_visible_rows() > get_line_count()) { - p_scroll = get_line_count() - get_visible_rows(); + if (p_scroll + get_visible_rows() > get_total_unhidden_rows()) { + int num_rows = num_lines_from(CLAMP(p_scroll, 0, text.size() - 1), MIN(get_visible_rows(), text.size() - 1 - p_scroll)); + p_scroll = text.size() - num_rows; } } v_scroll->set_value(p_scroll); - cursor.line_ofs = p_scroll; + cursor.line_ofs = num_lines_from(0, p_scroll); + line_scroll_pos = p_scroll; } int TextEdit::get_h_scroll() const { @@ -4760,7 +5171,7 @@ 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); + cursor_set_line(at, false, true); _insert_text(at, 0, p_text + "\n"); } @@ -4808,6 +5219,35 @@ int TextEdit::get_breakpoint_gutter_width() const { return cache.breakpoint_gutter_width; } +void TextEdit::set_draw_fold_gutter(bool p_draw) { + draw_fold_gutter = p_draw; + update(); +} + +bool TextEdit::is_drawing_fold_gutter() const { + return draw_fold_gutter; +} + +void TextEdit::set_fold_gutter_width(int p_gutter_width) { + fold_gutter_width = p_gutter_width; + update(); +} + +int TextEdit::get_fold_gutter_width() const { + return cache.fold_gutter_width; +} + +void TextEdit::set_hiding_enabled(int p_enabled) { + if (!p_enabled) + unhide_all_lines(); + hiding_enabled = p_enabled; + update(); +} + +int TextEdit::is_hiding_enabled() const { + return hiding_enabled; +} + void TextEdit::set_highlight_current_line(bool p_enabled) { highlight_current_line = p_enabled; update(); @@ -4866,6 +5306,10 @@ void TextEdit::set_context_menu_enabled(bool p_enable) { context_menu_enabled = p_enable; } +bool TextEdit::is_context_menu_enabled() { + return context_menu_enabled; +} + PopupMenu *TextEdit::get_menu() const { return menu; } @@ -4898,7 +5342,7 @@ void TextEdit::_bind_methods() { ClassDB::bind_method(D_METHOD("get_line", "line"), &TextEdit::get_line); 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"), &TextEdit::cursor_set_line, DEFVAL(true)); + ClassDB::bind_method(D_METHOD("cursor_set_line", "line", "adjust_viewport", "can_be_hidden"), &TextEdit::cursor_set_line, DEFVAL(true), DEFVAL(true)); ClassDB::bind_method(D_METHOD("cursor_get_column"), &TextEdit::cursor_get_column); ClassDB::bind_method(D_METHOD("cursor_get_line"), &TextEdit::cursor_get_line); @@ -4910,8 +5354,12 @@ void TextEdit::_bind_methods() { ClassDB::bind_method(D_METHOD("cursor_is_block_mode"), &TextEdit::cursor_is_block_mode); ClassDB::bind_method(D_METHOD("set_readonly", "enable"), &TextEdit::set_readonly); + ClassDB::bind_method(D_METHOD("is_readonly"), &TextEdit::is_readonly); + ClassDB::bind_method(D_METHOD("set_wrap", "enable"), &TextEdit::set_wrap); ClassDB::bind_method(D_METHOD("set_max_chars", "amount"), &TextEdit::set_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); ClassDB::bind_method(D_METHOD("cut"), &TextEdit::cut); ClassDB::bind_method(D_METHOD("copy"), &TextEdit::copy); @@ -4935,6 +5383,17 @@ 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_hiding_enabled", "enable"), &TextEdit::set_hiding_enabled); + ClassDB::bind_method(D_METHOD("is_hiding_enabled"), &TextEdit::is_hiding_enabled); + ClassDB::bind_method(D_METHOD("set_line_as_hidden", "line", "enable"), &TextEdit::set_line_as_hidden); + ClassDB::bind_method(D_METHOD("is_line_hidden"), &TextEdit::is_line_hidden); + ClassDB::bind_method(D_METHOD("fold_all_lines"), &TextEdit::fold_all_lines); + ClassDB::bind_method(D_METHOD("unhide_all_lines"), &TextEdit::unhide_all_lines); + ClassDB::bind_method(D_METHOD("fold_line", "line"), &TextEdit::fold_line); + ClassDB::bind_method(D_METHOD("unfold_line", "line"), &TextEdit::unfold_line); + ClassDB::bind_method(D_METHOD("can_fold", "line"), &TextEdit::can_fold); + ClassDB::bind_method(D_METHOD("is_folded", "line"), &TextEdit::is_folded); + ClassDB::bind_method(D_METHOD("set_highlight_all_occurrences", "enable"), &TextEdit::set_highlight_all_occurrences); ClassDB::bind_method(D_METHOD("is_highlight_all_occurrences_enabled"), &TextEdit::is_highlight_all_occurrences_enabled); @@ -4958,13 +5417,17 @@ void TextEdit::_bind_methods() { ClassDB::bind_method(D_METHOD("menu_option", "option"), &TextEdit::menu_option); ClassDB::bind_method(D_METHOD("get_menu"), &TextEdit::get_menu); + 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, "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"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "smooth_scrolling"), "set_smooth_scroll_enable", "is_smooth_scroll_enabled"); 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_GROUP("Caret", "caret_"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "caret_block_mode"), "cursor_set_block_mode", "cursor_is_block_mode"); @@ -5006,6 +5469,8 @@ TextEdit::TextEdit() { cache.line_number_w = 1; cache.breakpoint_gutter_width = 0; breakpoint_gutter_width = 0; + cache.fold_gutter_width = 0; + fold_gutter_width = 0; indent_size = 4; text.set_indent_size(indent_size); @@ -5075,6 +5540,8 @@ TextEdit::TextEdit() { line_length_guideline = false; line_length_guideline_col = 80; draw_breakpoint_gutter = false; + draw_fold_gutter = false; + hiding_enabled = false; next_operation_is_complex = false; scroll_past_end_of_file_enabled = false; auto_brace_completion_enabled = false; diff --git a/scene/gui/text_edit.h b/scene/gui/text_edit.h index 81310b7c10..b1c7b14e58 100644 --- a/scene/gui/text_edit.h +++ b/scene/gui/text_edit.h @@ -73,6 +73,9 @@ class TextEdit : public Control { struct Cache { Ref<Texture> tab_icon; + Ref<Texture> can_fold_icon; + Ref<Texture> folded_icon; + Ref<Texture> folded_eol_icon; Ref<StyleBox> style_normal; Ref<StyleBox> style_focus; Ref<Font> font; @@ -105,6 +108,7 @@ class TextEdit : public Control { int line_spacing; int line_number_w; int breakpoint_gutter_width; + int fold_gutter_width; Size2 size; } cache; @@ -136,6 +140,7 @@ class TextEdit : public Control { int width_cache : 24; bool marked : 1; bool breakpoint : 1; + bool hidden : 1; Map<int, ColorRegionInfo> region_info; String data; }; @@ -160,6 +165,8 @@ class TextEdit : public Control { bool is_marked(int p_line) const { return text[p_line].marked; } void set_breakpoint(int p_line, bool p_breakpoint) { text[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[p_line].hidden = p_hidden; } + bool is_hidden(int p_line) const { return text[p_line].hidden; } void insert(int p_at, const String &p_text); void remove(int p_at); int size() const { return text.size(); } @@ -251,6 +258,9 @@ class TextEdit : public Control { int line_length_guideline_col; bool draw_breakpoint_gutter; int breakpoint_gutter_width; + bool draw_fold_gutter; + int fold_gutter_width; + bool hiding_enabled; bool highlight_all_occurrences; bool scroll_past_end_of_file_enabled; @@ -293,9 +303,14 @@ class TextEdit : public Control { int search_result_line; int search_result_col; + double line_scroll_pos; + bool context_menu_enabled; int get_visible_rows() const; + int get_total_unhidden_rows() const; + double get_line_scroll_pos(bool p_recalculate = false) const; + void update_line_scroll_pos(); int get_char_count(); @@ -303,6 +318,7 @@ class TextEdit : public Control { int get_column_x_offset(int p_char, String p_str); void adjust_viewport_to_cursor(); + double get_scroll_line_diff() const; void _scroll_moved(double); void _update_scrollbars(); void _v_scroll_input(); @@ -405,6 +421,18 @@ public: void set_line_as_breakpoint(int p_line, bool p_breakpoint); bool is_line_set_as_breakpoint(int p_line) const; void get_breakpoints(List<int> *p_breakpoints) const; + + void set_line_as_hidden(int p_line, bool p_hidden); + bool is_line_hidden(int p_line) const; + void fold_all_lines(); + void unhide_all_lines(); + int num_lines_from(int p_line_from, int unhidden_amount) const; + int get_whitespace_level(int p_line) const; + bool can_fold(int p_line) const; + bool is_folded(int p_line) const; + void fold_line(int p_line); + void unfold_line(int p_line); + String get_text(); String get_line(int line) const; void set_line(int line, String new_text); @@ -433,7 +461,7 @@ public: void center_viewport_to_cursor(); void cursor_set_column(int p_col, bool p_adjust_viewport = true); - void cursor_set_line(int p_row, bool p_adjust_viewport = true); + void cursor_set_line(int p_row, bool p_adjust_viewport = true, bool p_can_be_hidden = true); int cursor_get_column() const; int cursor_get_line() const; @@ -448,6 +476,7 @@ public: bool cursor_is_block_mode() const; void set_readonly(bool p_readonly); + bool is_readonly() const; void set_max_chars(int p_max_chars); void set_wrap(bool p_wrap); @@ -463,6 +492,7 @@ public: void select_all(); void select(int p_from_line, int p_from_column, int p_to_line, int p_to_column); void deselect(); + void swap_lines(int line1, int line2); void set_search_text(const String &p_search_text); void set_search_flags(uint32_t p_flags); @@ -536,6 +566,15 @@ public: void set_breakpoint_gutter_width(int p_gutter_width); int get_breakpoint_gutter_width() const; + void set_draw_fold_gutter(bool p_draw); + bool is_drawing_fold_gutter() const; + + void set_fold_gutter_width(int p_gutter_width); + int get_fold_gutter_width() const; + + void set_hiding_enabled(int p_enabled); + int 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); @@ -547,6 +586,8 @@ public: bool is_selecting_identifiers_on_hover_enabled() const; void set_context_menu_enabled(bool p_enable); + bool is_context_menu_enabled(); + PopupMenu *get_menu() const; String get_text_for_completion(); diff --git a/scene/gui/video_player.cpp b/scene/gui/video_player.cpp index 190ccd50d5..1b6bd30b58 100644 --- a/scene/gui/video_player.cpp +++ b/scene/gui/video_player.cpp @@ -42,44 +42,127 @@ void VideoPlayer::sp_set_mix_rate(int p_rate) { server_mix_rate = p_rate; } -bool VideoPlayer::sp_mix(int32_t *p_buffer, int p_frames) { - - if (resampler.is_ready()) { +bool VideoPlayer::mix(AudioFrame *p_buffer, int p_frames) { + + // Check the amount resampler can really handle. + // If it cannot, wait "wait_resampler_phase_limit" times. + // This mechanism contributes to smoother pause/unpause operation. + if (p_frames <= resampler.get_num_of_ready_frames() || + wait_resampler_limit <= wait_resampler) { + wait_resampler = 0; return resampler.mix(p_buffer, p_frames); } - + wait_resampler++; return false; } -int VideoPlayer::_audio_mix_callback(void *p_udata, const int16_t *p_data, int p_frames) { +// Called from main thread (eg VideoStreamPlaybackWebm::update) +int VideoPlayer::_audio_mix_callback(void *p_udata, const float *p_data, int p_frames) { VideoPlayer *vp = (VideoPlayer *)p_udata; - int todo = MIN(vp->resampler.get_todo(), p_frames); + int todo = MIN(vp->resampler.get_writer_space(), p_frames); - int16_t *wb = vp->resampler.get_write_buffer(); + float *wb = vp->resampler.get_write_buffer(); int c = vp->resampler.get_channel_count(); for (int i = 0; i < todo * c; i++) { wb[i] = p_data[i]; } vp->resampler.write(todo); + return todo; } +// Called from audio thread +void VideoPlayer::_mix_audio() { + + if (!stream.is_valid()) { + return; + } + if (!playback.is_valid() || !playback->is_playing() || playback->is_paused()) { + return; + } + + AudioFrame *buffer = mix_buffer.ptr(); + int buffer_size = mix_buffer.size(); + + // Resample + if (!mix(buffer, buffer_size)) + return; + + AudioFrame vol = AudioFrame(volume, volume); + + // Copy to server's audio buffer + switch (AudioServer::get_singleton()->get_speaker_mode()) { + + case AudioServer::SPEAKER_MODE_STEREO: { + AudioFrame *target = AudioServer::get_singleton()->thread_get_channel_mix_buffer(bus_index, 0); + + for (int j = 0; j < buffer_size; j++) { + + target[j] += buffer[j] * vol; + } + + } break; + case AudioServer::SPEAKER_SURROUND_51: { + + AudioFrame *targets[2] = { + AudioServer::get_singleton()->thread_get_channel_mix_buffer(bus_index, 1), + AudioServer::get_singleton()->thread_get_channel_mix_buffer(bus_index, 2), + }; + + for (int j = 0; j < buffer_size; j++) { + + AudioFrame frame = buffer[j] * vol; + targets[0][j] = frame; + targets[1][j] = frame; + } + } break; + case AudioServer::SPEAKER_SURROUND_71: { + + AudioFrame *targets[3] = { + AudioServer::get_singleton()->thread_get_channel_mix_buffer(bus_index, 1), + AudioServer::get_singleton()->thread_get_channel_mix_buffer(bus_index, 2), + AudioServer::get_singleton()->thread_get_channel_mix_buffer(bus_index, 3) + }; + + for (int j = 0; j < buffer_size; j++) { + + AudioFrame frame = buffer[j] * vol; + targets[0][j] += frame; + targets[1][j] += frame; + targets[2][j] += frame; + } + + } break; + } +} + void VideoPlayer::_notification(int p_notification) { switch (p_notification) { case NOTIFICATION_ENTER_TREE: { + AudioServer::get_singleton()->add_callback(_mix_audios, this); + if (stream.is_valid() && autoplay && !Engine::get_singleton()->is_editor_hint()) { play(); } + + } break; + + case NOTIFICATION_EXIT_TREE: { + + AudioServer::get_singleton()->remove_callback(_mix_audios, this); + } break; case NOTIFICATION_INTERNAL_PROCESS: { + bus_index = AudioServer::get_singleton()->thread_find_bus_index(bus); + if (stream.is_null()) return; if (paused) @@ -87,10 +170,11 @@ void VideoPlayer::_notification(int p_notification) { if (!playback->is_playing()) return; - double audio_time = USEC_TO_SEC(OS::get_singleton()->get_ticks_usec()); //AudioServer::get_singleton()->get_mix_time(); + double audio_time = USEC_TO_SEC(OS::get_singleton()->get_ticks_usec()); double delta = last_audio_time == 0 ? 0 : audio_time - last_audio_time; last_audio_time = audio_time; + if (delta == 0) return; @@ -135,6 +219,9 @@ bool VideoPlayer::has_expand() const { void VideoPlayer::set_stream(const Ref<VideoStream> &p_stream) { stop(); + AudioServer::get_singleton()->lock(); + mix_buffer.resize(AudioServer::get_singleton()->thread_get_mix_buffer_size()); + AudioServer::get_singleton()->unlock(); stream = p_stream; if (stream.is_valid()) { @@ -309,6 +396,40 @@ bool VideoPlayer::has_autoplay() const { return autoplay; }; +void VideoPlayer::set_bus(const StringName &p_bus) { + + //if audio is active, must lock this + AudioServer::get_singleton()->lock(); + bus = p_bus; + AudioServer::get_singleton()->unlock(); +} + +StringName VideoPlayer::get_bus() const { + + for (int i = 0; i < AudioServer::get_singleton()->get_bus_count(); i++) { + if (AudioServer::get_singleton()->get_bus_name(i) == bus) { + return bus; + } + } + return "Master"; +} + +void VideoPlayer::_validate_property(PropertyInfo &property) const { + + if (property.name == "bus") { + + String options; + for (int i = 0; i < AudioServer::get_singleton()->get_bus_count(); i++) { + if (i > 0) + options += ","; + String name = AudioServer::get_singleton()->get_bus_name(i); + options += name; + } + + property.hint_string = options; + } +} + void VideoPlayer::_bind_methods() { ClassDB::bind_method(D_METHOD("set_stream", "stream"), &VideoPlayer::set_stream); @@ -345,6 +466,9 @@ void VideoPlayer::_bind_methods() { ClassDB::bind_method(D_METHOD("set_buffering_msec", "msec"), &VideoPlayer::set_buffering_msec); ClassDB::bind_method(D_METHOD("get_buffering_msec"), &VideoPlayer::get_buffering_msec); + ClassDB::bind_method(D_METHOD("set_bus", "bus"), &VideoPlayer::set_bus); + ClassDB::bind_method(D_METHOD("get_bus"), &VideoPlayer::get_bus); + ClassDB::bind_method(D_METHOD("get_video_texture"), &VideoPlayer::get_video_texture); ADD_PROPERTY(PropertyInfo(Variant::INT, "audio_track", PROPERTY_HINT_RANGE, "0,128,1"), "set_audio_track", "get_audio_track"); @@ -354,6 +478,7 @@ void VideoPlayer::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::BOOL, "autoplay"), "set_autoplay", "has_autoplay"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "paused"), "set_paused", "is_paused"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "expand"), "set_expand", "has_expand"); + ADD_PROPERTY(PropertyInfo(Variant::STRING, "bus", PROPERTY_HINT_ENUM, ""), "set_bus", "get_bus"); } VideoPlayer::VideoPlayer() { @@ -372,6 +497,9 @@ VideoPlayer::VideoPlayer() { // internal_stream.player=this; // stream_rid=AudioServer::get_singleton()->audio_stream_create(&internal_stream); last_audio_time = 0; + + wait_resampler = 0; + wait_resampler_limit = 2; }; VideoPlayer::~VideoPlayer() { diff --git a/scene/gui/video_player.h b/scene/gui/video_player.h index f04e90365f..74e2f14e58 100644 --- a/scene/gui/video_player.h +++ b/scene/gui/video_player.h @@ -33,17 +33,24 @@ #include "scene/gui/control.h" #include "scene/resources/video_stream.h" #include "servers/audio/audio_rb_resampler.h" +#include "servers/audio_server.h" class VideoPlayer : public Control { GDCLASS(VideoPlayer, Control); + struct Output { + + AudioFrame vol; + int bus_index; + Viewport *viewport; //pointer only used for reference to previous mix + }; Ref<VideoStreamPlayback> playback; Ref<VideoStream> stream; int sp_get_channel_count() const; void sp_set_mix_rate(int p_rate); //notify the stream of the mix rate - bool sp_mix(int32_t *p_buffer, int p_frames); + bool mix(AudioFrame *p_buffer, int p_frames); RID stream_rid; @@ -51,6 +58,8 @@ class VideoPlayer : public Control { Ref<Image> last_frame; AudioRBResampler resampler; + Vector<AudioFrame> mix_buffer; + int wait_resampler, wait_resampler_limit; bool paused; bool autoplay; @@ -61,12 +70,18 @@ class VideoPlayer : public Control { int buffering_ms; int server_mix_rate; int audio_track; + int bus_index; + + StringName bus; - static int _audio_mix_callback(void *p_udata, const int16_t *p_data, int p_frames); + void _mix_audio(); + static int _audio_mix_callback(void *p_udata, const float *p_data, int p_frames); + static void _mix_audios(void *self) { reinterpret_cast<VideoPlayer *>(self)->_mix_audio(); } protected: static void _bind_methods(); void _notification(int p_notification); + void _validate_property(PropertyInfo &property) const; public: Size2 get_minimum_size() const; @@ -104,6 +119,9 @@ public: void set_buffering_msec(int p_msec); int get_buffering_msec() const; + void set_bus(const StringName &p_bus); + StringName get_bus() const; + VideoPlayer(); ~VideoPlayer(); }; |