diff options
Diffstat (limited to 'scene/gui')
-rw-r--r-- | scene/gui/color_picker.cpp | 173 | ||||
-rw-r--r-- | scene/gui/color_picker.h | 6 | ||||
-rw-r--r-- | scene/gui/graph_edit.cpp | 116 | ||||
-rw-r--r-- | scene/gui/graph_edit.h | 10 | ||||
-rw-r--r-- | scene/gui/graph_node.cpp | 34 | ||||
-rw-r--r-- | scene/gui/graph_node.h | 6 | ||||
-rw-r--r-- | scene/gui/line_edit.cpp | 22 | ||||
-rw-r--r-- | scene/gui/option_button.cpp | 2 | ||||
-rw-r--r-- | scene/gui/rich_text_label.cpp | 90 | ||||
-rw-r--r-- | scene/gui/rich_text_label.h | 6 | ||||
-rw-r--r-- | scene/gui/tab_bar.cpp | 1 | ||||
-rw-r--r-- | scene/gui/text_edit.cpp | 2 | ||||
-rw-r--r-- | scene/gui/tree.cpp | 204 | ||||
-rw-r--r-- | scene/gui/tree.h | 10 |
14 files changed, 501 insertions, 181 deletions
diff --git a/scene/gui/color_picker.cpp b/scene/gui/color_picker.cpp index 6f7ad94139..5fff1e1df3 100644 --- a/scene/gui/color_picker.cpp +++ b/scene/gui/color_picker.cpp @@ -31,6 +31,7 @@ #include "color_picker.h" #include "core/input/input.h" +#include "core/math/color.h" #include "core/os/keyboard.h" #include "core/os/os.h" #include "scene/main/window.h" @@ -39,6 +40,9 @@ #include "editor/editor_settings.h" #endif +#include "thirdparty/misc/ok_color.h" +#include "thirdparty/misc/ok_color_shader.h" + List<Color> ColorPicker::preset_cache; void ColorPicker::_notification(int p_what) { @@ -102,6 +106,7 @@ void ColorPicker::_notification(int p_what) { Ref<Shader> ColorPicker::wheel_shader; Ref<Shader> ColorPicker::circle_shader; +Ref<Shader> ColorPicker::circle_ok_color_shader; void ColorPicker::init_shaders() { wheel_shader.instantiate(); @@ -152,11 +157,36 @@ void fragment() { COLOR = vec4(mix(vec3(1.0), clamp(abs(fract(vec3((a - TAU) / TAU) + vec3(1.0, 2.0 / 3.0, 1.0 / 3.0)) * 6.0 - vec3(3.0)) - vec3(1.0), 0.0, 1.0), ((float(sqrt(x * x + y * y)) * 2.0)) / 1.0) * vec3(v), (b + b2 + b3 + b4) / 4.00); })"); + + circle_ok_color_shader.instantiate(); + circle_ok_color_shader->set_code(OK_COLOR_SHADER + R"( +// ColorPicker ok color hsv circle shader. + +uniform float v = 1.0; + +void fragment() { + float x = UV.x - 0.5; + float y = UV.y - 0.5; + x += 0.001; + y += 0.001; + float b = float(sqrt(x * x + y * y) < 0.5); + x -= 0.002; + float b2 = float(sqrt(x * x + y * y) < 0.5); + y -= 0.002; + float b3 = float(sqrt(x * x + y * y) < 0.5); + x += 0.002; + float b4 = float(sqrt(x * x + y * y) < 0.5); + float s = sqrt(x * x + y * y); + float h = atan(y, x) / (2.0*M_PI); + vec3 col = okhsl_to_srgb(vec3(h, s, v)); + COLOR = vec4(col, (b + b2 + b3 + b4) / 4.00); +})"); } void ColorPicker::finish_shaders() { wheel_shader.unref(); circle_shader.unref(); + circle_ok_color_shader.unref(); } void ColorPicker::set_focus_on_line_edit() { @@ -166,8 +196,12 @@ 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) { + const char *hsl[3] = { "H", "S", "L" }; + if (hsv_mode_enabled && picker_type == SHAPE_OKHSL_CIRCLE) { + for (int i = 0; i < 3; i++) { + labels[i]->set_text(hsl[i]); + } + } else if (hsv_mode_enabled && picker_type != SHAPE_OKHSL_CIRCLE) { for (int i = 0; i < 3; i++) { labels[i]->set_text(hsv[i]); } @@ -176,14 +210,23 @@ void ColorPicker::_update_controls() { labels[i]->set_text(rgb[i]); } } - + if (picker_type == SHAPE_OKHSL_CIRCLE) { + btn_hsv->set_text(RTR("OKHSL")); + } else { + btn_hsv->set_text(RTR("HSV")); + } if (hsv_mode_enabled) { set_raw_mode(false); + set_hsv_mode(true); btn_raw->set_disabled(true); } else if (raw_mode_enabled) { + set_raw_mode(true); set_hsv_mode(false); + btn_raw->set_disabled(false); btn_hsv->set_disabled(true); } else { + set_raw_mode(false); + set_hsv_mode(false); btn_raw->set_disabled(false); btn_hsv->set_disabled(false); } @@ -236,8 +279,15 @@ void ColorPicker::_update_controls() { wheel_edit->show(); w_edit->show(); uv_edit->hide(); - wheel->set_material(circle_mat); + circle_mat->set_shader(circle_shader); + break; + case SHAPE_OKHSL_CIRCLE: + wheel_edit->show(); + w_edit->show(); + uv_edit->hide(); + wheel->set_material(circle_mat); + circle_mat->set_shader(circle_ok_color_shader); break; default: { } @@ -246,11 +296,17 @@ void ColorPicker::_update_controls() { void ColorPicker::_set_pick_color(const Color &p_color, bool p_update_sliders) { color = p_color; - if (color != last_hsv) { - h = color.get_h(); - s = color.get_s(); - v = color.get_v(); - last_hsv = color; + if (color != last_color) { + if (picker_type == SHAPE_OKHSL_CIRCLE) { + h = color.get_ok_hsl_h(); + s = color.get_ok_hsl_s(); + v = color.get_ok_hsl_l(); + } else { + h = color.get_h(); + s = color.get_s(); + v = color.get_v(); + } + last_color = color; } if (!is_inside_tree()) { @@ -301,10 +357,13 @@ void ColorPicker::_value_changed(double) { h = scroll[0]->get_value() / 360.0; s = scroll[1]->get_value() / 100.0; v = scroll[2]->get_value() / 100.0; - color.set_hsv(h, s, v, scroll[3]->get_value() / 255.0); - - last_hsv = color; + if (picker_type == SHAPE_OKHSL_CIRCLE) { + color.set_ok_hsl(h, s, v, Math::round(scroll[3]->get_value() / 255.0)); + } else { + color.set_hsv(h, s, v, Math::round(scroll[3]->get_value() / 255.0)); + } + last_color = color; } else { for (int i = 0; i < 4; i++) { color.components[i] = scroll[i]->get_value() / (raw_mode_enabled ? 1.0 : 255.0); @@ -342,7 +401,6 @@ void ColorPicker::_update_color(bool p_update_sliders) { 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); @@ -350,7 +408,7 @@ void ColorPicker::_update_color(bool p_update_sliders) { 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); + scroll[3]->set_value(Math::round(color.components[3] * 255.0)); } else { for (int i = 0; i < 4; i++) { if (raw_mode_enabled) { @@ -362,7 +420,7 @@ void ColorPicker::_update_color(bool p_update_sliders) { scroll[i]->set_value(color.components[i]); } else { scroll[i]->set_step(1); - const float byte_value = color.components[i] * 255.0; + const float byte_value = Math::round(color.components[i] * 255.0); scroll[i]->set_max(next_power_of_2(MAX(255, byte_value)) - 1); scroll[i]->set_value(byte_value); } @@ -426,7 +484,6 @@ Color ColorPicker::get_pick_color() const { void ColorPicker::set_picker_shape(PickerShapeType p_picker_type) { ERR_FAIL_INDEX(p_picker_type, SHAPE_MAX); picker_type = p_picker_type; - _update_controls(); _update_color(); } @@ -702,7 +759,7 @@ void ColorPicker::_hsv_draw(int p_which, Control *c) { Ref<Texture2D> cursor = get_theme_icon(SNAME("picker_cursor"), SNAME("ColorPicker")); int x; int y; - if (picker_type == SHAPE_VHS_CIRCLE) { + if (picker_type == SHAPE_VHS_CIRCLE || picker_type == SHAPE_OKHSL_CIRCLE) { x = center.x + (center.x * Math::cos(h * Math_TAU) * s) - (cursor->get_width() / 2); y = center.y + (center.y * Math::sin(h * Math_TAU) * s) - (cursor->get_height() / 2); } else { @@ -735,6 +792,25 @@ void ColorPicker::_hsv_draw(int p_which, Control *c) { Color col; col.set_hsv(h, 1, 1); c->draw_line(Point2(0, y), Point2(c->get_size().x, y), col.inverted()); + } else if (picker_type == SHAPE_OKHSL_CIRCLE) { + Vector<Point2> points; + Vector<Color> colors; + Color col; + col.set_ok_hsl(h, s, 1); + points.resize(4); + colors.resize(4); + points.set(0, Vector2()); + points.set(1, Vector2(c->get_size().x, 0)); + points.set(2, c->get_size()); + points.set(3, Vector2(0, c->get_size().y)); + colors.set(0, col); + colors.set(1, col); + colors.set(2, Color(0, 0, 0)); + colors.set(3, Color(0, 0, 0)); + c->draw_polygon(points, colors); + int y = c->get_size().y - c->get_size().y * CLAMP(v, 0, 1); + col.set_ok_hsl(h, 1, v); + c->draw_line(Point2(0, y), Point2(c->get_size().x, y), col.inverted()); } else if (picker_type == SHAPE_VHS_CIRCLE) { Vector<Point2> points; Vector<Color> colors; @@ -757,7 +833,7 @@ void ColorPicker::_hsv_draw(int p_which, Control *c) { } } else if (p_which == 2) { c->draw_rect(Rect2(Point2(), c->get_size()), Color(1, 1, 1)); - if (picker_type == SHAPE_VHS_CIRCLE) { + if (picker_type == SHAPE_VHS_CIRCLE || picker_type == SHAPE_OKHSL_CIRCLE) { circle_mat->set_shader_param("v", v); } } @@ -793,10 +869,19 @@ void ColorPicker::_slider_draw(int p_which) { } Color s_col; Color v_col; - s_col.set_hsv(h, 0, v); + if (picker_type == SHAPE_OKHSL_CIRCLE) { + s_col.set_ok_hsl(h, 0, v); + } else { + s_col.set_hsv(h, 0, v); + } left_color = (p_which == 1) ? s_col : Color(0, 0, 0); - s_col.set_hsv(h, 1, v); - v_col.set_hsv(h, s, 1); + if (picker_type == SHAPE_OKHSL_CIRCLE) { + s_col.set_ok_hsl(h, 1, v); + v_col.set_ok_hsl(h, s, 1); + } else { + s_col.set_hsv(h, 1, v); + v_col.set_hsv(h, s, 1); + } right_color = (p_which == 1) ? s_col : v_col; } else { left_color = Color( @@ -828,9 +913,8 @@ void ColorPicker::_uv_input(const Ref<InputEvent> &p_event, Control *c) { if (bev.is_valid()) { if (bev->is_pressed() && bev->get_button_index() == MouseButton::LEFT) { Vector2 center = c->get_size() / 2.0; - if (picker_type == SHAPE_VHS_CIRCLE) { + if (picker_type == SHAPE_VHS_CIRCLE || picker_type == SHAPE_OKHSL_CIRCLE) { real_t dist = center.distance_to(bev->get_position()); - if (dist <= center.x) { real_t rad = center.angle_to_point(bev->get_position()); h = ((rad >= 0) ? rad : (Math_TAU + rad)) / Math_TAU; @@ -867,8 +951,13 @@ void ColorPicker::_uv_input(const Ref<InputEvent> &p_event, Control *c) { } } changing_color = true; - color.set_hsv(h, s, v, color.a); - last_hsv = color; + if (picker_type == SHAPE_OKHSL_CIRCLE) { + color.set_ok_hsl(h, s, v, color.a); + } else if (picker_type != SHAPE_OKHSL_CIRCLE) { + color.set_hsv(h, s, v, color.a); + } + last_color = color; + set_pick_color(color); _update_color(); if (!deferred_mode_enabled) { @@ -892,7 +981,7 @@ void ColorPicker::_uv_input(const Ref<InputEvent> &p_event, Control *c) { } Vector2 center = c->get_size() / 2.0; - if (picker_type == SHAPE_VHS_CIRCLE) { + if (picker_type == SHAPE_VHS_CIRCLE || picker_type == SHAPE_OKHSL_CIRCLE) { real_t dist = center.distance_to(mev->get_position()); real_t rad = center.angle_to_point(mev->get_position()); h = ((rad >= 0) ? rad : (Math_TAU + rad)) / Math_TAU; @@ -913,9 +1002,12 @@ void ColorPicker::_uv_input(const Ref<InputEvent> &p_event, Control *c) { v = 1.0 - (y - corner_y) / real_size.y; } } - - color.set_hsv(h, s, v, color.a); - last_hsv = color; + if (picker_type != SHAPE_OKHSL_CIRCLE) { + color.set_hsv(h, s, v, color.a); + } else if (picker_type == SHAPE_OKHSL_CIRCLE) { + color.set_ok_hsl(h, s, v, color.a); + } + last_color = color; set_pick_color(color); _update_color(); if (!deferred_mode_enabled) { @@ -931,7 +1023,7 @@ void ColorPicker::_w_input(const Ref<InputEvent> &p_event) { if (bev->is_pressed() && bev->get_button_index() == MouseButton::LEFT) { changing_color = true; float y = CLAMP((float)bev->get_position().y, 0, w_edit->get_size().height); - if (picker_type == SHAPE_VHS_CIRCLE) { + if (picker_type == SHAPE_VHS_CIRCLE || picker_type == SHAPE_OKHSL_CIRCLE) { v = 1.0 - (y / w_edit->get_size().height); } else { h = y / w_edit->get_size().height; @@ -939,8 +1031,12 @@ void ColorPicker::_w_input(const Ref<InputEvent> &p_event) { } else { changing_color = false; } - color.set_hsv(h, s, v, color.a); - last_hsv = color; + if (picker_type != SHAPE_OKHSL_CIRCLE) { + color.set_hsv(h, s, v, color.a); + } else if (picker_type == SHAPE_OKHSL_CIRCLE) { + color.set_ok_hsl(h, s, v, color.a); + } + last_color = color; set_pick_color(color); _update_color(); if (!deferred_mode_enabled) { @@ -957,13 +1053,17 @@ void ColorPicker::_w_input(const Ref<InputEvent> &p_event) { return; } float y = CLAMP((float)mev->get_position().y, 0, w_edit->get_size().height); - if (picker_type == SHAPE_VHS_CIRCLE) { + if (picker_type == SHAPE_VHS_CIRCLE || picker_type == SHAPE_OKHSL_CIRCLE) { v = 1.0 - (y / w_edit->get_size().height); } else { h = y / w_edit->get_size().height; } - color.set_hsv(h, s, v, color.a); - last_hsv = color; + if (hsv_mode_enabled && picker_type != SHAPE_OKHSL_CIRCLE) { + color.set_hsv(h, s, v, color.a); + } else if (hsv_mode_enabled && picker_type == SHAPE_OKHSL_CIRCLE) { + color.set_ok_hsl(h, s, v, color.a); + } + last_color = color; set_pick_color(color); _update_color(); if (!deferred_mode_enabled) { @@ -1128,7 +1228,7 @@ void ColorPicker::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::BOOL, "hsv_mode"), "set_hsv_mode", "is_hsv_mode"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "raw_mode"), "set_raw_mode", "is_raw_mode"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "deferred_mode"), "set_deferred_mode", "is_deferred_mode"); - ADD_PROPERTY(PropertyInfo(Variant::INT, "picker_shape", PROPERTY_HINT_ENUM, "HSV Rectangle,HSV Rectangle Wheel,VHS Circle"), "set_picker_shape", "get_picker_shape"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "picker_shape", PROPERTY_HINT_ENUM, "HSV Rectangle,HSV Rectangle Wheel,VHS Circle,OKHSL Circle"), "set_picker_shape", "get_picker_shape"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "presets_enabled"), "set_presets_enabled", "are_presets_enabled"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "presets_visible"), "set_presets_visible", "are_presets_visible"); @@ -1139,6 +1239,7 @@ void ColorPicker::_bind_methods() { BIND_ENUM_CONSTANT(SHAPE_HSV_RECTANGLE); BIND_ENUM_CONSTANT(SHAPE_HSV_WHEEL); BIND_ENUM_CONSTANT(SHAPE_VHS_CIRCLE); + BIND_ENUM_CONSTANT(SHAPE_OKHSL_CIRCLE); } ColorPicker::ColorPicker() : diff --git a/scene/gui/color_picker.h b/scene/gui/color_picker.h index 6f3e16009c..953be032ec 100644 --- a/scene/gui/color_picker.h +++ b/scene/gui/color_picker.h @@ -68,6 +68,7 @@ public: SHAPE_HSV_RECTANGLE, SHAPE_HSV_WHEEL, SHAPE_VHS_CIRCLE, + SHAPE_OKHSL_CIRCLE, SHAPE_MAX }; @@ -75,6 +76,7 @@ public: private: static Ref<Shader> wheel_shader; static Ref<Shader> circle_shader; + static Ref<Shader> circle_ok_color_shader; static List<Color> preset_cache; Control *screen = nullptr; @@ -124,7 +126,7 @@ private: float h = 0.0; float s = 0.0; float v = 0.0; - Color last_hsv; + Color last_color; void _html_submitted(const String &p_html); void _value_changed(double); @@ -161,6 +163,8 @@ public: void set_edit_alpha(bool p_show); bool is_editing_alpha() const; + int get_preset_size(); + 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; diff --git a/scene/gui/graph_edit.cpp b/scene/gui/graph_edit.cpp index fdff6a88a9..8ad55fc6ef 100644 --- a/scene/gui/graph_edit.cpp +++ b/scene/gui/graph_edit.cpp @@ -426,8 +426,8 @@ void GraphEdit::_notification(int p_what) { switch (p_what) { case NOTIFICATION_ENTER_TREE: case NOTIFICATION_THEME_CHANGED: { - port_grab_distance_horizontal = get_theme_constant(SNAME("port_grab_distance_horizontal")); - port_grab_distance_vertical = get_theme_constant(SNAME("port_grab_distance_vertical")); + port_hotzone_inner_extent = get_theme_constant("port_hotzone_inner_extent"); + port_hotzone_outer_extent = get_theme_constant("port_hotzone_outer_extent"); zoom_minus->set_icon(get_theme_icon(SNAME("minus"))); zoom_reset->set_icon(get_theme_icon(SNAME("reset"))); @@ -508,8 +508,9 @@ void GraphEdit::_notification(int p_what) { void GraphEdit::_update_comment_enclosed_nodes_list(GraphNode *p_node, HashMap<StringName, Vector<GraphNode *>> &p_comment_enclosed_nodes) { Rect2 comment_node_rect = p_node->get_rect(); - Vector<GraphNode *> enclosed_nodes; + comment_node_rect.size *= zoom; + Vector<GraphNode *> enclosed_nodes; for (int i = 0; i < get_child_count(); i++) { GraphNode *gn = Object::cast_to<GraphNode>(get_child(i)); if (!gn || gn->is_selected()) { @@ -517,6 +518,8 @@ void GraphEdit::_update_comment_enclosed_nodes_list(GraphNode *p_node, HashMap<S } Rect2 node_rect = gn->get_rect(); + node_rect.size *= zoom; + bool included = comment_node_rect.encloses(node_rect); if (included) { enclosed_nodes.push_back(gn); @@ -544,8 +547,7 @@ void GraphEdit::_set_position_of_comment_enclosed_nodes(GraphNode *p_node, HashM } bool GraphEdit::_filter_input(const Point2 &p_point) { - Ref<Texture2D> port = get_theme_icon(SNAME("port"), SNAME("GraphNode")); - Vector2i port_size = Vector2i(port->get_width(), port->get_height()); + Ref<Texture2D> port_icon = get_theme_icon(SNAME("port"), SNAME("GraphNode")); for (int i = get_child_count() - 1; i >= 0; i--) { GraphNode *gn = Object::cast_to<GraphNode>(get_child(i)); @@ -553,14 +555,18 @@ bool GraphEdit::_filter_input(const Point2 &p_point) { continue; } - for (int j = 0; j < gn->get_connection_output_count(); j++) { - if (is_in_output_hotzone(gn, j, p_point / zoom, port_size)) { + for (int j = 0; j < gn->get_connection_input_count(); j++) { + Vector2i port_size = Vector2i(port_icon->get_width(), port_icon->get_height()); + port_size.height = MAX(port_size.height, gn->get_connection_input_height(j)); + if (is_in_input_hotzone(gn, j, p_point / zoom, port_size)) { return true; } } - for (int j = 0; j < gn->get_connection_input_count(); j++) { - if (is_in_input_hotzone(gn, j, p_point / zoom, port_size)) { + for (int j = 0; j < gn->get_connection_output_count(); j++) { + Vector2i port_size = Vector2i(port_icon->get_width(), port_icon->get_height()); + port_size.height = MAX(port_size.height, gn->get_connection_output_height(j)); + if (is_in_output_hotzone(gn, j, p_point / zoom, port_size)) { return true; } } @@ -572,8 +578,7 @@ bool GraphEdit::_filter_input(const Point2 &p_point) { void GraphEdit::_top_layer_input(const Ref<InputEvent> &p_ev) { Ref<InputEventMouseButton> mb = p_ev; if (mb.is_valid() && mb->get_button_index() == MouseButton::LEFT && mb->is_pressed()) { - Ref<Texture2D> port = get_theme_icon(SNAME("port"), SNAME("GraphNode")); - Vector2i port_size = Vector2i(port->get_width(), port->get_height()); + Ref<Texture2D> port_icon = get_theme_icon(SNAME("port"), SNAME("GraphNode")); connecting_valid = false; click_pos = mb->get_position() / zoom; @@ -585,6 +590,9 @@ void GraphEdit::_top_layer_input(const Ref<InputEvent> &p_ev) { for (int j = 0; j < gn->get_connection_output_count(); j++) { Vector2 pos = gn->get_connection_output_position(j) + gn->get_position(); + Vector2i port_size = Vector2i(port_icon->get_width(), port_icon->get_height()); + port_size.height = MAX(port_size.height, gn->get_connection_output_height(j)); + if (is_in_output_hotzone(gn, j, click_pos, port_size)) { if (valid_left_disconnect_types.has(gn->get_connection_output_type(j))) { //check disconnect @@ -629,6 +637,10 @@ void GraphEdit::_top_layer_input(const Ref<InputEvent> &p_ev) { for (int j = 0; j < gn->get_connection_input_count(); j++) { Vector2 pos = gn->get_connection_input_position(j) + gn->get_position(); + + Vector2i port_size = Vector2i(port_icon->get_width(), port_icon->get_height()); + port_size.height = MAX(port_size.height, gn->get_connection_input_height(j)); + if (is_in_input_hotzone(gn, j, click_pos, port_size)) { if (right_disconnects || valid_right_disconnect_types.has(gn->get_connection_input_type(j))) { //check disconnect @@ -682,11 +694,9 @@ void GraphEdit::_top_layer_input(const Ref<InputEvent> &p_ev) { connecting_valid = just_disconnected || click_pos.distance_to(connecting_to / zoom) > 20.0; if (connecting_valid) { - Ref<Texture2D> port = get_theme_icon(SNAME("port"), SNAME("GraphNode")); - Vector2i port_size = Vector2i(port->get_width(), port->get_height()); - Vector2 mpos = mm->get_position() / zoom; for (int i = get_child_count() - 1; i >= 0; i--) { + Ref<Texture2D> port_icon = get_theme_icon(SNAME("port"), SNAME("GraphNode")); GraphNode *gn = Object::cast_to<GraphNode>(get_child(i)); if (!gn) { continue; @@ -695,6 +705,9 @@ void GraphEdit::_top_layer_input(const Ref<InputEvent> &p_ev) { if (!connecting_out) { for (int j = 0; j < gn->get_connection_output_count(); j++) { Vector2 pos = gn->get_connection_output_position(j) + gn->get_position(); + Vector2i port_size = Vector2i(port_icon->get_width(), port_icon->get_height()); + port_size.height = MAX(port_size.height, gn->get_connection_output_height(j)); + int type = gn->get_connection_output_type(j); if ((type == connecting_type || valid_connection_types.has(ConnType(connecting_type, type))) && is_in_output_hotzone(gn, j, mpos, port_size)) { connecting_target = true; @@ -707,6 +720,9 @@ void GraphEdit::_top_layer_input(const Ref<InputEvent> &p_ev) { } else { for (int j = 0; j < gn->get_connection_input_count(); j++) { Vector2 pos = gn->get_connection_input_position(j) + gn->get_position(); + Vector2i port_size = Vector2i(port_icon->get_width(), port_icon->get_height()); + port_size.height = MAX(port_size.height, gn->get_connection_input_height(j)); + int type = gn->get_connection_input_type(j); if ((type == connecting_type || valid_connection_types.has(ConnType(connecting_type, type))) && is_in_input_hotzone(gn, j, mpos, port_size)) { connecting_target = true; @@ -754,19 +770,24 @@ void GraphEdit::_top_layer_input(const Ref<InputEvent> &p_ev) { } } -bool GraphEdit::_check_clickable_control(Control *p_control, const Vector2 &pos) { - if (p_control->is_set_as_top_level() || !p_control->is_visible()) { +bool GraphEdit::_check_clickable_control(Control *p_control, const Vector2 &mpos, const Vector2 &p_offset) { + if (p_control->is_set_as_top_level() || !p_control->is_visible() || !p_control->is_inside_tree()) { return false; } - if (!p_control->has_point(pos) || p_control->get_mouse_filter() == MOUSE_FILTER_IGNORE) { - //test children + Rect2 control_rect = p_control->get_rect(); + control_rect.size *= zoom; + control_rect.position *= zoom; + control_rect.position += p_offset; + + if (!control_rect.has_point(mpos) || p_control->get_mouse_filter() == MOUSE_FILTER_IGNORE) { + // Test children. for (int i = 0; i < p_control->get_child_count(); i++) { - Control *subchild = Object::cast_to<Control>(p_control->get_child(i)); - if (!subchild) { + Control *child_rect = Object::cast_to<Control>(p_control->get_child(i)); + if (!child_rect) { continue; } - if (_check_clickable_control(subchild, pos - subchild->get_position())) { + if (_check_clickable_control(child_rect, mpos, control_rect.position)) { return true; } } @@ -798,7 +819,13 @@ bool GraphEdit::is_in_output_hotzone(GraphNode *p_graph_node, int p_slot_index, } bool GraphEdit::is_in_port_hotzone(const Vector2 &pos, const Vector2 &p_mouse_pos, const Vector2i &p_port_size, bool p_left) { - if (!Rect2(pos.x - port_grab_distance_horizontal, pos.y - port_grab_distance_vertical, port_grab_distance_horizontal * 2, port_grab_distance_vertical * 2).has_point(p_mouse_pos)) { + Rect2 hotzone = Rect2( + pos.x - (p_left ? port_hotzone_outer_extent : port_hotzone_inner_extent), + pos.y - p_port_size.height / 2.0, + port_hotzone_inner_extent + port_hotzone_outer_extent, + p_port_size.height); + + if (!hotzone.has_point(p_mouse_pos)) { return false; } @@ -807,23 +834,17 @@ bool GraphEdit::is_in_port_hotzone(const Vector2 &pos, const Vector2 &p_mouse_po if (!child) { continue; } - Rect2 rect = child->get_rect(); - - // To prevent intersections with other nodes. - rect.position *= zoom; - rect.size *= zoom; - - if (rect.has_point(p_mouse_pos)) { - //check sub-controls - Vector2 subpos = p_mouse_pos - rect.position; + Rect2 child_rect = child->get_rect(); + child_rect.size *= zoom; + if (child_rect.has_point(p_mouse_pos * zoom)) { for (int j = 0; j < child->get_child_count(); j++) { Control *subchild = Object::cast_to<Control>(child->get_child(j)); if (!subchild) { continue; } - if (_check_clickable_control(subchild, subpos - subchild->get_position())) { + if (_check_clickable_control(subchild, p_mouse_pos * zoom, child_rect.position)) { return false; } } @@ -839,13 +860,23 @@ PackedVector2Array GraphEdit::get_connection_line(const Vector2 &p_from, const V return ret; } + float x_diff = (p_to.x - p_from.x); + float cp_offset = x_diff * lines_curvature; + if (x_diff < 0) { + cp_offset *= -1; + } + Curve2D curve; - Vector<Color> colors; curve.add_point(p_from); - curve.set_point_out(0, Vector2(60, 0)); + curve.set_point_out(0, Vector2(cp_offset, 0)); curve.add_point(p_to); - curve.set_point_in(1, Vector2(-60, 0)); - return curve.tessellate(); + curve.set_point_in(1, Vector2(-cp_offset, 0)); + + if (lines_curvature > 0) { + return curve.tessellate(5, 2.0); + } else { + return curve.tessellate(1); + } } void GraphEdit::_draw_connection_line(CanvasItem *p_where, const Vector2 &p_from, const Vector2 &p_to, const Color &p_color, const Color &p_to_color, float p_width, float p_zoom) { @@ -1666,6 +1697,15 @@ void GraphEdit::_minimap_toggled() { } } +void GraphEdit::set_connection_lines_curvature(float p_curvature) { + lines_curvature = p_curvature; + update(); +} + +float GraphEdit::get_connection_lines_curvature() const { + return lines_curvature; +} + void GraphEdit::set_connection_lines_thickness(float p_thickness) { lines_thickness = p_thickness; update(); @@ -2244,6 +2284,9 @@ void GraphEdit::_bind_methods() { ClassDB::bind_method(D_METHOD("set_use_snap", "enable"), &GraphEdit::set_use_snap); ClassDB::bind_method(D_METHOD("is_using_snap"), &GraphEdit::is_using_snap); + ClassDB::bind_method(D_METHOD("set_connection_lines_curvature", "curvature"), &GraphEdit::set_connection_lines_curvature); + ClassDB::bind_method(D_METHOD("get_connection_lines_curvature"), &GraphEdit::get_connection_lines_curvature); + ClassDB::bind_method(D_METHOD("set_connection_lines_thickness", "pixels"), &GraphEdit::set_connection_lines_thickness); ClassDB::bind_method(D_METHOD("get_connection_lines_thickness"), &GraphEdit::get_connection_lines_thickness); @@ -2280,6 +2323,7 @@ void GraphEdit::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::INT, "panning_scheme", PROPERTY_HINT_ENUM, "Scroll Zooms,Scroll Pans"), "set_panning_scheme", "get_panning_scheme"); ADD_GROUP("Connection Lines", "connection_lines"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "connection_lines_curvature"), "set_connection_lines_curvature", "get_connection_lines_curvature"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "connection_lines_thickness"), "set_connection_lines_thickness", "get_connection_lines_thickness"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "connection_lines_antialiased"), "set_connection_lines_antialiased", "is_connection_lines_antialiased"); diff --git a/scene/gui/graph_edit.h b/scene/gui/graph_edit.h index 5484a2317c..02e90e4717 100644 --- a/scene/gui/graph_edit.h +++ b/scene/gui/graph_edit.h @@ -124,8 +124,8 @@ private: HScrollBar *h_scroll = nullptr; VScrollBar *v_scroll = nullptr; - float port_grab_distance_horizontal = 0.0; - float port_grab_distance_vertical = 0.0; + float port_hotzone_inner_extent = 0.0; + float port_hotzone_outer_extent = 0.0; Ref<ViewPanner> panner; bool warped_panning = true; @@ -178,6 +178,7 @@ private: List<Connection> connections; float lines_thickness = 2.0f; + float lines_curvature = 0.5f; bool lines_antialiased = true; PackedVector2Array get_connection_line(const Vector2 &p_from, const Vector2 &p_to); @@ -250,7 +251,7 @@ private: friend class GraphEditMinimap; void _minimap_toggled(); - bool _check_clickable_control(Control *p_control, const Vector2 &pos); + bool _check_clickable_control(Control *p_control, const Vector2 &r_mouse_pos, const Vector2 &p_offset); bool arranging_graph = false; @@ -344,6 +345,9 @@ public: int get_snap() const; void set_snap(int p_snap); + void set_connection_lines_curvature(float p_curvature); + float get_connection_lines_curvature() const; + void set_connection_lines_thickness(float p_thickness); float get_connection_lines_thickness() const; diff --git a/scene/gui/graph_node.cpp b/scene/gui/graph_node.cpp index 45f036d8fc..5cb28a30e8 100644 --- a/scene/gui/graph_node.cpp +++ b/scene/gui/graph_node.cpp @@ -832,6 +832,7 @@ void GraphNode::_connpos_update() { cc.pos = Point2i(edgeofs, y + h / 2); cc.type = slot_info[idx].type_left; cc.color = slot_info[idx].color_left; + cc.height = size.height; conn_input_cache.push_back(cc); } if (slot_info[idx].enable_right) { @@ -839,6 +840,7 @@ void GraphNode::_connpos_update() { cc.pos = Point2i(get_size().width - edgeofs, y + h / 2); cc.type = slot_info[idx].type_right; cc.color = slot_info[idx].color_right; + cc.height = size.height; conn_output_cache.push_back(cc); } } @@ -859,12 +861,13 @@ int GraphNode::get_connection_input_count() { return conn_input_cache.size(); } -int GraphNode::get_connection_output_count() { +int GraphNode::get_connection_input_height(int p_idx) { if (connpos_dirty) { _connpos_update(); } - return conn_output_cache.size(); + ERR_FAIL_INDEX_V(p_idx, conn_input_cache.size(), 0); + return conn_input_cache[p_idx].height; } Vector2 GraphNode::get_connection_input_position(int p_idx) { @@ -897,6 +900,23 @@ Color GraphNode::get_connection_input_color(int p_idx) { return conn_input_cache[p_idx].color; } +int GraphNode::get_connection_output_count() { + if (connpos_dirty) { + _connpos_update(); + } + + return conn_output_cache.size(); +} + +int GraphNode::get_connection_output_height(int p_idx) { + if (connpos_dirty) { + _connpos_update(); + } + + ERR_FAIL_INDEX_V(p_idx, conn_output_cache.size(), 0); + return conn_output_cache[p_idx].height; +} + Vector2 GraphNode::get_connection_output_position(int p_idx) { if (connpos_dirty) { _connpos_update(); @@ -1066,15 +1086,17 @@ void GraphNode::_bind_methods() { ClassDB::bind_method(D_METHOD("set_selected", "selected"), &GraphNode::set_selected); ClassDB::bind_method(D_METHOD("is_selected"), &GraphNode::is_selected); - ClassDB::bind_method(D_METHOD("get_connection_output_count"), &GraphNode::get_connection_output_count); ClassDB::bind_method(D_METHOD("get_connection_input_count"), &GraphNode::get_connection_input_count); + ClassDB::bind_method(D_METHOD("get_connection_input_height", "idx"), &GraphNode::get_connection_input_height); + ClassDB::bind_method(D_METHOD("get_connection_input_position", "idx"), &GraphNode::get_connection_input_position); + ClassDB::bind_method(D_METHOD("get_connection_input_type", "idx"), &GraphNode::get_connection_input_type); + ClassDB::bind_method(D_METHOD("get_connection_input_color", "idx"), &GraphNode::get_connection_input_color); + ClassDB::bind_method(D_METHOD("get_connection_output_count"), &GraphNode::get_connection_output_count); + ClassDB::bind_method(D_METHOD("get_connection_output_height", "idx"), &GraphNode::get_connection_output_height); ClassDB::bind_method(D_METHOD("get_connection_output_position", "idx"), &GraphNode::get_connection_output_position); ClassDB::bind_method(D_METHOD("get_connection_output_type", "idx"), &GraphNode::get_connection_output_type); ClassDB::bind_method(D_METHOD("get_connection_output_color", "idx"), &GraphNode::get_connection_output_color); - ClassDB::bind_method(D_METHOD("get_connection_input_position", "idx"), &GraphNode::get_connection_input_position); - ClassDB::bind_method(D_METHOD("get_connection_input_type", "idx"), &GraphNode::get_connection_input_type); - ClassDB::bind_method(D_METHOD("get_connection_input_color", "idx"), &GraphNode::get_connection_input_color); ClassDB::bind_method(D_METHOD("set_show_close_button", "show"), &GraphNode::set_show_close_button); ClassDB::bind_method(D_METHOD("is_close_button_visible"), &GraphNode::is_close_button_visible); diff --git a/scene/gui/graph_node.h b/scene/gui/graph_node.h index 9481a7452d..f6c943dc89 100644 --- a/scene/gui/graph_node.h +++ b/scene/gui/graph_node.h @@ -81,6 +81,7 @@ private: Vector2 pos; int type = 0; Color color; + int height; }; Vector<ConnCache> conn_input_cache; @@ -167,10 +168,13 @@ public: bool is_close_button_visible() const; int get_connection_input_count(); - int get_connection_output_count(); + int get_connection_input_height(int p_idx); Vector2 get_connection_input_position(int p_idx); int get_connection_input_type(int p_idx); Color get_connection_input_color(int p_idx); + + int get_connection_output_count(); + int get_connection_output_height(int p_idx); Vector2 get_connection_output_position(int p_idx); int get_connection_output_type(int p_idx); Color get_connection_output_color(int p_idx); diff --git a/scene/gui/line_edit.cpp b/scene/gui/line_edit.cpp index 73188d6602..540250c8e9 100644 --- a/scene/gui/line_edit.cpp +++ b/scene/gui/line_edit.cpp @@ -312,7 +312,7 @@ void LineEdit::gui_input(const Ref<InputEvent> &p_event) { selection.end = text.length(); selection.double_click = true; last_dblclk = 0; - caret_column = selection.begin; + set_caret_column(selection.begin); if (!pass && DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_CLIPBOARD_PRIMARY)) { DisplayServer::get_singleton()->clipboard_set_primary(text); } @@ -327,7 +327,7 @@ void LineEdit::gui_input(const Ref<InputEvent> &p_event) { selection.begin = words[i]; selection.end = words[i + 1]; selection.double_click = true; - caret_column = selection.end; + set_caret_column(selection.end); break; } } @@ -695,7 +695,7 @@ bool LineEdit::_is_over_clear_button(const Point2 &p_pos) const { return false; } Ref<Texture2D> icon = Control::get_theme_icon(SNAME("clear")); - int x_ofs = get_theme_stylebox(SNAME("normal"))->get_offset().x; + int x_ofs = get_theme_stylebox(SNAME("normal"))->get_margin(SIDE_RIGHT); return p_pos.x > get_size().width - icon->get_width() - x_ofs; } @@ -1686,13 +1686,17 @@ Size2 LineEdit::get_minimum_size() const { min_size.height = MAX(TS->shaped_text_get_size(text_rid).y + font->get_spacing(TextServer::SPACING_TOP) + font->get_spacing(TextServer::SPACING_BOTTOM), font->get_height(font_size)); // Take icons into account. - bool using_placeholder = text.is_empty() && ime_text.is_empty(); - bool display_clear_icon = !using_placeholder && is_editable() && clear_button_enabled; - if (right_icon.is_valid() || display_clear_icon) { - Ref<Texture2D> r_icon = display_clear_icon ? Control::get_theme_icon(SNAME("clear")) : right_icon; - min_size.width += r_icon->get_width(); - min_size.height = MAX(min_size.height, r_icon->get_height()); + int icon_max_width = 0; + if (right_icon.is_valid()) { + min_size.height = MAX(min_size.height, right_icon->get_height()); + icon_max_width = right_icon->get_width(); + } + if (clear_button_enabled) { + Ref<Texture2D> clear_icon = Control::get_theme_icon(SNAME("clear")); + min_size.height = MAX(min_size.height, clear_icon->get_height()); + icon_max_width = MAX(icon_max_width, clear_icon->get_width()); } + min_size.width += icon_max_width; return style->get_minimum_size() + min_size; } diff --git a/scene/gui/option_button.cpp b/scene/gui/option_button.cpp index 4b79d79846..a86f2bdbc1 100644 --- a/scene/gui/option_button.cpp +++ b/scene/gui/option_button.cpp @@ -320,7 +320,7 @@ int OptionButton::get_selectable_item(bool p_from_last) const { } } } else { - for (int i = get_item_count() - 1; i >= 0; i++) { + for (int i = get_item_count() - 1; i >= 0; i--) { if (!is_item_disabled(i) && !is_item_separator(i)) { return i; } diff --git a/scene/gui/rich_text_label.cpp b/scene/gui/rich_text_label.cpp index 94c2a9e64b..cf7bf930a5 100644 --- a/scene/gui/rich_text_label.cpp +++ b/scene/gui/rich_text_label.cpp @@ -414,7 +414,7 @@ float RichTextLabel::_resize_line(ItemFrame *p_frame, int p_line, const Ref<Font } l.offset.y = p_h; - return l.offset.y + l.text_buf->get_size().y + l.text_buf->get_line_count() * get_theme_constant(SNAME("line_separation")); + return _calculate_line_vertical_offset(l); } float RichTextLabel::_shape_line(ItemFrame *p_frame, int p_line, const Ref<Font> &p_base_font, int p_base_font_size, int p_width, float p_h, int *r_char_offset) { @@ -683,7 +683,7 @@ float RichTextLabel::_shape_line(ItemFrame *p_frame, int p_line, const Ref<Font> *r_char_offset = l.char_offset + l.char_count; l.offset.y = p_h; - return l.offset.y + l.text_buf->get_size().y + l.text_buf->get_line_count() * get_theme_constant(SNAME("line_separation")); + return _calculate_line_vertical_offset(l); } int RichTextLabel::_draw_line(ItemFrame *p_frame, int p_line, const Vector2 &p_ofs, int p_width, const Color &p_base_color, int p_outline_size, const Color &p_outline_color, const Color &p_font_shadow_color, int p_shadow_outline_size, const Point2 &p_shadow_ofs, int &r_processed_glyphs) { @@ -1566,7 +1566,7 @@ int RichTextLabel::_find_first_line(int p_from, int p_to, int p_vofs) const { while (l < r) { int m = Math::floor(double(l + r) / 2.0); MutexLock lock(main->lines[m].text_buf->get_mutex()); - int ofs = main->lines[m].offset.y + main->lines[m].text_buf->get_size().y + main->lines[m].text_buf->get_line_count() * get_theme_constant(SNAME("line_separation")); + int ofs = _calculate_line_vertical_offset(main->lines[m]); if (ofs < p_vofs) { l = m + 1; } else { @@ -1576,6 +1576,10 @@ int RichTextLabel::_find_first_line(int p_from, int p_to, int p_vofs) const { return l; } +_FORCE_INLINE_ float RichTextLabel::_calculate_line_vertical_offset(const RichTextLabel::Line &line) const { + return line.get_height(get_theme_constant(SNAME("line_separation"))); +} + void RichTextLabel::_notification(int p_what) { switch (p_what) { case NOTIFICATION_MOUSE_EXIT: { @@ -1609,6 +1613,7 @@ void RichTextLabel::_notification(int p_what) { update(); } break; + case NOTIFICATION_PREDELETE: case NOTIFICATION_EXIT_TREE: { _stop_thread(); } break; @@ -2489,7 +2494,7 @@ bool RichTextLabel::_validate_line_caches() { // Resize lines without reshaping. int fi = main->first_resized_line.load(); - float total_height = 0; + float total_height = (fi == 0) ? 0 : _calculate_line_vertical_offset(main->lines[fi - 1]); for (int i = fi; i < (int)main->lines.size(); i++) { total_height = _resize_line(main, i, base_font, base_font_size, text_rect.get_size().width - scroll_w, total_height); @@ -2551,6 +2556,10 @@ bool RichTextLabel::_validate_line_caches() { void RichTextLabel::_process_line_caches() { // Shape invalid lines. + if (!is_inside_tree()) { + return; + } + MutexLock data_lock(data_mutex); Rect2 text_rect = _get_text_rect(); @@ -2560,10 +2569,9 @@ void RichTextLabel::_process_line_caches() { int fi = main->first_invalid_line.load(); int total_chars = (fi == 0) ? 0 : (main->lines[fi].char_offset + main->lines[fi].char_count); - float total_height = 0; + float total_height = (fi == 0) ? 0 : _calculate_line_vertical_offset(main->lines[fi - 1]); for (int i = fi; i < (int)main->lines.size(); i++) { total_height = _shape_line(main, i, base_font, base_font_size, text_rect.get_size().width - scroll_w, total_height, &total_chars); - updating_scroll = true; bool exceeds = total_height > ctrl_height && scroll_active; if (exceeds != scroll_visible) { @@ -2577,11 +2585,11 @@ void RichTextLabel::_process_line_caches() { scroll_w = 0; vscroll->hide(); } - main->first_invalid_line.store(0); main->first_resized_line.store(0); main->first_invalid_font_line.store(0); + // since scroll was added or removed we need to resize all lines total_height = 0; for (int j = 0; j <= i; j++) { total_height = _resize_line(main, j, base_font, base_font_size, text_rect.get_size().width - scroll_w, total_height); @@ -3369,21 +3377,34 @@ void RichTextLabel::append_text(const String &p_bbcode) { bool in_bold = false; bool in_italics = false; + bool after_list_open_tag = false; + bool after_list_close_tag = false; set_process_internal(false); - while (pos < p_bbcode.length()) { + while (pos <= p_bbcode.length()) { int brk_pos = p_bbcode.find("[", pos); if (brk_pos < 0) { brk_pos = p_bbcode.length(); } - if (brk_pos > pos) { - add_text(p_bbcode.substr(pos, brk_pos - pos)); + String text = brk_pos > pos ? p_bbcode.substr(pos, brk_pos - pos) : ""; + + // Trim the first newline character, it may be added later as needed. + if (after_list_close_tag || after_list_open_tag) { + text = text.trim_prefix("\n"); } if (brk_pos == p_bbcode.length()) { + // For tags that are not properly closed. + if (text.is_empty() && after_list_open_tag) { + text = "\n"; + } + + if (!text.is_empty()) { + add_text(text); + } break; //nothing else to add } @@ -3391,7 +3412,8 @@ void RichTextLabel::append_text(const String &p_bbcode) { if (brk_end == -1) { //no close, add the rest - add_text(p_bbcode.substr(brk_pos, p_bbcode.length() - brk_pos)); + text += p_bbcode.substr(brk_pos, p_bbcode.length() - brk_pos); + add_text(text); break; } @@ -3437,18 +3459,60 @@ void RichTextLabel::append_text(const String &p_bbcode) { } if (!tag_ok) { - add_text("[" + tag); + text += "[" + tag; + add_text(text); + after_list_open_tag = false; + after_list_close_tag = false; pos = brk_end; continue; } + if (text.is_empty() && after_list_open_tag) { + text = "\n"; // Make empty list have at least one item. + } + after_list_open_tag = false; + + if (tag == "/ol" || tag == "/ul") { + if (!text.is_empty()) { + // Make sure text ends with a newline character, that is, the last item + // will wrap at the end of block. + if (!text.ends_with("\n")) { + text += "\n"; + } + } else if (!after_list_close_tag) { + text = "\n"; // Make the innermost list item wrap at the end of lists. + } + after_list_close_tag = true; + } else { + after_list_close_tag = false; + } + + if (!text.is_empty()) { + add_text(text); + } + tag_stack.pop_front(); pos = brk_end + 1; if (tag != "/img" && tag != "/dropcap") { pop(); } + continue; + } + + if (tag == "ol" || tag.begins_with("ol ") || tag == "ul" || tag.begins_with("ul ")) { + if (text.is_empty() && after_list_open_tag) { + text = "\n"; // Make each list have at least one item at the beginning. + } + after_list_open_tag = true; + } else { + after_list_open_tag = false; + } + if (!text.is_empty()) { + add_text(text); + } + after_list_close_tag = false; - } else if (tag == "b") { + if (tag == "b") { //use bold font in_bold = true; if (in_italics) { diff --git a/scene/gui/rich_text_label.h b/scene/gui/rich_text_label.h index 7fbd5f1745..93e57058b0 100644 --- a/scene/gui/rich_text_label.h +++ b/scene/gui/rich_text_label.h @@ -117,6 +117,10 @@ private: int char_count = 0; Line() { text_buf.instantiate(); } + + _FORCE_INLINE_ float get_height(float line_separation) const { + return offset.y + text_buf->get_size().y + text_buf->get_line_count() * line_separation; + } }; struct Item { @@ -504,6 +508,8 @@ private: void _scroll_changed(double); int _find_first_line(int p_from, int p_to, int p_vofs) const; + _FORCE_INLINE_ float _calculate_line_vertical_offset(const Line &line) const; + virtual void gui_input(const Ref<InputEvent> &p_event) override; virtual String get_tooltip(const Point2 &p_pos) const override; Item *_get_next_item(Item *p_item, bool p_free = false) const; diff --git a/scene/gui/tab_bar.cpp b/scene/gui/tab_bar.cpp index b96ba0ebf9..fec8d11bef 100644 --- a/scene/gui/tab_bar.cpp +++ b/scene/gui/tab_bar.cpp @@ -862,6 +862,7 @@ void TabBar::_update_hover() { void TabBar::_update_cache() { if (tabs.is_empty()) { + buttons_visible = false; return; } diff --git a/scene/gui/text_edit.cpp b/scene/gui/text_edit.cpp index 0f74c9c357..8e948203f1 100644 --- a/scene/gui/text_edit.cpp +++ b/scene/gui/text_edit.cpp @@ -3080,7 +3080,7 @@ void TextEdit::set_line(int p_line, const String &p_new_text) { _remove_text(p_line, 0, p_line, text[p_line].length()); _insert_text(p_line, 0, p_new_text); if (caret.line == p_line && caret.column > p_new_text.length()) { - set_caret_column(MIN(caret.column, p_new_text.length()), false); + set_caret_column(p_new_text.length(), false); } if (has_selection() && p_line == selection.to_line && selection.to_column > text[p_line].length()) { selection.to_column = text[p_line].length(); diff --git a/scene/gui/tree.cpp b/scene/gui/tree.cpp index 0ca9a66e08..d3e7540790 100644 --- a/scene/gui/tree.cpp +++ b/scene/gui/tree.cpp @@ -544,6 +544,21 @@ bool TreeItem::is_collapsed() { return collapsed; } +void TreeItem::set_visible(bool p_visible) { + if (visible == p_visible) { + return; + } + visible = p_visible; + if (tree) { + tree->update(); + _changed_notify(); + } +} + +bool TreeItem::is_visible() { + return visible; +} + void TreeItem::uncollapse_tree() { TreeItem *t = this; while (t) { @@ -646,7 +661,7 @@ TreeItem *TreeItem::get_first_child() const { return first_child; } -TreeItem *TreeItem::get_prev_visible(bool p_wrap) { +TreeItem *TreeItem::_get_prev_visible(bool p_wrap) { TreeItem *current = this; TreeItem *prev = current->get_prev(); @@ -682,7 +697,21 @@ TreeItem *TreeItem::get_prev_visible(bool p_wrap) { return current; } -TreeItem *TreeItem::get_next_visible(bool p_wrap) { +TreeItem *TreeItem::get_prev_visible(bool p_wrap) { + TreeItem *loop = this; + TreeItem *prev = this->_get_prev_visible(p_wrap); + while (prev && !prev->is_visible()) { + prev = prev->_get_prev_visible(p_wrap); + if (prev == loop) { + // Check that we haven't looped all the way around to the start. + prev = nullptr; + break; + } + } + return prev; +} + +TreeItem *TreeItem::_get_next_visible(bool p_wrap) { TreeItem *current = this; if (!current->collapsed && current->first_child) { @@ -709,12 +738,37 @@ TreeItem *TreeItem::get_next_visible(bool p_wrap) { return current; } +TreeItem *TreeItem::get_next_visible(bool p_wrap) { + TreeItem *loop = this; + TreeItem *next = this->_get_next_visible(p_wrap); + while (next && !next->is_visible()) { + next = next->_get_next_visible(p_wrap); + if (next == loop) { + // Check that we haven't looped all the way around to the start. + next = nullptr; + break; + } + } + return next; +} + TreeItem *TreeItem::get_child(int p_idx) { _create_children_cache(); ERR_FAIL_INDEX_V(p_idx, children_cache.size(), nullptr); return children_cache.get(p_idx); } +int TreeItem::get_visible_child_count() { + _create_children_cache(); + int visible_count = 0; + for (int i = 0; i < children_cache.size(); i++) { + if (children_cache[i]->is_visible()) { + visible_count += 1; + } + } + return visible_count; +} + int TreeItem::get_child_count() { _create_children_cache(); return children_cache.size(); @@ -1256,6 +1310,9 @@ void TreeItem::_bind_methods() { ClassDB::bind_method(D_METHOD("set_collapsed", "enable"), &TreeItem::set_collapsed); ClassDB::bind_method(D_METHOD("is_collapsed"), &TreeItem::is_collapsed); + ClassDB::bind_method(D_METHOD("set_visible", "enable"), &TreeItem::set_visible); + ClassDB::bind_method(D_METHOD("is_visible"), &TreeItem::is_visible); + ClassDB::bind_method(D_METHOD("uncollapse_tree"), &TreeItem::uncollapse_tree); ClassDB::bind_method(D_METHOD("set_custom_minimum_height", "height"), &TreeItem::set_custom_minimum_height); @@ -1340,6 +1397,7 @@ void TreeItem::_bind_methods() { } ADD_PROPERTY(PropertyInfo(Variant::BOOL, "collapsed"), "set_collapsed", "is_collapsed"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "visible"), "set_visible", "is_visible"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "disable_folding"), "set_disable_folding", "is_folding_disabled"); ADD_PROPERTY(PropertyInfo(Variant::INT, "custom_minimum_height", PROPERTY_HINT_RANGE, "0,1000,1"), "set_custom_minimum_height", "get_custom_minimum_height"); @@ -1445,7 +1503,7 @@ void Tree::update_cache() { } int Tree::compute_item_height(TreeItem *p_item) const { - if (p_item == root && hide_root) { + if ((p_item == root && hide_root) || !p_item->is_visible()) { return 0; } @@ -1506,6 +1564,9 @@ int Tree::compute_item_height(TreeItem *p_item) const { } int Tree::get_item_height(TreeItem *p_item) const { + if (!p_item->is_visible()) { + return 0; + } int height = compute_item_height(p_item); height += cache.vseparation; @@ -1686,6 +1747,10 @@ int Tree::draw_item(const Point2i &p_pos, const Point2 &p_draw_ofs, const Size2 return -1; //draw no more! } + if (!p_item->is_visible()) { + return 0; + } + RID ci = get_canvas_item(); int htotal = 0; @@ -2056,7 +2121,7 @@ int Tree::draw_item(const Point2i &p_pos, const Point2 &p_draw_ofs, const Size2 } } - if (!p_item->disable_folding && !hide_folding && p_item->first_child) { //has children, draw the guide box + if (!p_item->disable_folding && !hide_folding && p_item->first_child && p_item->get_visible_child_count() != 0) { //has visible children, draw the guide box Ref<Texture2D> arrow; @@ -2099,12 +2164,12 @@ int Tree::draw_item(const Point2i &p_pos, const Point2 &p_draw_ofs, const Size2 } // Draw relationship lines. - if (cache.draw_relationship_lines > 0 && (!hide_root || c->parent != root)) { + if (cache.draw_relationship_lines > 0 && (!hide_root || c->parent != root) && c->is_visible()) { int root_ofs = children_pos.x + ((p_item->disable_folding || hide_folding) ? cache.hseparation : cache.item_margin); int parent_ofs = p_pos.x + cache.item_margin; Point2i root_pos = Point2i(root_ofs, children_pos.y + label_h / 2) - cache.offset + p_draw_ofs; - if (c->get_first_child() != nullptr) { + if (c->get_visible_child_count() > 0) { root_pos -= Point2i(cache.arrow->get_width(), 0); } @@ -2382,6 +2447,11 @@ void Tree::_range_click_timeout() { } int Tree::propagate_mouse_event(const Point2i &p_pos, int x_ofs, int y_ofs, int x_limit, bool p_double_click, TreeItem *p_item, MouseButton p_button, const Ref<InputEventWithModifiers> &p_mod) { + if (p_item && !p_item->is_visible()) { + // Skip any processing of invisible items. + return 0; + } + int item_h = compute_item_height(p_item) + cache.vseparation; bool skip = (p_item == root && hide_root); @@ -2491,7 +2561,6 @@ int Tree::propagate_mouse_event(const Point2i &p_pos, int x_ofs, int y_ofs, int cache.click_column = col; cache.click_pos = click_pos; update(); - //emit_signal(SNAME("button_pressed")); return -1; } @@ -2513,9 +2582,7 @@ int Tree::propagate_mouse_event(const Point2i &p_pos, int x_ofs, int y_ofs, int if (!c.selected || p_button == MouseButton::RIGHT) { p_item->select(col); emit_signal(SNAME("multi_selected"), p_item, col, true); - if (p_button == MouseButton::RIGHT) { - emit_signal(SNAME("item_rmb_selected"), get_local_mouse_position()); - } + emit_signal(SNAME("item_mouse_selected"), get_local_mouse_position(), p_button); //p_item->selected_signal.call(col); } else { @@ -2530,9 +2597,7 @@ int Tree::propagate_mouse_event(const Point2i &p_pos, int x_ofs, int y_ofs, int bool inrange = false; select_single_item(p_item, root, col, selected_item, &inrange); - if (p_button == MouseButton::RIGHT) { - emit_signal(SNAME("item_rmb_selected"), get_local_mouse_position()); - } + emit_signal(SNAME("item_mouse_selected"), get_local_mouse_position(), p_button); } else { int icount = _count_selected_items(root); @@ -2544,9 +2609,7 @@ int Tree::propagate_mouse_event(const Point2i &p_pos, int x_ofs, int y_ofs, int select_single_item(p_item, root, col); } - if (p_button == MouseButton::RIGHT) { - emit_signal(SNAME("item_rmb_selected"), get_local_mouse_position()); - } + emit_signal(SNAME("item_mouse_selected"), get_local_mouse_position(), p_button); } } @@ -2583,11 +2646,11 @@ int Tree::propagate_mouse_event(const Point2i &p_pos, int x_ofs, int y_ofs, int if (force_edit_checkbox_only_on_checkbox) { if (x < cache.checked->get_width()) { p_item->set_checked(col, !c.checked); - item_edited(col, p_item); + item_edited(col, p_item, p_button); } } else { p_item->set_checked(col, !c.checked); - item_edited(col, p_item); + item_edited(col, p_item, p_button); } click_handled = true; //p_item->edited_signal.call(col); @@ -2629,17 +2692,17 @@ int Tree::propagate_mouse_event(const Point2i &p_pos, int x_ofs, int y_ofs, int p_item->set_range(col, c.val + (up ? 1.0 : -1.0) * c.step); - item_edited(col, p_item); + item_edited(col, p_item, p_button); } else if (p_button == MouseButton::RIGHT) { p_item->set_range(col, (up ? c.max : c.min)); - item_edited(col, p_item); + item_edited(col, p_item, p_button); } else if (p_button == MouseButton::WHEEL_UP) { p_item->set_range(col, c.val + c.step); - item_edited(col, p_item); + item_edited(col, p_item, p_button); } else if (p_button == MouseButton::WHEEL_DOWN) { p_item->set_range(col, c.val - c.step); - item_edited(col, p_item); + item_edited(col, p_item, p_button); } //p_item->edited_signal.call(col); @@ -2670,7 +2733,7 @@ int Tree::propagate_mouse_event(const Point2i &p_pos, int x_ofs, int y_ofs, int } if (!p_item->cells[col].custom_button || !on_arrow) { - item_edited(col, p_item, p_button == MouseButton::LEFT); + item_edited(col, p_item, p_button); } click_handled = true; return -1; @@ -2717,8 +2780,8 @@ int Tree::propagate_mouse_event(const Point2i &p_pos, int x_ofs, int y_ofs, int item_h += child_h; } } - if (p_item == root && p_button == MouseButton::RIGHT) { - emit_signal(SNAME("empty_rmb"), get_local_mouse_position()); + if (p_item == root) { + emit_signal(SNAME("empty_clicked"), get_local_mouse_position(), p_button); } } @@ -3126,7 +3189,6 @@ void Tree::gui_input(const Ref<InputEvent> &p_event) { } Ref<InputEventMouseMotion> mm = p_event; - if (mm.is_valid()) { if (cache.font.is_null()) { // avoid a strange case that may corrupt stuff update_cache(); @@ -3256,18 +3318,17 @@ void Tree::gui_input(const Ref<InputEvent> &p_event) { } } - Ref<InputEventMouseButton> b = p_event; - - if (b.is_valid()) { + Ref<InputEventMouseButton> mb = p_event; + if (mb.is_valid()) { if (cache.font.is_null()) { // avoid a strange case that may corrupt stuff update_cache(); } bool rtl = is_layout_rtl(); - if (!b->is_pressed()) { - if (b->get_button_index() == MouseButton::LEFT) { - Point2 pos = b->get_position(); + if (!mb->is_pressed()) { + if (mb->get_button_index() == MouseButton::LEFT) { + Point2 pos = mb->get_position(); if (rtl) { pos.x = get_size().width - pos.x; } @@ -3302,7 +3363,7 @@ void Tree::gui_input(const Ref<InputEvent> &p_event) { warp_mouse(range_drag_capture_pos); } else { Rect2 rect = get_selected()->get_meta("__focus_rect"); - Point2 mpos = b->get_position(); + Point2 mpos = mb->get_position(); int icon_size_x = 0; Ref<Texture2D> icon = get_selected()->get_icon(selected_col); if (icon.is_valid()) { @@ -3330,17 +3391,6 @@ void Tree::gui_input(const Ref<InputEvent> &p_event) { pressing_for_editor = false; } - if (cache.click_type == Cache::CLICK_BUTTON && cache.click_item != nullptr) { - // make sure in case of wrong reference after reconstructing whole TreeItems - cache.click_item = get_item_at_position(cache.click_pos); - emit_signal(SNAME("button_pressed"), cache.click_item, cache.click_column, cache.click_id); - } - cache.click_type = Cache::CLICK_NONE; - cache.click_index = -1; - cache.click_id = -1; - cache.click_item = nullptr; - cache.click_column = 0; - if (drag_touching) { if (drag_speed == 0) { drag_touching_deaccel = false; @@ -3350,8 +3400,20 @@ void Tree::gui_input(const Ref<InputEvent> &p_event) { drag_touching_deaccel = true; } } - update(); } + + if (cache.click_type == Cache::CLICK_BUTTON && cache.click_item != nullptr) { + // make sure in case of wrong reference after reconstructing whole TreeItems + cache.click_item = get_item_at_position(cache.click_pos); + emit_signal("button_clicked", cache.click_item, cache.click_column, cache.click_id, mb->get_button_index()); + } + + cache.click_type = Cache::CLICK_NONE; + cache.click_index = -1; + cache.click_id = -1; + cache.click_item = nullptr; + cache.click_column = 0; + update(); return; } @@ -3359,12 +3421,12 @@ void Tree::gui_input(const Ref<InputEvent> &p_event) { return; } - switch (b->get_button_index()) { + switch (mb->get_button_index()) { case MouseButton::RIGHT: case MouseButton::LEFT: { Ref<StyleBox> bg = cache.bg; - Point2 pos = b->get_position(); + Point2 pos = mb->get_position(); if (rtl) { pos.x = get_size().width - pos.x; } @@ -3374,7 +3436,7 @@ void Tree::gui_input(const Ref<InputEvent> &p_event) { pos.y -= _get_title_button_height(); if (pos.y < 0) { - if (b->get_button_index() == MouseButton::LEFT) { + if (mb->get_button_index() == MouseButton::LEFT) { pos.x += cache.offset.x; int len = 0; for (int i = 0; i < columns.size(); i++) { @@ -3391,10 +3453,8 @@ void Tree::gui_input(const Ref<InputEvent> &p_event) { break; } } + if (!root || (!root->get_first_child() && hide_root)) { - if (b->get_button_index() == MouseButton::RIGHT && allow_rmb_select) { - emit_signal(SNAME("empty_tree_rmb_selected"), get_local_mouse_position()); - } break; } @@ -3409,17 +3469,17 @@ void Tree::gui_input(const Ref<InputEvent> &p_event) { cache.rtl = is_layout_rtl(); blocked++; - propagate_mouse_event(pos + cache.offset, 0, 0, x_limit + cache.offset.width, b->is_double_click(), root, b->get_button_index(), b); + propagate_mouse_event(pos + cache.offset, 0, 0, x_limit + cache.offset.width, mb->is_double_click(), root, mb->get_button_index(), mb); blocked--; if (pressing_for_editor) { - pressing_pos = b->get_position(); + pressing_pos = mb->get_position(); if (rtl) { pressing_pos.x = get_size().width - pressing_pos.x; } } - if (b->get_button_index() == MouseButton::RIGHT) { + if (mb->get_button_index() == MouseButton::RIGHT) { break; } @@ -3442,8 +3502,8 @@ void Tree::gui_input(const Ref<InputEvent> &p_event) { set_physics_process_internal(true); } - if (b->get_button_index() == MouseButton::LEFT) { - if (get_item_at_position(b->get_position()) == nullptr && !b->is_shift_pressed() && !b->is_ctrl_pressed() && !b->is_command_pressed()) { + if (mb->get_button_index() == MouseButton::LEFT) { + if (get_item_at_position(mb->get_position()) == nullptr && !mb->is_shift_pressed() && !mb->is_ctrl_pressed() && !mb->is_command_pressed()) { emit_signal(SNAME("nothing_selected")); } } @@ -3457,7 +3517,7 @@ void Tree::gui_input(const Ref<InputEvent> &p_event) { } break; case MouseButton::WHEEL_UP: { double prev_value = v_scroll->get_value(); - v_scroll->set_value(v_scroll->get_value() - v_scroll->get_page() * b->get_factor() / 8); + v_scroll->set_value(v_scroll->get_value() - v_scroll->get_page() * mb->get_factor() / 8); if (v_scroll->get_value() != prev_value) { accept_event(); } @@ -3465,7 +3525,7 @@ void Tree::gui_input(const Ref<InputEvent> &p_event) { } break; case MouseButton::WHEEL_DOWN: { double prev_value = v_scroll->get_value(); - v_scroll->set_value(v_scroll->get_value() + v_scroll->get_page() * b->get_factor() / 8); + v_scroll->set_value(v_scroll->get_value() + v_scroll->get_page() * mb->get_factor() / 8); if (v_scroll->get_value() != prev_value) { accept_event(); } @@ -3911,16 +3971,15 @@ TreeItem *Tree::get_last_item() const { return last; } -void Tree::item_edited(int p_column, TreeItem *p_item, bool p_lmb) { +void Tree::item_edited(int p_column, TreeItem *p_item, MouseButton p_custom_mouse_index) { edited_item = p_item; edited_col = p_column; if (p_item != nullptr && p_column >= 0 && p_column < p_item->cells.size()) { edited_item->cells.write[p_column].dirty = true; } - if (p_lmb) { - emit_signal(SNAME("item_edited")); - } else { - emit_signal(SNAME("item_rmb_edited")); + emit_signal(SNAME("item_edited")); + if (p_custom_mouse_index != MouseButton::NONE) { + emit_signal(SNAME("custom_item_clicked"), p_custom_mouse_index); } } @@ -4147,7 +4206,7 @@ int Tree::get_column_minimum_width(int p_column) const { depth += 1; } else { TreeItem *common_parent = item->get_parent(); - while (common_parent != next->get_parent()) { + while (common_parent != next->get_parent() && common_parent) { common_parent = common_parent->get_parent(); depth -= 1; } @@ -4464,7 +4523,8 @@ Point2 Tree::get_scroll() const { } void Tree::scroll_to_item(TreeItem *p_item, bool p_center_on_item) { - if (!is_visible_in_tree()) { + ERR_FAIL_NULL(p_item); + if (!is_visible_in_tree() || !p_item->is_visible()) { return; // Hack to work around crash in get_item_rect() if Tree is not in tree. } @@ -4588,7 +4648,7 @@ void Tree::_do_incr_search(const String &p_add) { TreeItem *Tree::_find_item_at_pos(TreeItem *p_item, const Point2 &p_pos, int &r_column, int &h, int §ion) const { Point2 pos = p_pos; - if (root != p_item || !hide_root) { + if ((root != p_item || !hide_root) && p_item->is_visible()) { h = compute_item_height(p_item) + cache.vseparation; if (pos.y < h) { if (drop_mode_flags == DROP_MODE_ON_ITEM) { @@ -4621,7 +4681,7 @@ TreeItem *Tree::_find_item_at_pos(TreeItem *p_item, const Point2 &p_pos, int &r_ h = 0; } - if (p_item->is_collapsed()) { + if (p_item->is_collapsed() || !p_item->is_visible()) { return nullptr; // do not try children, it's collapsed } @@ -4965,17 +5025,15 @@ void Tree::_bind_methods() { ADD_SIGNAL(MethodInfo("item_selected")); 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_mouse_selected", PropertyInfo(Variant::VECTOR2, "position"), PropertyInfo(Variant::INT, "mouse_button_index"))); + ADD_SIGNAL(MethodInfo("empty_clicked", PropertyInfo(Variant::VECTOR2, "position"), PropertyInfo(Variant::INT, "mouse_button_index"))); ADD_SIGNAL(MethodInfo("item_edited")); - ADD_SIGNAL(MethodInfo("item_rmb_edited")); + ADD_SIGNAL(MethodInfo("custom_item_clicked", PropertyInfo(Variant::INT, "mouse_button_index"))); ADD_SIGNAL(MethodInfo("item_custom_button_pressed")); ADD_SIGNAL(MethodInfo("item_double_clicked")); ADD_SIGNAL(MethodInfo("item_collapsed", PropertyInfo(Variant::OBJECT, "item", PROPERTY_HINT_RESOURCE_TYPE, "TreeItem"))); ADD_SIGNAL(MethodInfo("check_propagated_to_item", PropertyInfo(Variant::OBJECT, "item", PROPERTY_HINT_RESOURCE_TYPE, "TreeItem"), PropertyInfo(Variant::INT, "column"))); - //ADD_SIGNAL( MethodInfo("item_double_clicked" ) ); - ADD_SIGNAL(MethodInfo("button_pressed", PropertyInfo(Variant::OBJECT, "item", PROPERTY_HINT_RESOURCE_TYPE, "TreeItem"), PropertyInfo(Variant::INT, "column"), PropertyInfo(Variant::INT, "id"))); + ADD_SIGNAL(MethodInfo("button_clicked", PropertyInfo(Variant::OBJECT, "item", PROPERTY_HINT_RESOURCE_TYPE, "TreeItem"), PropertyInfo(Variant::INT, "column"), PropertyInfo(Variant::INT, "id"), PropertyInfo(Variant::INT, "mouse_button_index"))); ADD_SIGNAL(MethodInfo("custom_popup_edited", PropertyInfo(Variant::BOOL, "arrow_clicked"))); ADD_SIGNAL(MethodInfo("item_activated")); ADD_SIGNAL(MethodInfo("column_title_pressed", PropertyInfo(Variant::INT, "column"))); diff --git a/scene/gui/tree.h b/scene/gui/tree.h index 8ee2a3c382..0a8dd3204a 100644 --- a/scene/gui/tree.h +++ b/scene/gui/tree.h @@ -124,6 +124,7 @@ private: Vector<Cell> cells; bool collapsed = false; // won't show children + bool visible = true; bool disable_folding = false; int custom_min_height = 0; @@ -209,6 +210,9 @@ private: void _propagate_check_through_children(int p_column, bool p_checked, bool p_emit_signal); void _propagate_check_through_parents(int p_column, bool p_emit_signal); + TreeItem *_get_prev_visible(bool p_wrap = false); + TreeItem *_get_next_visible(bool p_wrap = false); + public: void set_text(int p_column, String p_text); String get_text(int p_column) const; @@ -273,6 +277,9 @@ public: void set_collapsed(bool p_collapsed); bool is_collapsed(); + void set_visible(bool p_visible); + bool is_visible(); + void uncollapse_tree(); void set_custom_minimum_height(int p_height); @@ -335,6 +342,7 @@ public: TreeItem *get_next_visible(bool p_wrap = false); TreeItem *get_child(int p_idx); + int get_visible_child_count(); int get_child_count(); Array get_children(); int get_index(); @@ -466,7 +474,7 @@ private: void _notification(int p_what); - void item_edited(int p_column, TreeItem *p_item, bool p_lmb = true); + void item_edited(int p_column, TreeItem *p_item, MouseButton p_custom_mouse_index = MouseButton::NONE); void item_changed(int p_column, TreeItem *p_item); void item_selected(int p_column, TreeItem *p_item); void item_deselected(int p_column, TreeItem *p_item); |