diff options
Diffstat (limited to 'scene/gui')
-rw-r--r-- | scene/gui/button.cpp | 5 | ||||
-rw-r--r-- | scene/gui/code_edit.cpp | 43 | ||||
-rw-r--r-- | scene/gui/control.cpp | 5 | ||||
-rw-r--r-- | scene/gui/graph_edit.cpp | 6 | ||||
-rw-r--r-- | scene/gui/line_edit.cpp | 17 | ||||
-rw-r--r-- | scene/gui/line_edit.h | 4 | ||||
-rw-r--r-- | scene/gui/option_button.cpp | 48 | ||||
-rw-r--r-- | scene/gui/popup_menu.cpp | 6 | ||||
-rw-r--r-- | scene/gui/tab_bar.cpp | 552 | ||||
-rw-r--r-- | scene/gui/tab_bar.h | 16 | ||||
-rw-r--r-- | scene/gui/text_edit.cpp | 16 | ||||
-rw-r--r-- | scene/gui/text_edit.h | 6 | ||||
-rw-r--r-- | scene/gui/texture_button.cpp | 5 | ||||
-rw-r--r-- | scene/gui/tree.cpp | 12 | ||||
-rw-r--r-- | scene/gui/tree.h | 10 |
15 files changed, 493 insertions, 258 deletions
diff --git a/scene/gui/button.cpp b/scene/gui/button.cpp index 552dd28513..3ed1b873af 100644 --- a/scene/gui/button.cpp +++ b/scene/gui/button.cpp @@ -197,6 +197,8 @@ void Button::_notification(int p_what) { color = get_theme_color(SNAME("font_disabled_color")); if (has_theme_color(SNAME("icon_disabled_color"))) { color_icon = get_theme_color(SNAME("icon_disabled_color")); + } else { + color_icon.a = 0.4; } } break; @@ -232,9 +234,6 @@ void Button::_notification(int p_what) { } if (!_icon.is_null()) { int valign = size.height - style->get_minimum_size().y; - if (is_disabled()) { - color_icon.a = 0.4; - } float icon_ofs_region = 0.0; Point2 style_offset; diff --git a/scene/gui/code_edit.cpp b/scene/gui/code_edit.cpp index 5511a1d910..22def607ed 100644 --- a/scene/gui/code_edit.cpp +++ b/scene/gui/code_edit.cpp @@ -571,6 +571,8 @@ Control::CursorShape CodeEdit::get_cursor_shape(const Point2 &p_pos) const { // Overridable actions void CodeEdit::_handle_unicode_input_internal(const uint32_t p_unicode) { bool had_selection = has_selection(); + String selection_text = (had_selection ? get_selected_text() : ""); + if (had_selection) { begin_complex_operation(); delete_selection(); @@ -591,27 +593,38 @@ void CodeEdit::_handle_unicode_input_internal(const uint32_t p_unicode) { if (auto_brace_completion_enabled) { int cl = get_caret_line(); int cc = get_caret_column(); - int caret_move_offset = 1; - - int post_brace_pair = cc < get_line(cl).length() ? _get_auto_brace_pair_close_at_pos(cl, cc) : -1; - if (has_string_delimiter(chr) && cc > 0 && _is_char(get_line(cl)[cc - 1]) && post_brace_pair == -1) { - insert_text_at_caret(chr); - } else if (cc < get_line(cl).length() && _is_char(get_line(cl)[cc])) { - insert_text_at_caret(chr); - } else if (post_brace_pair != -1 && auto_brace_completion_pairs[post_brace_pair].close_key[0] == chr[0]) { - caret_move_offset = auto_brace_completion_pairs[post_brace_pair].close_key.length(); - } else if (is_in_comment(cl, cc) != -1 || (is_in_string(cl, cc) != -1 && has_string_delimiter(chr))) { + if (had_selection) { insert_text_at_caret(chr); + + String close_key = get_auto_brace_completion_close_key(chr); + if (!close_key.is_empty()) { + insert_text_at_caret(selection_text + close_key); + set_caret_column(get_caret_column() - 1); + } } else { - insert_text_at_caret(chr); + int caret_move_offset = 1; + + int post_brace_pair = cc < get_line(cl).length() ? _get_auto_brace_pair_close_at_pos(cl, cc) : -1; + + if (has_string_delimiter(chr) && cc > 0 && _is_char(get_line(cl)[cc - 1]) && post_brace_pair == -1) { + insert_text_at_caret(chr); + } else if (cc < get_line(cl).length() && _is_char(get_line(cl)[cc])) { + insert_text_at_caret(chr); + } else if (post_brace_pair != -1 && auto_brace_completion_pairs[post_brace_pair].close_key[0] == chr[0]) { + caret_move_offset = auto_brace_completion_pairs[post_brace_pair].close_key.length(); + } else if (is_in_comment(cl, cc) != -1 || (is_in_string(cl, cc) != -1 && has_string_delimiter(chr))) { + insert_text_at_caret(chr); + } else { + insert_text_at_caret(chr); - int pre_brace_pair = _get_auto_brace_pair_open_at_pos(cl, cc + 1); - if (pre_brace_pair != -1) { - insert_text_at_caret(auto_brace_completion_pairs[pre_brace_pair].close_key); + int pre_brace_pair = _get_auto_brace_pair_open_at_pos(cl, cc + 1); + if (pre_brace_pair != -1) { + insert_text_at_caret(auto_brace_completion_pairs[pre_brace_pair].close_key); + } } + set_caret_column(cc + caret_move_offset); } - set_caret_column(cc + caret_move_offset); } else { insert_text_at_caret(chr); } diff --git a/scene/gui/control.cpp b/scene/gui/control.cpp index 562ea8a8f7..7ebc5c27f8 100644 --- a/scene/gui/control.cpp +++ b/scene/gui/control.cpp @@ -576,6 +576,11 @@ void Control::_update_canvas_item_transform() { Transform2D xform = _get_internal_transform(); xform[2] += get_position(); + // We use a little workaround to avoid flickering when moving the pivot with _edit_set_pivot() + if (is_inside_tree() && Math::abs(Math::sin(data.rotation * 4.0f)) < 0.00001f && get_viewport()->is_snap_controls_to_pixels_enabled()) { + xform[2] = xform[2].round(); + } + RenderingServer::get_singleton()->canvas_item_set_transform(get_canvas_item(), xform); } diff --git a/scene/gui/graph_edit.cpp b/scene/gui/graph_edit.cpp index 2be76473b0..95575a8226 100644 --- a/scene/gui/graph_edit.cpp +++ b/scene/gui/graph_edit.cpp @@ -1692,8 +1692,9 @@ int GraphEdit::_set_operations(SET_OPERATIONS p_operation, Set<StringName> &r_u, switch (p_operation) { case GraphEdit::IS_EQUAL: { for (Set<StringName>::Element *E = r_u.front(); E; E = E->next()) { - if (!r_v.has(E->get())) + if (!r_v.has(E->get())) { return 0; + } } return r_u.size() == r_v.size(); } break; @@ -1702,8 +1703,9 @@ int GraphEdit::_set_operations(SET_OPERATIONS p_operation, Set<StringName> &r_u, return 1; } for (Set<StringName>::Element *E = r_u.front(); E; E = E->next()) { - if (!r_v.has(E->get())) + if (!r_v.has(E->get())) { return 0; + } } return 1; } break; diff --git a/scene/gui/line_edit.cpp b/scene/gui/line_edit.cpp index 88953fa7db..3aae3377bc 100644 --- a/scene/gui/line_edit.cpp +++ b/scene/gui/line_edit.cpp @@ -752,7 +752,7 @@ void LineEdit::_notification(int p_what) { // Draw placeholder color. if (using_placeholder) { - font_color.a *= placeholder_alpha; + font_color = get_theme_color(SNAME("font_placeholder_color")); } bool display_clear_icon = !using_placeholder && is_editable() && clear_button_enabled; @@ -1476,15 +1476,6 @@ String LineEdit::get_placeholder() const { return placeholder; } -void LineEdit::set_placeholder_alpha(float p_alpha) { - placeholder_alpha = p_alpha; - update(); -} - -float LineEdit::get_placeholder_alpha() const { - return placeholder_alpha; -} - void LineEdit::set_caret_column(int p_column) { if (p_column > (int)text.length()) { p_column = text.length(); @@ -2245,8 +2236,6 @@ void LineEdit::_bind_methods() { ClassDB::bind_method(D_METHOD("get_structured_text_bidi_override_options"), &LineEdit::get_structured_text_bidi_override_options); ClassDB::bind_method(D_METHOD("set_placeholder", "text"), &LineEdit::set_placeholder); ClassDB::bind_method(D_METHOD("get_placeholder"), &LineEdit::get_placeholder); - ClassDB::bind_method(D_METHOD("set_placeholder_alpha", "alpha"), &LineEdit::set_placeholder_alpha); - ClassDB::bind_method(D_METHOD("get_placeholder_alpha"), &LineEdit::get_placeholder_alpha); ClassDB::bind_method(D_METHOD("set_caret_column", "position"), &LineEdit::set_caret_column); ClassDB::bind_method(D_METHOD("get_caret_column"), &LineEdit::get_caret_column); ClassDB::bind_method(D_METHOD("get_scroll_offset"), &LineEdit::get_scroll_offset); @@ -2328,6 +2317,7 @@ void LineEdit::_bind_methods() { BIND_ENUM_CONSTANT(MENU_MAX); ADD_PROPERTY(PropertyInfo(Variant::STRING, "text"), "set_text", "get_text"); + ADD_PROPERTY(PropertyInfo(Variant::STRING, "placeholder_text"), "set_placeholder", "get_placeholder"); ADD_PROPERTY(PropertyInfo(Variant::INT, "alignment", PROPERTY_HINT_ENUM, "Left,Center,Right,Fill"), "set_horizontal_alignment", "get_horizontal_alignment"); ADD_PROPERTY(PropertyInfo(Variant::INT, "max_length", PROPERTY_HINT_RANGE, "0,1000,1,or_greater"), "set_max_length", "get_max_length"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "editable"), "set_editable", "is_editable"); @@ -2349,9 +2339,6 @@ void LineEdit::_bind_methods() { ADD_GROUP("Structured Text", "structured_text_"); ADD_PROPERTY(PropertyInfo(Variant::INT, "structured_text_bidi_override", PROPERTY_HINT_ENUM, "Default,URI,File,Email,List,None,Custom"), "set_structured_text_bidi_override", "get_structured_text_bidi_override"); ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "structured_text_bidi_override_options"), "set_structured_text_bidi_override_options", "get_structured_text_bidi_override_options"); - ADD_GROUP("Placeholder", "placeholder_"); - ADD_PROPERTY(PropertyInfo(Variant::STRING, "placeholder_text"), "set_placeholder", "get_placeholder"); - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "placeholder_alpha", PROPERTY_HINT_RANGE, "0,1,0.001"), "set_placeholder_alpha", "get_placeholder_alpha"); ADD_GROUP("Caret", "caret_"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "caret_blink"), "set_caret_blink_enabled", "is_caret_blink_enabled"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "caret_blink_speed", PROPERTY_HINT_RANGE, "0.1,10,0.01"), "set_caret_blink_speed", "get_caret_blink_speed"); diff --git a/scene/gui/line_edit.h b/scene/gui/line_edit.h index 0c313f71c2..1519c09d73 100644 --- a/scene/gui/line_edit.h +++ b/scene/gui/line_edit.h @@ -82,7 +82,6 @@ private: String placeholder; String placeholder_translated; String secret_character = "*"; - float placeholder_alpha = 0.6; String ime_text; Point2 ime_selection; @@ -262,9 +261,6 @@ public: void set_placeholder(String p_text); String get_placeholder() const; - void set_placeholder_alpha(float p_alpha); - float get_placeholder_alpha() const; - void set_caret_column(int p_column); int get_caret_column() const; diff --git a/scene/gui/option_button.cpp b/scene/gui/option_button.cpp index 90bb316448..c80de04c01 100644 --- a/scene/gui/option_button.cpp +++ b/scene/gui/option_button.cpp @@ -32,6 +32,8 @@ #include "core/string/print_string.h" +static const int NONE_SELECTED = -1; + Size2 OptionButton::get_minimum_size() const { Size2 minsize = Button::get_minimum_size(); @@ -119,7 +121,7 @@ bool OptionButton::_set(const StringName &p_name, const Variant &p_value) { int idx = components[1].get_slice("_", 1).to_int(); if (idx == current) { // Force refreshing currently displayed item. - current = -1; + current = NONE_SELECTED; _select(idx, false); } @@ -154,7 +156,7 @@ void OptionButton::_get_property_list(List<PropertyInfo> *p_list) const { pi.usage &= ~(!popup->is_item_checked(i) ? PROPERTY_USAGE_STORAGE : 0); p_list->push_back(pi); - pi = PropertyInfo(Variant::INT, vformat("popup/item_%d/id", i), PROPERTY_HINT_RANGE, "1,10,1,or_greater"); + pi = PropertyInfo(Variant::INT, vformat("popup/item_%d/id", i), PROPERTY_HINT_RANGE, "0,10,1,or_greater"); p_list->push_back(pi); pi = PropertyInfo(Variant::BOOL, vformat("popup/item_%d/disabled", i)); @@ -234,6 +236,10 @@ Ref<Texture2D> OptionButton::get_item_icon(int p_idx) const { } int OptionButton::get_item_id(int p_idx) const { + if (p_idx == NONE_SELECTED) { + return NONE_SELECTED; + } + return popup->get_item_id(p_idx); } @@ -266,26 +272,33 @@ void OptionButton::add_separator() { void OptionButton::clear() { popup->clear(); set_text(""); - current = -1; + current = NONE_SELECTED; } void OptionButton::_select(int p_which, bool p_emit) { - if (p_which < 0) { - return; - } if (p_which == current) { return; } - ERR_FAIL_INDEX(p_which, popup->get_item_count()); + if (p_which == NONE_SELECTED) { + for (int i = 0; i < popup->get_item_count(); i++) { + popup->set_item_checked(i, false); + } - for (int i = 0; i < popup->get_item_count(); i++) { - popup->set_item_checked(i, i == p_which); - } + current = NONE_SELECTED; + set_text(""); + set_icon(NULL); + } else { + ERR_FAIL_INDEX(p_which, popup->get_item_count()); - current = p_which; - set_text(popup->get_item_text(current)); - set_icon(popup->get_item_icon(current)); + for (int i = 0; i < popup->get_item_count(); i++) { + popup->set_item_checked(i, i == p_which); + } + + current = p_which; + set_text(popup->get_item_text(current)); + set_icon(popup->get_item_icon(current)); + } if (is_inside_tree() && p_emit) { emit_signal(SNAME("item_selected"), current); @@ -293,7 +306,7 @@ void OptionButton::_select(int p_which, bool p_emit) { } void OptionButton::_select_int(int p_which) { - if (p_which < 0 || p_which >= popup->get_item_count()) { + if (p_which < NONE_SELECTED || p_which >= popup->get_item_count()) { return; } _select(p_which, false); @@ -308,10 +321,6 @@ int OptionButton::get_selected() const { } int OptionButton::get_selected_id() const { - int idx = get_selected(); - if (idx < 0) { - return 0; - } return get_item_id(current); } @@ -325,6 +334,9 @@ Variant OptionButton::get_selected_metadata() const { void OptionButton::remove_item(int p_idx) { popup->remove_item(p_idx); + if (current == p_idx) { + _select(NONE_SELECTED); + } } PopupMenu *OptionButton::get_popup() const { diff --git a/scene/gui/popup_menu.cpp b/scene/gui/popup_menu.cpp index c9fddc3e17..fc853a3df4 100644 --- a/scene/gui/popup_menu.cpp +++ b/scene/gui/popup_menu.cpp @@ -810,7 +810,7 @@ void PopupMenu::_notification(int p_what) { #define ITEM_SETUP_WITH_ACCEL(p_label, p_id, p_accel) \ item.text = p_label; \ item.xl_text = atr(p_label); \ - item.id = p_id == -1 ? items.size() : p_id; \ + item.id = p_id == -1 ? items.size() - 1 : p_id; \ item.accel = p_accel; void PopupMenu::add_item(const String &p_label, int p_id, Key p_accel) { @@ -892,7 +892,7 @@ void PopupMenu::add_multistate_item(const String &p_label, int p_max_states, int _ref_shortcut(p_shortcut); \ item.text = p_shortcut->get_name(); \ item.xl_text = atr(item.text); \ - item.id = p_id == -1 ? items.size() : p_id; \ + item.id = p_id == -1 ? items.size() - 1 : p_id; \ item.shortcut = p_shortcut; \ item.shortcut_is_global = p_global; @@ -961,7 +961,7 @@ void PopupMenu::add_submenu_item(const String &p_label, const String &p_submenu, Item item; item.text = p_label; item.xl_text = atr(p_label); - item.id = p_id == -1 ? items.size() : p_id; + item.id = p_id == -1 ? items.size() - 1 : p_id; item.submenu = p_submenu; items.push_back(item); _shape_item(items.size() - 1); diff --git a/scene/gui/tab_bar.cpp b/scene/gui/tab_bar.cpp index 9da030f0a2..5a551ec5a5 100644 --- a/scene/gui/tab_bar.cpp +++ b/scene/gui/tab_bar.cpp @@ -38,48 +38,71 @@ #include "scene/gui/texture_rect.h" Size2 TabBar::get_minimum_size() const { + Size2 ms; + + if (tabs.is_empty()) { + return ms; + } + Ref<StyleBox> tab_unselected = get_theme_stylebox(SNAME("tab_unselected")); Ref<StyleBox> tab_selected = get_theme_stylebox(SNAME("tab_selected")); Ref<StyleBox> tab_disabled = get_theme_stylebox(SNAME("tab_disabled")); + Ref<StyleBox> button_highlight = get_theme_stylebox(SNAME("button_highlight")); + Ref<Texture2D> close = get_theme_icon(SNAME("close")); + int hseparation = get_theme_constant(SNAME("hseparation")); int y_margin = MAX(MAX(tab_unselected->get_minimum_size().height, tab_selected->get_minimum_size().height), tab_disabled->get_minimum_size().height); - Size2 ms(0, 0); - for (int i = 0; i < tabs.size(); i++) { - Ref<Texture2D> tex = tabs[i].icon; - if (tex.is_valid()) { - ms.height = MAX(ms.height, tex->get_size().height); - if (!tabs[i].text.is_empty()) { - ms.width += get_theme_constant(SNAME("hseparation")); - } + if (tabs[i].hidden) { + continue; } - ms.width += Math::ceil(tabs[i].text_buf->get_size().x); - ms.height = MAX(ms.height, tabs[i].text_buf->get_size().y + y_margin); + int ofs = ms.width; + Ref<StyleBox> style; if (tabs[i].disabled) { - ms.width += tab_disabled->get_minimum_size().width; + style = tab_disabled; } else if (current == i) { - ms.width += tab_selected->get_minimum_size().width; + style = tab_selected; } else { - ms.width += tab_unselected->get_minimum_size().width; + style = tab_unselected; + } + ms.width += style->get_minimum_size().width; + + Ref<Texture2D> tex = tabs[i].icon; + if (tex.is_valid()) { + ms.height = MAX(ms.height, tex->get_size().height); + ms.width += tex->get_size().width + hseparation; } + if (!tabs[i].text.is_empty()) { + ms.width += tabs[i].size_text + hseparation; + } + ms.height = MAX(ms.height, tabs[i].text_buf->get_size().y + y_margin); + + bool close_visible = cb_displaypolicy == CLOSE_BUTTON_SHOW_ALWAYS || (cb_displaypolicy == CLOSE_BUTTON_SHOW_ACTIVE_ONLY && i == current); + if (tabs[i].right_button.is_valid()) { Ref<Texture2D> rb = tabs[i].right_button; - Size2 bms = rb->get_size(); - bms.width += get_theme_constant(SNAME("hseparation")); - ms.width += bms.width; - ms.height = MAX(bms.height + tab_unselected->get_minimum_size().height, ms.height); + + if (close_visible) { + ms.width += button_highlight->get_minimum_size().width + rb->get_width(); + } else { + ms.width += button_highlight->get_margin(SIDE_LEFT) + rb->get_width() + hseparation; + } + + ms.height = MAX(rb->get_height() + style->get_minimum_size().height, ms.height); + } + + if (close_visible) { + ms.width += button_highlight->get_margin(SIDE_LEFT) + close->get_width() + hseparation; + + ms.height = MAX(close->get_height() + style->get_minimum_size().height, ms.height); } - if (cb_displaypolicy == CLOSE_BUTTON_SHOW_ALWAYS || (cb_displaypolicy == CLOSE_BUTTON_SHOW_ACTIVE_ONLY && i == current)) { - Ref<Texture2D> cb = get_theme_icon(SNAME("close")); - Size2 bms = cb->get_size(); - bms.width += get_theme_constant(SNAME("hseparation")); - ms.width += bms.width; - ms.height = MAX(bms.height + tab_unselected->get_minimum_size().height, ms.height); + if (ms.width - ofs > style->get_minimum_size().width) { + ms.width -= hseparation; } } @@ -165,8 +188,7 @@ void TabBar::gui_input(const Ref<InputEvent> &p_event) { if (rb_pressing && !mb->is_pressed() && mb->get_button_index() == MouseButton::LEFT) { if (rb_hover != -1) { - // Right mouse button clicked. - emit_signal(SNAME("tab_rmb_clicked"), rb_hover); + emit_signal(SNAME("tab_button_pressed"), rb_hover); } rb_pressing = false; @@ -175,7 +197,6 @@ void TabBar::gui_input(const Ref<InputEvent> &p_event) { if (cb_pressing && !mb->is_pressed() && mb->get_button_index() == MouseButton::LEFT) { if (cb_hover != -1) { - // Close button pressed. emit_signal(SNAME("tab_close_pressed"), cb_hover); } @@ -184,7 +205,6 @@ void TabBar::gui_input(const Ref<InputEvent> &p_event) { } if (mb->is_pressed() && (mb->get_button_index() == MouseButton::LEFT || (select_with_rmb && mb->get_button_index() == MouseButton::RIGHT))) { - // Clicks. Point2 pos = mb->get_position(); if (buttons_visible) { @@ -234,6 +254,10 @@ void TabBar::gui_input(const Ref<InputEvent> &p_event) { int found = -1; for (int i = offset; i <= max_drawn_tab; i++) { + if (tabs[i].hidden) { + continue; + } + if (tabs[i].rb_rect.has_point(pos)) { rb_pressing = true; update(); @@ -256,6 +280,12 @@ void TabBar::gui_input(const Ref<InputEvent> &p_event) { if (found != -1) { set_current_tab(found); + + if (mb->get_button_index() == MouseButton::RIGHT) { + // Right mouse button clicked. + emit_signal(SNAME("tab_rmb_clicked"), found); + } + emit_signal(SNAME("tab_clicked"), found); } } @@ -275,13 +305,12 @@ void TabBar::_shape(int p_tab) { tabs.write[p_tab].text_buf->set_direction((TextServer::Direction)tabs[p_tab].text_direction); } - tabs.write[p_tab].text_buf->add_string(tabs.write[p_tab].xl_text, font, font_size, tabs[p_tab].opentype_features, !tabs[p_tab].language.is_empty() ? tabs[p_tab].language : TranslationServer::get_singleton()->get_tool_locale()); + tabs.write[p_tab].text_buf->add_string(tabs[p_tab].xl_text, font, font_size, tabs[p_tab].opentype_features, !tabs[p_tab].language.is_empty() ? tabs[p_tab].language : TranslationServer::get_singleton()->get_tool_locale()); } void TabBar::_notification(int p_what) { switch (p_what) { case NOTIFICATION_LAYOUT_DIRECTION_CHANGED: { - _update_cache(); update(); } break; case NOTIFICATION_THEME_CHANGED: @@ -289,14 +318,19 @@ void TabBar::_notification(int p_what) { for (int i = 0; i < tabs.size(); ++i) { _shape(i); } - _update_cache(); - update_minimum_size(); - update(); - } break; + + [[fallthrough]]; + } case NOTIFICATION_RESIZED: { + int ofs_old = offset; + int max_old = max_drawn_tab; + _update_cache(); _ensure_no_over_offset(); - ensure_tab_visible(current); + + if (scroll_to_selected && (offset != ofs_old || max_drawn_tab != max_old)) { + ensure_tab_visible(current); + } } break; case NOTIFICATION_DRAW: { if (tabs.is_empty()) { @@ -322,6 +356,10 @@ void TabBar::_notification(int p_what) { // Draw unselected tabs in the back. for (int i = offset; i <= max_drawn_tab; i++) { + if (tabs[i].hidden) { + continue; + } + if (i != current) { Ref<StyleBox> sb; Color col; @@ -344,14 +382,14 @@ void TabBar::_notification(int p_what) { } // Draw selected tab in the front, but only if it's visible. - if (current >= offset && current <= max_drawn_tab) { + if (current >= offset && current <= max_drawn_tab && !tabs[current].hidden) { Ref<StyleBox> sb = tabs[current].disabled ? tab_disabled : tab_selected; float x = rtl ? size.width - tabs[current].ofs_cache - tabs[current].size_cache : tabs[current].ofs_cache; _draw_tab(sb, font_selected_color, current, x); } - if (offset > 0 || missing_right) { + if (buttons_visible) { int vofs = (get_size().height - incr->get_size().height) / 2; if (rtl) { @@ -386,47 +424,52 @@ void TabBar::_notification(int p_what) { void TabBar::_draw_tab(Ref<StyleBox> &p_tab_style, Color &p_font_color, int p_index, float p_x) { RID ci = get_canvas_item(); + bool rtl = is_layout_rtl(); Color font_outline_color = get_theme_color(SNAME("font_outline_color")); int outline_size = get_theme_constant(SNAME("outline_size")); + int hseparation = get_theme_constant(SNAME("hseparation")); - Tab tab = tabs[p_index]; - - Rect2 sb_rect = Rect2(p_x, 0, tab.size_cache, get_size().height); + Rect2 sb_rect = Rect2(p_x, 0, tabs[p_index].size_cache, get_size().height); p_tab_style->draw(ci, sb_rect); - p_x += p_tab_style->get_margin(SIDE_LEFT); + p_x += rtl ? tabs[p_index].size_cache - p_tab_style->get_margin(SIDE_LEFT) : p_tab_style->get_margin(SIDE_LEFT); Size2i sb_ms = p_tab_style->get_minimum_size(); - Ref<Texture2D> icon = tab.icon; + // Draw the icon. + Ref<Texture2D> icon = tabs[p_index].icon; if (icon.is_valid()) { - icon->draw(ci, Point2i(p_x, p_tab_style->get_margin(SIDE_TOP) + ((sb_rect.size.y - sb_ms.y) - icon->get_height()) / 2)); + icon->draw(ci, Point2i(rtl ? p_x - icon->get_width() : p_x, p_tab_style->get_margin(SIDE_TOP) + ((sb_rect.size.y - sb_ms.y) - icon->get_height()) / 2)); - if (!tab.text.is_empty()) { - p_x += icon->get_width() + get_theme_constant(SNAME("hseparation")); - } + p_x = rtl ? p_x - icon->get_width() - hseparation : p_x + icon->get_width() + hseparation; } - Vector2 text_pos = Point2i(p_x, p_tab_style->get_margin(SIDE_TOP) + ((sb_rect.size.y - sb_ms.y) - tab.text_buf->get_size().y) / 2); - if (outline_size > 0 && font_outline_color.a > 0) { - tab.text_buf->draw_outline(ci, text_pos, outline_size, font_outline_color); - } - tab.text_buf->draw(ci, text_pos, p_font_color); + // Draw the text. + if (!tabs[p_index].text.is_empty()) { + Point2i text_pos = Point2i(rtl ? p_x - tabs[p_index].size_text : p_x, + p_tab_style->get_margin(SIDE_TOP) + ((sb_rect.size.y - sb_ms.y) - tabs[p_index].text_buf->get_size().y) / 2); - p_x += tab.size_text; + if (outline_size > 0 && font_outline_color.a > 0) { + tabs[p_index].text_buf->draw_outline(ci, text_pos, outline_size, font_outline_color); + } + tabs[p_index].text_buf->draw(ci, text_pos, p_font_color); - if (tab.right_button.is_valid()) { - Ref<StyleBox> style = get_theme_stylebox(SNAME("close_bg_highlight")); - Ref<Texture2D> rb = tab.right_button; + p_x = rtl ? p_x - tabs[p_index].size_text - hseparation : p_x + tabs[p_index].size_text + hseparation; + } - p_x += get_theme_constant(SNAME("hseparation")); + // Draw and calculate rect of the right button. + if (tabs[p_index].right_button.is_valid()) { + Ref<StyleBox> style = get_theme_stylebox(SNAME("button_highlight")); + Ref<Texture2D> rb = tabs[p_index].right_button; Rect2 rb_rect; rb_rect.size = style->get_minimum_size() + rb->get_size(); - rb_rect.position.x = p_x; + rb_rect.position.x = rtl ? p_x - rb_rect.size.width : p_x; rb_rect.position.y = p_tab_style->get_margin(SIDE_TOP) + ((sb_rect.size.y - sb_ms.y) - (rb_rect.size.y)) / 2; + tabs.write[p_index].rb_rect = rb_rect; + if (rb_hover == p_index) { if (rb_pressing) { get_theme_stylebox(SNAME("button_pressed"))->draw(ci, rb_rect); @@ -435,41 +478,61 @@ void TabBar::_draw_tab(Ref<StyleBox> &p_tab_style, Color &p_font_color, int p_in } } - rb->draw(ci, Point2i(p_x + style->get_margin(SIDE_LEFT), rb_rect.position.y + style->get_margin(SIDE_TOP))); - p_x += rb->get_width(); - tabs.write[p_index].rb_rect = rb_rect; + rb->draw(ci, Point2i(rb_rect.position.x + style->get_margin(SIDE_LEFT), rb_rect.position.y + style->get_margin(SIDE_TOP))); + + p_x = rtl ? rb_rect.position.x : rb_rect.position.x + rb_rect.size.width; } + // Draw and calculate rect of the close button. if (cb_displaypolicy == CLOSE_BUTTON_SHOW_ALWAYS || (cb_displaypolicy == CLOSE_BUTTON_SHOW_ACTIVE_ONLY && p_index == current)) { - Ref<StyleBox> style = get_theme_stylebox(SNAME("close_bg_highlight")); + Ref<StyleBox> style = get_theme_stylebox(SNAME("button_highlight")); Ref<Texture2D> cb = get_theme_icon(SNAME("close")); - p_x += get_theme_constant(SNAME("hseparation")); - Rect2 cb_rect; cb_rect.size = style->get_minimum_size() + cb->get_size(); - cb_rect.position.x = p_x; + cb_rect.position.x = rtl ? p_x - cb_rect.size.width : p_x; cb_rect.position.y = p_tab_style->get_margin(SIDE_TOP) + ((sb_rect.size.y - sb_ms.y) - (cb_rect.size.y)) / 2; - if (!tab.disabled && cb_hover == p_index) { + tabs.write[p_index].cb_rect = cb_rect; + + if (!tabs[p_index].disabled && cb_hover == p_index) { if (cb_pressing) { - get_theme_stylebox(SNAME("close_bg_pressed"))->draw(ci, cb_rect); + get_theme_stylebox(SNAME("button_pressed"))->draw(ci, cb_rect); } else { style->draw(ci, cb_rect); } } - cb->draw(ci, Point2i(p_x + style->get_margin(SIDE_LEFT), cb_rect.position.y + style->get_margin(SIDE_TOP))); - p_x += cb->get_width(); - tabs.write[p_index].cb_rect = cb_rect; + cb->draw(ci, Point2i(cb_rect.position.x + style->get_margin(SIDE_LEFT), cb_rect.position.y + style->get_margin(SIDE_TOP))); } } void TabBar::set_tab_count(int p_count) { + if (p_count == tabs.size()) { + return; + } + ERR_FAIL_COND(p_count < 0); tabs.resize(p_count); + + if (p_count == 0) { + offset = 0; + max_drawn_tab = 0; + current = 0; + previous = 0; + } else { + offset = MIN(offset, p_count - 1); + max_drawn_tab = MIN(max_drawn_tab, p_count - 1); + current = MIN(current, p_count - 1); + } + _update_cache(); + _ensure_no_over_offset(); + if (scroll_to_selected) { + ensure_tab_visible(current); + } update(); + update_minimum_size(); notify_property_list_changed(); } @@ -478,15 +541,26 @@ int TabBar::get_tab_count() const { } void TabBar::set_current_tab(int p_current) { - if (current == p_current) { - return; - } ERR_FAIL_INDEX(p_current, get_tab_count()); previous = current; current = p_current; + if (current == previous) { + emit_signal(SNAME("tab_selected"), current); + return; + } + // Triggered by dragging a tab from another TabBar to the selected index, to ensure that tab_changed is emitted. + if (previous == -1) { + previous = current; + } + + emit_signal(SNAME("tab_selected"), current); + _update_cache(); + if (scroll_to_selected) { + ensure_tab_visible(current); + } update(); emit_signal(SNAME("tab_changed"), p_current); @@ -515,8 +589,13 @@ bool TabBar::get_offset_buttons_visible() const { void TabBar::set_tab_title(int p_tab, const String &p_title) { ERR_FAIL_INDEX(p_tab, tabs.size()); tabs.write[p_tab].text = p_title; + _shape(p_tab); _update_cache(); + _ensure_no_over_offset(); + if (scroll_to_selected) { + ensure_tab_visible(current); + } update(); update_minimum_size(); } @@ -529,6 +608,7 @@ String TabBar::get_tab_title(int p_tab) const { void TabBar::set_tab_text_direction(int p_tab, Control::TextDirection p_text_direction) { ERR_FAIL_INDEX(p_tab, tabs.size()); ERR_FAIL_COND((int)p_text_direction < -1 || (int)p_text_direction > 3); + if (tabs[p_tab].text_direction != p_text_direction) { tabs.write[p_tab].text_direction = p_text_direction; _shape(p_tab); @@ -544,24 +624,38 @@ Control::TextDirection TabBar::get_tab_text_direction(int p_tab) const { void TabBar::clear_tab_opentype_features(int p_tab) { ERR_FAIL_INDEX(p_tab, tabs.size()); tabs.write[p_tab].opentype_features.clear(); + _shape(p_tab); _update_cache(); + _ensure_no_over_offset(); + if (scroll_to_selected) { + ensure_tab_visible(current); + } update(); + update_minimum_size(); } void TabBar::set_tab_opentype_feature(int p_tab, const String &p_name, int p_value) { ERR_FAIL_INDEX(p_tab, tabs.size()); + int32_t tag = TS->name_to_tag(p_name); if (!tabs[p_tab].opentype_features.has(tag) || (int)tabs[p_tab].opentype_features[tag] != p_value) { tabs.write[p_tab].opentype_features[tag] = p_value; + _shape(p_tab); _update_cache(); + _ensure_no_over_offset(); + if (scroll_to_selected) { + ensure_tab_visible(current); + } update(); + update_minimum_size(); } } int TabBar::get_tab_opentype_feature(int p_tab, const String &p_name) const { ERR_FAIL_INDEX_V(p_tab, tabs.size(), -1); + int32_t tag = TS->name_to_tag(p_name); if (!tabs[p_tab].opentype_features.has(tag)) { return -1; @@ -571,10 +665,17 @@ int TabBar::get_tab_opentype_feature(int p_tab, const String &p_name) const { void TabBar::set_tab_language(int p_tab, const String &p_language) { ERR_FAIL_INDEX(p_tab, tabs.size()); + if (tabs[p_tab].language != p_language) { tabs.write[p_tab].language = p_language; _shape(p_tab); + _update_cache(); + _ensure_no_over_offset(); + if (scroll_to_selected) { + ensure_tab_visible(current); + } update(); + update_minimum_size(); } } @@ -586,7 +687,12 @@ String TabBar::get_tab_language(int p_tab) const { void TabBar::set_tab_icon(int p_tab, const Ref<Texture2D> &p_icon) { ERR_FAIL_INDEX(p_tab, tabs.size()); tabs.write[p_tab].icon = p_icon; + _update_cache(); + _ensure_no_over_offset(); + if (scroll_to_selected) { + ensure_tab_visible(current); + } update(); update_minimum_size(); } @@ -599,7 +705,14 @@ Ref<Texture2D> TabBar::get_tab_icon(int p_tab) const { void TabBar::set_tab_disabled(int p_tab, bool p_disabled) { ERR_FAIL_INDEX(p_tab, tabs.size()); tabs.write[p_tab].disabled = p_disabled; + + _update_cache(); + _ensure_no_over_offset(); + if (scroll_to_selected) { + ensure_tab_visible(current); + } update(); + update_minimum_size(); } bool TabBar::is_tab_disabled(int p_tab) const { @@ -607,15 +720,38 @@ bool TabBar::is_tab_disabled(int p_tab) const { return tabs[p_tab].disabled; } -void TabBar::set_tab_right_button(int p_tab, const Ref<Texture2D> &p_right_button) { +void TabBar::set_tab_hidden(int p_tab, bool p_hidden) { + ERR_FAIL_INDEX(p_tab, tabs.size()); + tabs.write[p_tab].hidden = p_hidden; + + _update_cache(); + _ensure_no_over_offset(); + if (scroll_to_selected) { + ensure_tab_visible(current); + } + update(); + update_minimum_size(); +} + +bool TabBar::is_tab_hidden(int p_tab) const { + ERR_FAIL_INDEX_V(p_tab, tabs.size(), false); + return tabs[p_tab].hidden; +} + +void TabBar::set_tab_button_icon(int p_tab, const Ref<Texture2D> &p_icon) { ERR_FAIL_INDEX(p_tab, tabs.size()); - tabs.write[p_tab].right_button = p_right_button; + tabs.write[p_tab].right_button = p_icon; + _update_cache(); + _ensure_no_over_offset(); + if (scroll_to_selected) { + ensure_tab_visible(current); + } update(); update_minimum_size(); } -Ref<Texture2D> TabBar::get_tab_right_button(int p_tab) const { +Ref<Texture2D> TabBar::get_tab_button_icon(int p_tab) const { ERR_FAIL_INDEX_V(p_tab, tabs.size(), Ref<Texture2D>()); return tabs[p_tab].right_button; } @@ -626,34 +762,53 @@ void TabBar::_update_hover() { } const Point2 &pos = get_local_mouse_position(); - // test hovering to display right or close button. + // Test hovering to display right or close button. int hover_now = -1; int hover_buttons = -1; - for (int i = offset; i < tabs.size(); i++) { + for (int i = offset; i <= max_drawn_tab; i++) { + if (tabs[i].hidden) { + continue; + } + Rect2 rect = get_tab_rect(i); if (rect.has_point(pos)) { hover_now = i; } + if (tabs[i].rb_rect.has_point(pos)) { rb_hover = i; cb_hover = -1; hover_buttons = i; - break; } else if (!tabs[i].disabled && tabs[i].cb_rect.has_point(pos)) { cb_hover = i; rb_hover = -1; hover_buttons = i; + } + + if (hover_buttons != -1) { + update(); break; } } + if (hover != hover_now) { hover = hover_now; - emit_signal(SNAME("tab_hovered"), hover); + + if (hover != -1) { + emit_signal(SNAME("tab_hovered"), hover); + } } if (hover_buttons == -1) { // No hover. + int rb_hover_old = rb_hover; + int cb_hover_old = cb_hover; + rb_hover = hover_buttons; cb_hover = hover_buttons; + + if (rb_hover != rb_hover_old || cb_hover != cb_hover_old) { + update(); + } } } @@ -677,16 +832,20 @@ void TabBar::_update_cache() { int count_resize = 0; for (int i = 0; i < tabs.size(); i++) { - tabs.write[i].ofs_cache = 0; - tabs.write[i].size_cache = get_tab_width(i); tabs.write[i].size_text = Math::ceil(tabs[i].text_buf->get_size().x); tabs.write[i].text_buf->set_width(-1); - mw += tabs[i].size_cache; - if (tabs[i].size_cache <= min_width || i == current) { - size_fixed += tabs[i].size_cache; - } else { - count_resize++; + tabs.write[i].ofs_cache = 0; + tabs.write[i].size_cache = get_tab_width(i); + + if (!tabs[i].hidden) { + mw += tabs[i].size_cache; + + if (tabs[i].size_cache <= min_width || i == current) { + size_fixed += tabs[i].size_cache; + } else { + count_resize++; + } } } @@ -696,34 +855,20 @@ void TabBar::_update_cache() { } for (int i = offset; i < tabs.size(); i++) { - Ref<StyleBox> sb; - if (tabs[i].disabled) { - sb = tab_disabled; - } else if (i == current) { - sb = tab_selected; - } else { - sb = tab_unselected; + if (tabs[i].hidden) { + tabs.write[i].ofs_cache = w; + max_drawn_tab = i; + + continue; } int lsize = tabs[i].size_cache; int slen = tabs[i].size_text; - if (min_width > 0 && mw > limit_minus_buttons && i != current) { - if (lsize > m_width) { - slen = m_width - (sb->get_margin(SIDE_LEFT) + sb->get_margin(SIDE_RIGHT)); - if (tabs[i].icon.is_valid()) { - slen -= tabs[i].icon->get_width(); - slen -= get_theme_constant(SNAME("hseparation")); - } - if (cb_displaypolicy == CLOSE_BUTTON_SHOW_ALWAYS || (cb_displaypolicy == CLOSE_BUTTON_SHOW_ACTIVE_ONLY && i == current)) { - Ref<Texture2D> cb = get_theme_icon(SNAME("close")); - slen -= cb->get_width(); - slen -= get_theme_constant(SNAME("hseparation")); - } - - slen = MAX(slen, 1); - lsize = m_width; - } + // FIXME: This is completely broken. + if (min_width > 0 && (mw > limit || (offset > 0 && mw > limit_minus_buttons)) && i != current && lsize > m_width) { + slen = MAX(m_width - tabs[i].size_cache + tabs[i].size_text, 1); + lsize = m_width; } tabs.write[i].ofs_cache = w; @@ -735,13 +880,22 @@ void TabBar::_update_cache() { max_drawn_tab = i; // Check if all tabs would fit inside the area. - if (i > offset && (w > limit || (offset > 0 && w > limit_minus_buttons))) { - w -= get_tab_width(i); - max_drawn_tab -= 1; + if (clip_tabs && i > offset && (w > limit || (offset > 0 && w > limit_minus_buttons))) { + tabs.write[i].ofs_cache = 0; + tabs.write[i].text_buf->set_width(-1); + + w -= tabs[i].size_cache; + max_drawn_tab--; while (w > limit_minus_buttons && max_drawn_tab > offset) { - w -= get_tab_width(max_drawn_tab); - max_drawn_tab -= 1; + tabs.write[max_drawn_tab].ofs_cache = 0; + + if (!tabs[max_drawn_tab].hidden) { + tabs.write[max_drawn_tab].text_buf->set_width(-1); + w -= tabs[max_drawn_tab].size_cache; + } + + max_drawn_tab--; } break; @@ -752,21 +906,25 @@ void TabBar::_update_cache() { buttons_visible = offset > 0 || missing_right; if (tab_alignment == ALIGNMENT_LEFT) { + _update_hover(); return; - } else if (tab_alignment == ALIGNMENT_CENTER) { + } + + if (tab_alignment == ALIGNMENT_CENTER) { w = ((buttons_visible ? limit_minus_buttons : limit) - w) / 2; } else if (tab_alignment == ALIGNMENT_RIGHT) { w = (buttons_visible ? limit_minus_buttons : limit) - w; } - if (w < 0) { - w = 0; - } - for (int i = offset; i <= max_drawn_tab; i++) { tabs.write[i].ofs_cache = w; - w += tabs.write[i].size_cache; + + if (!tabs[i].hidden) { + w += tabs[i].size_cache; + } } + + _update_hover(); } void TabBar::_on_mouse_exited() { @@ -784,20 +942,26 @@ void TabBar::add_tab(const String &p_str, const Ref<Texture2D> &p_icon) { t.text_buf->set_direction(is_layout_rtl() ? TextServer::DIRECTION_RTL : TextServer::DIRECTION_LTR); t.text_buf->add_string(t.xl_text, get_theme_font(SNAME("font")), get_theme_font_size(SNAME("font_size")), Dictionary(), TranslationServer::get_singleton()->get_tool_locale()); t.icon = p_icon; - tabs.push_back(t); + _update_cache(); - call_deferred(SNAME("_update_hover")); + if (scroll_to_selected) { + ensure_tab_visible(current); + } update(); update_minimum_size(); } void TabBar::clear_tabs() { tabs.clear(); + offset = 0; + max_drawn_tab = 0; current = 0; previous = 0; - call_deferred(SNAME("_update_hover")); + + _update_cache(); update(); + update_minimum_size(); notify_property_list_changed(); } @@ -807,10 +971,6 @@ void TabBar::remove_tab(int p_idx) { if (current >= p_idx) { current--; } - _update_cache(); - call_deferred(SNAME("_update_hover")); - update(); - update_minimum_size(); if (current < 0) { current = 0; @@ -820,7 +980,13 @@ void TabBar::remove_tab(int p_idx) { current = tabs.size() - 1; } + _update_cache(); _ensure_no_over_offset(); + if (scroll_to_selected && !tabs.is_empty()) { + ensure_tab_visible(current); + } + update(); + update_minimum_size(); notify_property_list_changed(); } @@ -840,15 +1006,13 @@ Variant TabBar::get_drag_data(const Point2 &p_point) { if (!tabs[tab_over].icon.is_null()) { TextureRect *tf = memnew(TextureRect); tf->set_texture(tabs[tab_over].icon); + tf->set_stretch_mode(TextureRect::STRETCH_KEEP_CENTERED); drag_preview->add_child(tf); } + Label *label = memnew(Label(tabs[tab_over].xl_text)); drag_preview->add_child(label); - if (!tabs[tab_over].right_button.is_null()) { - TextureRect *tf = memnew(TextureRect); - tf->set_texture(tabs[tab_over].right_button); - drag_preview->add_child(tf); - } + set_drag_preview(drag_preview); Dictionary drag_data; @@ -901,31 +1065,40 @@ void TabBar::drop_data(const Point2 &p_point, const Variant &p_data) { int tab_from_id = d["tab_element"]; NodePath from_path = d["from_path"]; NodePath to_path = get_path(); + if (from_path == to_path) { if (hover_now < 0) { hover_now = get_tab_count() - 1; } + move_tab(tab_from_id, hover_now); emit_signal(SNAME("active_tab_rearranged"), hover_now); set_current_tab(hover_now); } else if (get_tabs_rearrange_group() != -1) { // Drag and drop between Tabs. + Node *from_node = get_node(from_path); TabBar *from_tabs = Object::cast_to<TabBar>(from_node); + if (from_tabs && from_tabs->get_tabs_rearrange_group() == get_tabs_rearrange_group()) { if (tab_from_id >= from_tabs->get_tab_count()) { return; } + Tab moving_tab = from_tabs->tabs[tab_from_id]; if (hover_now < 0) { hover_now = get_tab_count(); } + + // Workaround to ensure that tab_changed is emitted. + if (current == hover_now) { + current = -1; + } + tabs.insert(hover_now, moving_tab); from_tabs->remove_tab(tab_from_id); set_current_tab(hover_now); - emit_signal(SNAME("tab_changed"), hover_now); - _update_cache(); - update(); + update_minimum_size(); } } } @@ -946,6 +1119,7 @@ int TabBar::get_tab_idx_at_point(const Point2 &p_point) const { void TabBar::set_tab_alignment(AlignmentMode p_alignment) { ERR_FAIL_INDEX(p_alignment, ALIGNMENT_MAX); tab_alignment = p_alignment; + _update_cache(); update(); } @@ -959,7 +1133,16 @@ void TabBar::set_clip_tabs(bool p_clip_tabs) { return; } clip_tabs = p_clip_tabs; + + if (!clip_tabs) { + offset = 0; + max_drawn_tab = 0; + } + _update_cache(); + if (scroll_to_selected) { + ensure_tab_visible(current); + } update(); update_minimum_size(); } @@ -981,6 +1164,10 @@ void TabBar::move_tab(int from, int to) { tabs.insert(to, tab_from); _update_cache(); + _ensure_no_over_offset(); + if (scroll_to_selected) { + ensure_tab_visible(current); + } update(); notify_property_list_changed(); } @@ -991,37 +1178,49 @@ int TabBar::get_tab_width(int p_idx) const { Ref<StyleBox> tab_unselected = get_theme_stylebox(SNAME("tab_unselected")); Ref<StyleBox> tab_selected = get_theme_stylebox(SNAME("tab_selected")); Ref<StyleBox> tab_disabled = get_theme_stylebox(SNAME("tab_disabled")); + int hseparation = get_theme_constant(SNAME("hseparation")); - int x = 0; + Ref<StyleBox> style; + + if (tabs[p_idx].disabled) { + style = tab_disabled; + } else if (current == p_idx) { + style = tab_selected; + } else { + style = tab_unselected; + } + int x = style->get_minimum_size().width; Ref<Texture2D> tex = tabs[p_idx].icon; if (tex.is_valid()) { - x += tex->get_width(); - if (!tabs[p_idx].text.is_empty()) { - x += get_theme_constant(SNAME("hseparation")); - } + x += tex->get_width() + hseparation; } - x += Math::ceil(tabs[p_idx].text_buf->get_size().x); - - if (tabs[p_idx].disabled) { - x += tab_disabled->get_minimum_size().width; - } else if (current == p_idx) { - x += tab_selected->get_minimum_size().width; - } else { - x += tab_unselected->get_minimum_size().width; + if (!tabs[p_idx].text.is_empty()) { + x += tabs[p_idx].size_text + hseparation; } + bool close_visible = cb_displaypolicy == CLOSE_BUTTON_SHOW_ALWAYS || (cb_displaypolicy == CLOSE_BUTTON_SHOW_ACTIVE_ONLY && p_idx == current); + if (tabs[p_idx].right_button.is_valid()) { + Ref<StyleBox> btn_style = get_theme_stylebox(SNAME("button_highlight")); Ref<Texture2D> rb = tabs[p_idx].right_button; - x += rb->get_width(); - x += get_theme_constant(SNAME("hseparation")); + + if (close_visible) { + x += btn_style->get_minimum_size().width + rb->get_width(); + } else { + x += btn_style->get_margin(SIDE_LEFT) + rb->get_width() + hseparation; + } } - if (cb_displaypolicy == CLOSE_BUTTON_SHOW_ALWAYS || (cb_displaypolicy == CLOSE_BUTTON_SHOW_ACTIVE_ONLY && p_idx == current)) { + if (close_visible) { + Ref<StyleBox> btn_style = get_theme_stylebox(SNAME("button_highlight")); Ref<Texture2D> cb = get_theme_icon(SNAME("close")); - x += cb->get_width(); - x += get_theme_constant(SNAME("hseparation")); + x += btn_style->get_margin(SIDE_LEFT) + cb->get_width() + hseparation; + } + + if (x > style->get_minimum_size().width) { + x -= hseparation; } return x; @@ -1039,8 +1238,12 @@ void TabBar::_ensure_no_over_offset() { int prev_offset = offset; int total_w = tabs[max_drawn_tab].ofs_cache + tabs[max_drawn_tab].size_cache - tabs[offset].ofs_cache; - while (offset > 0) { - total_w += tabs[offset - 1].size_cache; + for (int i = offset; i > 0; i--) { + if (tabs[i - 1].hidden) { + continue; + } + + total_w += tabs[i - 1].size_cache; if (total_w < limit_minus_buttons) { offset--; @@ -1061,7 +1264,7 @@ void TabBar::ensure_tab_visible(int p_idx) { } ERR_FAIL_INDEX(p_idx, tabs.size()); - if (p_idx >= offset && p_idx <= max_drawn_tab) { + if (tabs[p_idx].hidden || (p_idx >= offset && p_idx <= max_drawn_tab)) { return; } @@ -1079,12 +1282,20 @@ void TabBar::ensure_tab_visible(int p_idx) { int total_w = tabs[max_drawn_tab].ofs_cache - tabs[offset].ofs_cache; for (int i = max_drawn_tab; i <= p_idx; i++) { + if (tabs[i].hidden) { + continue; + } + total_w += tabs[i].size_cache; } int prev_offset = offset; for (int i = offset; i < p_idx; i++) { + if (tabs[i].hidden) { + continue; + } + if (total_w > limit_minus_buttons) { total_w -= tabs[i].size_cache; offset++; @@ -1111,8 +1322,14 @@ Rect2 TabBar::get_tab_rect(int p_tab) const { void TabBar::set_tab_close_display_policy(CloseButtonDisplayPolicy p_policy) { ERR_FAIL_INDEX(p_policy, CLOSE_BUTTON_MAX); cb_displaypolicy = p_policy; + _update_cache(); + _ensure_no_over_offset(); + if (scroll_to_selected) { + ensure_tab_visible(current); + } update(); + update_minimum_size(); } TabBar::CloseButtonDisplayPolicy TabBar::get_tab_close_display_policy() const { @@ -1147,6 +1364,17 @@ int TabBar::get_tabs_rearrange_group() const { return tabs_rearrange_group; } +void TabBar::set_scroll_to_selected(bool p_enabled) { + scroll_to_selected = p_enabled; + if (p_enabled) { + ensure_tab_visible(current); + } +} + +bool TabBar::get_scroll_to_selected() const { + return scroll_to_selected; +} + void TabBar::set_select_with_rmb(bool p_enabled) { select_with_rmb = p_enabled; } @@ -1225,8 +1453,12 @@ void TabBar::_bind_methods() { ClassDB::bind_method(D_METHOD("get_tab_language", "tab_idx"), &TabBar::get_tab_language); ClassDB::bind_method(D_METHOD("set_tab_icon", "tab_idx", "icon"), &TabBar::set_tab_icon); ClassDB::bind_method(D_METHOD("get_tab_icon", "tab_idx"), &TabBar::get_tab_icon); + ClassDB::bind_method(D_METHOD("set_tab_button_icon", "tab_idx", "icon"), &TabBar::set_tab_button_icon); + ClassDB::bind_method(D_METHOD("get_tab_button_icon", "tab_idx"), &TabBar::get_tab_button_icon); ClassDB::bind_method(D_METHOD("set_tab_disabled", "tab_idx", "disabled"), &TabBar::set_tab_disabled); ClassDB::bind_method(D_METHOD("is_tab_disabled", "tab_idx"), &TabBar::is_tab_disabled); + ClassDB::bind_method(D_METHOD("set_tab_hidden", "tab_idx", "hidden"), &TabBar::set_tab_hidden); + ClassDB::bind_method(D_METHOD("is_tab_hidden", "tab_idx"), &TabBar::is_tab_hidden); ClassDB::bind_method(D_METHOD("remove_tab", "tab_idx"), &TabBar::remove_tab); ClassDB::bind_method(D_METHOD("add_tab", "title", "icon"), &TabBar::add_tab, DEFVAL(""), DEFVAL(Ref<Texture2D>())); ClassDB::bind_method(D_METHOD("set_tab_alignment", "alignment"), &TabBar::set_tab_alignment); @@ -1246,16 +1478,19 @@ void TabBar::_bind_methods() { ClassDB::bind_method(D_METHOD("get_drag_to_rearrange_enabled"), &TabBar::get_drag_to_rearrange_enabled); ClassDB::bind_method(D_METHOD("set_tabs_rearrange_group", "group_id"), &TabBar::set_tabs_rearrange_group); ClassDB::bind_method(D_METHOD("get_tabs_rearrange_group"), &TabBar::get_tabs_rearrange_group); - + ClassDB::bind_method(D_METHOD("set_scroll_to_selected", "enabled"), &TabBar::set_scroll_to_selected); + ClassDB::bind_method(D_METHOD("get_scroll_to_selected"), &TabBar::get_scroll_to_selected); ClassDB::bind_method(D_METHOD("set_select_with_rmb", "enabled"), &TabBar::set_select_with_rmb); ClassDB::bind_method(D_METHOD("get_select_with_rmb"), &TabBar::get_select_with_rmb); + ADD_SIGNAL(MethodInfo("tab_selected", PropertyInfo(Variant::INT, "tab"))); ADD_SIGNAL(MethodInfo("tab_changed", PropertyInfo(Variant::INT, "tab"))); + ADD_SIGNAL(MethodInfo("tab_clicked", PropertyInfo(Variant::INT, "tab"))); ADD_SIGNAL(MethodInfo("tab_rmb_clicked", PropertyInfo(Variant::INT, "tab"))); ADD_SIGNAL(MethodInfo("tab_close_pressed", PropertyInfo(Variant::INT, "tab"))); + ADD_SIGNAL(MethodInfo("tab_button_pressed", PropertyInfo(Variant::INT, "tab"))); ADD_SIGNAL(MethodInfo("tab_hovered", PropertyInfo(Variant::INT, "tab"))); ADD_SIGNAL(MethodInfo("active_tab_rearranged", PropertyInfo(Variant::INT, "idx_to"))); - ADD_SIGNAL(MethodInfo("tab_clicked", PropertyInfo(Variant::INT, "tab"))); ADD_PROPERTY(PropertyInfo(Variant::INT, "current_tab", PROPERTY_HINT_RANGE, "-1,4096,1", PROPERTY_USAGE_EDITOR), "set_current_tab", "get_current_tab"); ADD_PROPERTY(PropertyInfo(Variant::INT, "tab_alignment", PROPERTY_HINT_ENUM, "Left,Center,Right"), "set_tab_alignment", "get_tab_alignment"); @@ -1263,6 +1498,8 @@ void TabBar::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::INT, "tab_close_display_policy", PROPERTY_HINT_ENUM, "Show Never,Show Active Only,Show Always"), "set_tab_close_display_policy", "get_tab_close_display_policy"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "scrolling_enabled"), "set_scrolling_enabled", "get_scrolling_enabled"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "drag_to_rearrange_enabled"), "set_drag_to_rearrange_enabled", "get_drag_to_rearrange_enabled"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "scroll_to_selected"), "set_scroll_to_selected", "get_scroll_to_selected"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "select_with_rmb"), "set_select_with_rmb", "get_select_with_rmb"); ADD_ARRAY_COUNT("Tabs", "tab_count", "set_tab_count", "get_tab_count", "tab_"); @@ -1278,5 +1515,6 @@ void TabBar::_bind_methods() { } TabBar::TabBar() { + set_size(Size2(get_size().width, get_minimum_size().height)); connect("mouse_exited", callable_mp(this, &TabBar::_on_mouse_exited)); } diff --git a/scene/gui/tab_bar.h b/scene/gui/tab_bar.h index d0055ae4d2..b428538570 100644 --- a/scene/gui/tab_bar.h +++ b/scene/gui/tab_bar.h @@ -63,12 +63,11 @@ private: Ref<TextLine> text_buf; Ref<Texture2D> icon; - int ofs_cache = 0; bool disabled = false; + bool hidden = false; + int ofs_cache = 0; int size_cache = 0; int size_text = 0; - int x_cache = 0; - int x_size_cache = 0; Ref<Texture2D> right_button; Rect2 rb_rect; @@ -102,6 +101,7 @@ private: int min_width = 0; bool scrolling_enabled = true; bool drag_to_rearrange_enabled = false; + bool scroll_to_selected = true; int tabs_rearrange_group = -1; int get_tab_width(int p_idx) const; @@ -150,8 +150,11 @@ public: void set_tab_disabled(int p_tab, bool p_disabled); bool is_tab_disabled(int p_tab) const; - void set_tab_right_button(int p_tab, const Ref<Texture2D> &p_right_button); - Ref<Texture2D> get_tab_right_button(int p_tab) const; + void set_tab_hidden(int p_tab, bool p_hidden); + bool is_tab_hidden(int p_tab) const; + + void set_tab_button_icon(int p_tab, const Ref<Texture2D> &p_icon); + Ref<Texture2D> get_tab_button_icon(int p_tab) const; void set_tab_alignment(AlignmentMode p_alignment); AlignmentMode get_tab_alignment() const; @@ -187,6 +190,9 @@ public: void set_tabs_rearrange_group(int p_group_id); int get_tabs_rearrange_group() const; + void set_scroll_to_selected(bool p_enabled); + bool get_scroll_to_selected() const; + void set_select_with_rmb(bool p_enabled); bool get_select_with_rmb() const; diff --git a/scene/gui/text_edit.cpp b/scene/gui/text_edit.cpp index 7db1fae2b6..e060d3b901 100644 --- a/scene/gui/text_edit.cpp +++ b/scene/gui/text_edit.cpp @@ -874,7 +874,7 @@ void TextEdit::_notification(int p_what) { // Ensure we at least use the font color. Color current_color = !editable ? font_readonly_color : font_color; if (draw_placeholder) { - current_color.a *= placeholder_alpha; + current_color = font_placeholder_color; } const Ref<TextParagraph> ldata = draw_placeholder ? placeholder_data_buf : text.get_line_data(line); @@ -2515,6 +2515,7 @@ void TextEdit::_update_caches() { font_size = get_theme_font_size(SNAME("font_size")); font_color = get_theme_color(SNAME("font_color")); font_readonly_color = get_theme_color(SNAME("font_readonly_color")); + font_placeholder_color = get_theme_color(SNAME("font_placeholder_color")); outline_size = get_theme_constant(SNAME("outline_size")); outline_color = get_theme_color(SNAME("font_outline_color")); @@ -2947,15 +2948,6 @@ String TextEdit::get_placeholder() const { return placeholder_text; } -void TextEdit::set_placeholder_alpha(float p_alpha) { - placeholder_alpha = p_alpha; - update(); -} - -float TextEdit::get_placeholder_alpha() const { - return placeholder_alpha; -} - void TextEdit::set_line(int p_line, const String &p_new_text) { if (p_line < 0 || p_line >= text.size()) { return; @@ -4944,9 +4936,6 @@ void TextEdit::_bind_methods() { ClassDB::bind_method(D_METHOD("set_placeholder", "text"), &TextEdit::set_placeholder); ClassDB::bind_method(D_METHOD("get_placeholder"), &TextEdit::get_placeholder); - ClassDB::bind_method(D_METHOD("set_placeholder_alpha", "alpha"), &TextEdit::set_placeholder_alpha); - ClassDB::bind_method(D_METHOD("get_placeholder_alpha"), &TextEdit::get_placeholder_alpha); - ClassDB::bind_method(D_METHOD("set_line", "line", "new_text"), &TextEdit::set_line); ClassDB::bind_method(D_METHOD("get_line", "line"), &TextEdit::get_line); @@ -5256,7 +5245,6 @@ void TextEdit::_bind_methods() { /* Inspector */ ADD_PROPERTY(PropertyInfo(Variant::STRING, "text", PROPERTY_HINT_MULTILINE_TEXT), "set_text", "get_text"); ADD_PROPERTY(PropertyInfo(Variant::STRING, "placeholder_text", PROPERTY_HINT_MULTILINE_TEXT), "set_placeholder", "get_placeholder"); - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "placeholder_alpha", PROPERTY_HINT_RANGE, "0,1,0.001"), "set_placeholder_alpha", "get_placeholder_alpha"); ADD_PROPERTY(PropertyInfo(Variant::INT, "text_direction", PROPERTY_HINT_ENUM, "Auto,Left-to-Right,Right-to-Left,Inherited"), "set_text_direction", "get_text_direction"); ADD_PROPERTY(PropertyInfo(Variant::STRING, "language", PROPERTY_HINT_LOCALE_ID, ""), "set_language", "get_language"); diff --git a/scene/gui/text_edit.h b/scene/gui/text_edit.h index fdaa928598..079890249e 100644 --- a/scene/gui/text_edit.h +++ b/scene/gui/text_edit.h @@ -251,8 +251,6 @@ private: Point2 ime_selection; // Placeholder - float placeholder_alpha = 0.6; - String placeholder_text = ""; Array placeholder_bidi_override; Ref<TextParagraph> placeholder_data_buf; @@ -525,6 +523,7 @@ private: int font_size = 16; Color font_color = Color(1, 1, 1); Color font_readonly_color = Color(1, 1, 1); + Color font_placeholder_color = Color(1, 1, 1, 0.6); int outline_size = 0; Color outline_color = Color(1, 1, 1); @@ -684,9 +683,6 @@ public: void set_placeholder(const String &p_text); String get_placeholder() const; - void set_placeholder_alpha(float p_alpha); - float get_placeholder_alpha() const; - void set_line(int p_line, const String &p_new_text); String get_line(int p_line) const; diff --git a/scene/gui/texture_button.cpp b/scene/gui/texture_button.cpp index da202c1c8f..89a17ae854 100644 --- a/scene/gui/texture_button.cpp +++ b/scene/gui/texture_button.cpp @@ -173,7 +173,8 @@ void TextureButton::_notification(int p_what) { bool draw_focus = (has_focus() && focused.is_valid()); // If no other texture is valid, try using focused texture. - if (!texdraw.is_valid() && draw_focus) { + bool draw_focus_only = draw_focus && !texdraw.is_valid(); + if (draw_focus_only) { texdraw = focused; } @@ -232,7 +233,7 @@ void TextureButton::_notification(int p_what) { size.width *= hflip ? -1.0f : 1.0f; size.height *= vflip ? -1.0f : 1.0f; - if (texdraw == focused) { + if (draw_focus_only) { // Do nothing, we only needed to calculate the rectangle. } else if (_tile) { draw_texture_rect(texdraw, Rect2(ofs, size), _tile); diff --git a/scene/gui/tree.cpp b/scene/gui/tree.cpp index f2fe967f9a..1b32884880 100644 --- a/scene/gui/tree.cpp +++ b/scene/gui/tree.cpp @@ -555,8 +555,9 @@ void TreeItem::uncollapse_tree() { void TreeItem::set_custom_minimum_height(int p_height) { custom_min_height = p_height; - for (Cell &c : cells) + for (Cell &c : cells) { c.cached_minimum_size_dirty = true; + } _changed_notify(); } @@ -1092,8 +1093,9 @@ bool TreeItem::get_expand_right(int p_column) const { void TreeItem::set_disable_folding(bool p_disable) { disable_folding = p_disable; - for (Cell &c : cells) + for (Cell &c : cells) { c.cached_minimum_size_dirty = true; + } _changed_notify(0); } @@ -1317,10 +1319,10 @@ void TreeItem::_bind_methods() { ClassDB::bind_method(D_METHOD("get_children"), &TreeItem::get_children); ClassDB::bind_method(D_METHOD("get_index"), &TreeItem::get_index); - ClassDB::bind_method(D_METHOD("move_before", "item"), &TreeItem::_move_before); - ClassDB::bind_method(D_METHOD("move_after", "item"), &TreeItem::_move_after); + ClassDB::bind_method(D_METHOD("move_before", "item"), &TreeItem::move_before); + ClassDB::bind_method(D_METHOD("move_after", "item"), &TreeItem::move_after); - ClassDB::bind_method(D_METHOD("remove_child", "child"), &TreeItem::_remove_child); + ClassDB::bind_method(D_METHOD("remove_child", "child"), &TreeItem::remove_child); { MethodInfo mi; diff --git a/scene/gui/tree.h b/scene/gui/tree.h index fd65f90c49..c24763a0e4 100644 --- a/scene/gui/tree.h +++ b/scene/gui/tree.h @@ -188,16 +188,6 @@ protected: return d; } - void _remove_child(Object *p_child) { - remove_child(Object::cast_to<TreeItem>(p_child)); - } - - void _move_before(Object *p_item) { - move_before(Object::cast_to<TreeItem>(p_item)); - } - void _move_after(Object *p_item) { - move_after(Object::cast_to<TreeItem>(p_item)); - } Variant _call_recursive_bind(const Variant **p_args, int p_argcount, Callable::CallError &r_error); |