diff options
Diffstat (limited to 'scene/gui')
87 files changed, 6029 insertions, 3518 deletions
diff --git a/scene/gui/aspect_ratio_container.cpp b/scene/gui/aspect_ratio_container.cpp index 75f19ac452..e4a79c7aa3 100644 --- a/scene/gui/aspect_ratio_container.cpp +++ b/scene/gui/aspect_ratio_container.cpp @@ -51,21 +51,33 @@ Size2 AspectRatioContainer::get_minimum_size() const { } void AspectRatioContainer::set_ratio(float p_ratio) { + if (ratio == p_ratio) { + return; + } ratio = p_ratio; queue_sort(); } void AspectRatioContainer::set_stretch_mode(StretchMode p_mode) { + if (stretch_mode == p_mode) { + return; + } stretch_mode = p_mode; queue_sort(); } void AspectRatioContainer::set_alignment_horizontal(AlignmentMode p_alignment_horizontal) { + if (alignment_horizontal == p_alignment_horizontal) { + return; + } alignment_horizontal = p_alignment_horizontal; queue_sort(); } void AspectRatioContainer::set_alignment_vertical(AlignmentMode p_alignment_vertical) { + if (alignment_vertical == p_alignment_vertical) { + return; + } alignment_vertical = p_alignment_vertical; queue_sort(); } diff --git a/scene/gui/base_button.cpp b/scene/gui/base_button.cpp index 776623f7ce..cf467ceafb 100644 --- a/scene/gui/base_button.cpp +++ b/scene/gui/base_button.cpp @@ -64,7 +64,10 @@ void BaseButton::gui_input(const Ref<InputEvent> &p_event) { bool button_masked = mouse_button.is_valid() && (mouse_button_to_mask(mouse_button->get_button_index()) & button_mask) != MouseButton::NONE; if (button_masked || ui_accept) { + was_mouse_pressed = button_masked; on_action_event(p_event); + was_mouse_pressed = false; + return; } @@ -74,7 +77,7 @@ void BaseButton::gui_input(const Ref<InputEvent> &p_event) { bool last_press_inside = status.pressing_inside; status.pressing_inside = has_point(mouse_motion->get_position()); if (last_press_inside != status.pressing_inside) { - update(); + queue_redraw(); } } } @@ -84,32 +87,32 @@ void BaseButton::_notification(int p_what) { switch (p_what) { case NOTIFICATION_MOUSE_ENTER: { status.hovering = true; - update(); + queue_redraw(); } break; case NOTIFICATION_MOUSE_EXIT: { status.hovering = false; - update(); + queue_redraw(); } break; case NOTIFICATION_DRAG_BEGIN: case NOTIFICATION_SCROLL_BEGIN: { if (status.press_attempt) { status.press_attempt = false; - update(); + queue_redraw(); } } break; case NOTIFICATION_FOCUS_ENTER: { - update(); + queue_redraw(); } break; case NOTIFICATION_FOCUS_EXIT: { if (status.press_attempt) { status.press_attempt = false; - update(); + queue_redraw(); } else if (status.hovering) { - update(); + queue_redraw(); } } break; @@ -185,7 +188,7 @@ void BaseButton::on_action_event(Ref<InputEvent> p_event) { emit_signal(SNAME("button_up")); } - update(); + queue_redraw(); } void BaseButton::pressed() { @@ -207,7 +210,7 @@ void BaseButton::set_disabled(bool p_disabled) { status.press_attempt = false; status.pressing_inside = false; } - update(); + queue_redraw(); } bool BaseButton::is_disabled() const { @@ -231,7 +234,7 @@ void BaseButton::set_pressed(bool p_pressed) { } _toggled(status.pressed); - update(); + queue_redraw(); } void BaseButton::set_pressed_no_signal(bool p_pressed) { @@ -243,7 +246,7 @@ void BaseButton::set_pressed_no_signal(bool p_pressed) { } status.pressed = p_pressed; - update(); + queue_redraw(); } bool BaseButton::is_pressing() const { @@ -382,7 +385,7 @@ void BaseButton::set_button_group(const Ref<ButtonGroup> &p_group) { button_group->buttons.insert(this); } - update(); //checkbox changes to radio if set a buttongroup + queue_redraw(); //checkbox changes to radio if set a buttongroup } Ref<ButtonGroup> BaseButton::get_button_group() const { @@ -417,6 +420,10 @@ bool BaseButton::_is_focus_owner_in_shortcut_context() const { return ctx_node && vp_focus && (ctx_node == vp_focus || ctx_node->is_ancestor_of(vp_focus)); } +bool BaseButton::_was_pressed_by_mouse() const { + return was_mouse_pressed; +} + void BaseButton::_bind_methods() { ClassDB::bind_method(D_METHOD("set_pressed", "pressed"), &BaseButton::set_pressed); ClassDB::bind_method(D_METHOD("is_pressed"), &BaseButton::is_pressed); @@ -490,8 +497,8 @@ void ButtonGroup::get_buttons(List<BaseButton *> *r_buttons) { } } -Array ButtonGroup::_get_buttons() { - Array btns; +TypedArray<BaseButton> ButtonGroup::_get_buttons() { + TypedArray<BaseButton> btns; for (const BaseButton *E : buttons) { btns.push_back(E); } diff --git a/scene/gui/base_button.h b/scene/gui/base_button.h index 7cf8de6432..c83b08aadf 100644 --- a/scene/gui/base_button.h +++ b/scene/gui/base_button.h @@ -49,6 +49,7 @@ private: MouseButton button_mask = MouseButton::MASK_LEFT; bool toggle_mode = false; bool shortcut_in_tooltip = true; + bool was_mouse_pressed = false; bool keep_pressed_outside = false; Ref<Shortcut> shortcut; ObjectID shortcut_context; @@ -81,6 +82,7 @@ protected: void _notification(int p_what); bool _is_focus_owner_in_shortcut_context() const; + bool _was_pressed_by_mouse() const; GDVIRTUAL0(_pressed) GDVIRTUAL1(_toggled, bool) @@ -151,7 +153,7 @@ protected: public: BaseButton *get_pressed_button(); void get_buttons(List<BaseButton *> *r_buttons); - Array _get_buttons(); + TypedArray<BaseButton> _get_buttons(); ButtonGroup(); }; diff --git a/scene/gui/box_container.cpp b/scene/gui/box_container.cpp index df695feba8..151b0b93d4 100644 --- a/scene/gui/box_container.cpp +++ b/scene/gui/box_container.cpp @@ -44,7 +44,6 @@ void BoxContainer::_resort() { Size2i new_size = get_size(); - int sep = get_theme_constant(SNAME("separation")); //,vertical?"VBoxContainer":"HBoxContainer"); bool rtl = is_layout_rtl(); bool first = true; @@ -90,7 +89,7 @@ void BoxContainer::_resort() { return; } - int stretch_max = (vertical ? new_size.height : new_size.width) - (children_count - 1) * sep; + int stretch_max = (vertical ? new_size.height : new_size.width) - (children_count - 1) * theme_cache.separation; int stretch_diff = stretch_max - stretch_min; if (stretch_diff < 0) { //avoid negative stretch space @@ -214,7 +213,7 @@ void BoxContainer::_resort() { if (first) { first = false; } else { - ofs += sep; + ofs += theme_cache.separation; } int from = ofs; @@ -248,7 +247,6 @@ Size2 BoxContainer::get_minimum_size() const { /* Calculate MINIMUM SIZE */ Size2i minimum; - int sep = get_theme_constant(SNAME("separation")); //,vertical?"VBoxContainer":"HBoxContainer"); bool first = true; @@ -273,7 +271,7 @@ Size2 BoxContainer::get_minimum_size() const { minimum.width = size.width; } - minimum.height += size.height + (first ? 0 : sep); + minimum.height += size.height + (first ? 0 : theme_cache.separation); } else { /* HORIZONTAL */ @@ -281,7 +279,7 @@ Size2 BoxContainer::get_minimum_size() const { minimum.height = size.height; } - minimum.width += size.width + (first ? 0 : sep); + minimum.width += size.width + (first ? 0 : theme_cache.separation); } first = false; @@ -290,6 +288,12 @@ Size2 BoxContainer::get_minimum_size() const { return minimum; } +void BoxContainer::_update_theme_item_cache() { + Container::_update_theme_item_cache(); + + theme_cache.separation = get_theme_constant(SNAME("separation")); +} + void BoxContainer::_notification(int p_what) { switch (p_what) { case NOTIFICATION_SORT_CHILDREN: { @@ -307,7 +311,16 @@ void BoxContainer::_notification(int p_what) { } } +void BoxContainer::_validate_property(PropertyInfo &p_property) const { + if (is_fixed && p_property.name == "vertical") { + p_property.usage = PROPERTY_USAGE_NONE; + } +} + void BoxContainer::set_alignment(AlignmentMode p_alignment) { + if (alignment == p_alignment) { + return; + } alignment = p_alignment; _resort(); } @@ -316,6 +329,17 @@ BoxContainer::AlignmentMode BoxContainer::get_alignment() const { return alignment; } +void BoxContainer::set_vertical(bool p_vertical) { + ERR_FAIL_COND_MSG(is_fixed, "Can't change orientation of " + get_class() + "."); + vertical = p_vertical; + update_minimum_size(); + _resort(); +} + +bool BoxContainer::is_vertical() const { + return vertical; +} + Control *BoxContainer::add_spacer(bool p_begin) { Control *c = memnew(Control); c->set_mouse_filter(MOUSE_FILTER_PASS); //allow spacer to pass mouse events @@ -364,14 +388,17 @@ BoxContainer::BoxContainer(bool p_vertical) { void BoxContainer::_bind_methods() { ClassDB::bind_method(D_METHOD("add_spacer", "begin"), &BoxContainer::add_spacer); - ClassDB::bind_method(D_METHOD("get_alignment"), &BoxContainer::get_alignment); ClassDB::bind_method(D_METHOD("set_alignment", "alignment"), &BoxContainer::set_alignment); + ClassDB::bind_method(D_METHOD("get_alignment"), &BoxContainer::get_alignment); + ClassDB::bind_method(D_METHOD("set_vertical", "vertical"), &BoxContainer::set_vertical); + ClassDB::bind_method(D_METHOD("is_vertical"), &BoxContainer::is_vertical); BIND_ENUM_CONSTANT(ALIGNMENT_BEGIN); BIND_ENUM_CONSTANT(ALIGNMENT_CENTER); BIND_ENUM_CONSTANT(ALIGNMENT_END); ADD_PROPERTY(PropertyInfo(Variant::INT, "alignment", PROPERTY_HINT_ENUM, "Begin,Center,End"), "set_alignment", "get_alignment"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "vertical"), "set_vertical", "is_vertical"); } MarginContainer *VBoxContainer::add_margin_child(const String &p_label, Control *p_control, bool p_expand) { diff --git a/scene/gui/box_container.h b/scene/gui/box_container.h index 3043c3ea45..0ee5bd1772 100644 --- a/scene/gui/box_container.h +++ b/scene/gui/box_container.h @@ -47,11 +47,19 @@ private: bool vertical = false; AlignmentMode alignment = ALIGNMENT_BEGIN; + struct ThemeCache { + int separation = 0; + } theme_cache; + void _resort(); protected: - void _notification(int p_what); + bool is_fixed = false; + + virtual void _update_theme_item_cache() override; + void _notification(int p_what); + void _validate_property(PropertyInfo &p_property) const; static void _bind_methods(); public: @@ -60,6 +68,9 @@ public: void set_alignment(AlignmentMode p_alignment); AlignmentMode get_alignment() const; + void set_vertical(bool p_vertical); + bool is_vertical() const; + virtual Size2 get_minimum_size() const override; virtual Vector<int> get_allowed_size_flags_horizontal() const override; @@ -73,7 +84,7 @@ class HBoxContainer : public BoxContainer { public: HBoxContainer() : - BoxContainer(false) {} + BoxContainer(false) { is_fixed = true; } }; class MarginContainer; @@ -84,7 +95,7 @@ public: MarginContainer *add_margin_child(const String &p_label, Control *p_control, bool p_expand = false); VBoxContainer() : - BoxContainer(true) {} + BoxContainer(true) { is_fixed = true; } }; VARIANT_ENUM_CAST(BoxContainer::AlignmentMode); diff --git a/scene/gui/button.cpp b/scene/gui/button.cpp index 0a163b65ff..c2b82e01d1 100644 --- a/scene/gui/button.cpp +++ b/scene/gui/button.cpp @@ -34,11 +34,9 @@ #include "servers/rendering_server.h" Size2 Button::get_minimum_size() const { - Ref<Texture2D> _icon; - if (icon.is_null() && has_theme_icon(SNAME("icon"))) { - _icon = Control::get_theme_icon(SNAME("icon")); - } else { - _icon = icon; + Ref<Texture2D> _icon = icon; + if (_icon.is_null() && has_theme_icon(SNAME("icon"))) { + _icon = theme_cache.icon; } return get_minimum_size_for_text_and_icon("", _icon); @@ -48,10 +46,49 @@ void Button::_set_internal_margin(Side p_side, float p_value) { _internal_margin[p_side] = p_value; } +void Button::_update_theme_item_cache() { + BaseButton::_update_theme_item_cache(); + + theme_cache.normal = get_theme_stylebox(SNAME("normal")); + theme_cache.normal_mirrored = get_theme_stylebox(SNAME("normal_mirrored")); + theme_cache.pressed = get_theme_stylebox(SNAME("pressed")); + theme_cache.pressed_mirrored = get_theme_stylebox(SNAME("pressed_mirrored")); + theme_cache.hover = get_theme_stylebox(SNAME("hover")); + theme_cache.hover_mirrored = get_theme_stylebox(SNAME("hover_mirrored")); + theme_cache.hover_pressed = get_theme_stylebox(SNAME("hover_pressed")); + theme_cache.hover_pressed_mirrored = get_theme_stylebox(SNAME("hover_pressed_mirrored")); + theme_cache.disabled = get_theme_stylebox(SNAME("disabled")); + theme_cache.disabled_mirrored = get_theme_stylebox(SNAME("disabled_mirrored")); + theme_cache.focus = get_theme_stylebox(SNAME("focus")); + + theme_cache.font_color = get_theme_color(SNAME("font_color")); + theme_cache.font_focus_color = get_theme_color(SNAME("font_focus_color")); + theme_cache.font_pressed_color = get_theme_color(SNAME("font_pressed_color")); + theme_cache.font_hover_color = get_theme_color(SNAME("font_hover_color")); + theme_cache.font_hover_pressed_color = get_theme_color(SNAME("font_hover_pressed_color")); + theme_cache.font_disabled_color = get_theme_color(SNAME("font_disabled_color")); + + theme_cache.font = get_theme_font(SNAME("font")); + theme_cache.font_size = get_theme_font_size(SNAME("font_size")); + theme_cache.outline_size = get_theme_constant(SNAME("outline_size")); + theme_cache.font_outline_color = get_theme_color(SNAME("font_outline_color")); + + theme_cache.icon_normal_color = get_theme_color(SNAME("icon_normal_color")); + theme_cache.icon_focus_color = get_theme_color(SNAME("icon_focus_color")); + theme_cache.icon_pressed_color = get_theme_color(SNAME("icon_pressed_color")); + theme_cache.icon_hover_color = get_theme_color(SNAME("icon_hover_color")); + theme_cache.icon_hover_pressed_color = get_theme_color(SNAME("icon_hover_pressed_color")); + theme_cache.icon_disabled_color = get_theme_color(SNAME("icon_disabled_color")); + + theme_cache.icon = get_theme_icon(SNAME("icon")); + + theme_cache.h_separation = get_theme_constant(SNAME("h_separation")); +} + void Button::_notification(int p_what) { switch (p_what) { case NOTIFICATION_LAYOUT_DIRECTION_CHANGED: { - update(); + queue_redraw(); } break; case NOTIFICATION_TRANSLATION_CHANGED: { @@ -59,14 +96,14 @@ void Button::_notification(int p_what) { _shape(); update_minimum_size(); - update(); + queue_redraw(); } break; case NOTIFICATION_THEME_CHANGED: { _shape(); update_minimum_size(); - update(); + queue_redraw(); } break; case NOTIFICATION_DRAW: { @@ -75,15 +112,15 @@ void Button::_notification(int p_what) { Color color; Color color_icon(1, 1, 1, 1); - Ref<StyleBox> style = get_theme_stylebox(SNAME("normal")); + Ref<StyleBox> style = theme_cache.normal; bool rtl = is_layout_rtl(); switch (get_draw_mode()) { case DRAW_NORMAL: { if (rtl && has_theme_stylebox(SNAME("normal_mirrored"))) { - style = get_theme_stylebox(SNAME("normal_mirrored")); + style = theme_cache.normal_mirrored; } else { - style = get_theme_stylebox(SNAME("normal")); + style = theme_cache.normal; } if (!flat) { @@ -92,14 +129,14 @@ void Button::_notification(int p_what) { // Focus colors only take precedence over normal state. if (has_focus()) { - color = get_theme_color(SNAME("font_focus_color")); + color = theme_cache.font_focus_color; if (has_theme_color(SNAME("icon_focus_color"))) { - color_icon = get_theme_color(SNAME("icon_focus_color")); + color_icon = theme_cache.icon_focus_color; } } else { - color = get_theme_color(SNAME("font_color")); + color = theme_cache.font_color; if (has_theme_color(SNAME("icon_normal_color"))) { - color_icon = get_theme_color(SNAME("icon_normal_color")); + color_icon = theme_cache.icon_normal_color; } } } break; @@ -107,19 +144,19 @@ void Button::_notification(int p_what) { // Edge case for CheckButton and CheckBox. if (has_theme_stylebox("hover_pressed")) { if (rtl && has_theme_stylebox(SNAME("hover_pressed_mirrored"))) { - style = get_theme_stylebox(SNAME("hover_pressed_mirrored")); + style = theme_cache.hover_pressed_mirrored; } else { - style = get_theme_stylebox(SNAME("hover_pressed")); + style = theme_cache.hover_pressed; } if (!flat) { style->draw(ci, Rect2(Point2(0, 0), size)); } if (has_theme_color(SNAME("font_hover_pressed_color"))) { - color = get_theme_color(SNAME("font_hover_pressed_color")); + color = theme_cache.font_hover_pressed_color; } if (has_theme_color(SNAME("icon_hover_pressed_color"))) { - color_icon = get_theme_color(SNAME("icon_hover_pressed_color")); + color_icon = theme_cache.icon_hover_pressed_color; } break; @@ -128,53 +165,53 @@ void Button::_notification(int p_what) { } case DRAW_PRESSED: { if (rtl && has_theme_stylebox(SNAME("pressed_mirrored"))) { - style = get_theme_stylebox(SNAME("pressed_mirrored")); + style = theme_cache.pressed_mirrored; } else { - style = get_theme_stylebox(SNAME("pressed")); + style = theme_cache.pressed; } if (!flat) { style->draw(ci, Rect2(Point2(0, 0), size)); } if (has_theme_color(SNAME("font_pressed_color"))) { - color = get_theme_color(SNAME("font_pressed_color")); + color = theme_cache.font_pressed_color; } else { - color = get_theme_color(SNAME("font_color")); + color = theme_cache.font_color; } if (has_theme_color(SNAME("icon_pressed_color"))) { - color_icon = get_theme_color(SNAME("icon_pressed_color")); + color_icon = theme_cache.icon_pressed_color; } } break; case DRAW_HOVER: { if (rtl && has_theme_stylebox(SNAME("hover_mirrored"))) { - style = get_theme_stylebox(SNAME("hover_mirrored")); + style = theme_cache.hover_mirrored; } else { - style = get_theme_stylebox(SNAME("hover")); + style = theme_cache.hover; } if (!flat) { style->draw(ci, Rect2(Point2(0, 0), size)); } - color = get_theme_color(SNAME("font_hover_color")); + color = theme_cache.font_hover_color; if (has_theme_color(SNAME("icon_hover_color"))) { - color_icon = get_theme_color(SNAME("icon_hover_color")); + color_icon = theme_cache.icon_hover_color; } } break; case DRAW_DISABLED: { if (rtl && has_theme_stylebox(SNAME("disabled_mirrored"))) { - style = get_theme_stylebox(SNAME("disabled_mirrored")); + style = theme_cache.disabled_mirrored; } else { - style = get_theme_stylebox(SNAME("disabled")); + style = theme_cache.disabled; } if (!flat) { style->draw(ci, Rect2(Point2(0, 0), size)); } - color = get_theme_color(SNAME("font_disabled_color")); + color = theme_cache.font_disabled_color; if (has_theme_color(SNAME("icon_disabled_color"))) { - color_icon = get_theme_color(SNAME("icon_disabled_color")); + color_icon = theme_cache.icon_disabled_color; } else { color_icon.a = 0.4; } @@ -183,13 +220,13 @@ void Button::_notification(int p_what) { } if (has_focus()) { - Ref<StyleBox> style2 = get_theme_stylebox(SNAME("focus")); + Ref<StyleBox> style2 = theme_cache.focus; style2->draw(ci, Rect2(Point2(), size)); } Ref<Texture2D> _icon; if (icon.is_null() && has_theme_icon(SNAME("icon"))) { - _icon = Control::get_theme_icon(SNAME("icon")); + _icon = theme_cache.icon; } else { _icon = icon; } @@ -219,21 +256,21 @@ void Button::_notification(int p_what) { if (icon_align_rtl_checked == HORIZONTAL_ALIGNMENT_LEFT) { style_offset.x = style->get_margin(SIDE_LEFT); if (_internal_margin[SIDE_LEFT] > 0) { - icon_ofs_region = _internal_margin[SIDE_LEFT] + get_theme_constant(SNAME("h_separation")); + icon_ofs_region = _internal_margin[SIDE_LEFT] + theme_cache.h_separation; } } else if (icon_align_rtl_checked == HORIZONTAL_ALIGNMENT_CENTER) { style_offset.x = 0.0; } else if (icon_align_rtl_checked == HORIZONTAL_ALIGNMENT_RIGHT) { style_offset.x = -style->get_margin(SIDE_RIGHT); if (_internal_margin[SIDE_RIGHT] > 0) { - icon_ofs_region = -_internal_margin[SIDE_RIGHT] - get_theme_constant(SNAME("h_separation")); + icon_ofs_region = -_internal_margin[SIDE_RIGHT] - theme_cache.h_separation; } } style_offset.y = style->get_margin(SIDE_TOP); if (expand_icon) { Size2 _size = get_size() - style->get_offset() * 2; - int icon_text_separation = text.is_empty() ? 0 : get_theme_constant(SNAME("h_separation")); + int icon_text_separation = text.is_empty() ? 0 : theme_cache.h_separation; _size.width -= icon_text_separation + icon_ofs_region; if (!clip_text && icon_align_rtl_checked != HORIZONTAL_ALIGNMENT_CENTER) { _size.width -= text_buf->get_size().width; @@ -263,7 +300,7 @@ void Button::_notification(int p_what) { } } - Point2 icon_ofs = !_icon.is_null() ? Point2(icon_region.size.width + get_theme_constant(SNAME("h_separation")), 0) : Point2(); + Point2 icon_ofs = !_icon.is_null() ? Point2(icon_region.size.width + theme_cache.h_separation, 0) : Point2(); if (align_rtl_checked == HORIZONTAL_ALIGNMENT_CENTER && icon_align_rtl_checked == HORIZONTAL_ALIGNMENT_CENTER) { icon_ofs.x = 0.0; } @@ -273,10 +310,10 @@ void Button::_notification(int p_what) { int text_width = MAX(1, (clip_text || overrun_behavior != TextServer::OVERRUN_NO_TRIMMING) ? MIN(text_clip, text_buf->get_size().x) : text_buf->get_size().x); if (_internal_margin[SIDE_LEFT] > 0) { - text_clip -= _internal_margin[SIDE_LEFT] + get_theme_constant(SNAME("h_separation")); + text_clip -= _internal_margin[SIDE_LEFT] + theme_cache.h_separation; } if (_internal_margin[SIDE_RIGHT] > 0) { - text_clip -= _internal_margin[SIDE_RIGHT] + get_theme_constant(SNAME("h_separation")); + text_clip -= _internal_margin[SIDE_RIGHT] + theme_cache.h_separation; } Point2 text_ofs = (size - style->get_minimum_size() - icon_ofs - text_buf->get_size() - Point2(_internal_margin[SIDE_RIGHT] - _internal_margin[SIDE_LEFT], 0)) / 2.0; @@ -290,7 +327,7 @@ void Button::_notification(int p_what) { icon_ofs.x = 0.0; } if (_internal_margin[SIDE_LEFT] > 0) { - text_ofs.x = style->get_margin(SIDE_LEFT) + icon_ofs.x + _internal_margin[SIDE_LEFT] + get_theme_constant(SNAME("h_separation")); + text_ofs.x = style->get_margin(SIDE_LEFT) + icon_ofs.x + _internal_margin[SIDE_LEFT] + theme_cache.h_separation; } else { text_ofs.x = style->get_margin(SIDE_LEFT) + icon_ofs.x; } @@ -307,7 +344,7 @@ void Button::_notification(int p_what) { } break; case HORIZONTAL_ALIGNMENT_RIGHT: { if (_internal_margin[SIDE_RIGHT] > 0) { - text_ofs.x = size.x - style->get_margin(SIDE_RIGHT) - text_width - _internal_margin[SIDE_RIGHT] - get_theme_constant(SNAME("h_separation")); + text_ofs.x = size.x - style->get_margin(SIDE_RIGHT) - text_width - _internal_margin[SIDE_RIGHT] - theme_cache.h_separation; } else { text_ofs.x = size.x - style->get_margin(SIDE_RIGHT) - text_width; } @@ -318,8 +355,8 @@ void Button::_notification(int p_what) { } break; } - Color font_outline_color = get_theme_color(SNAME("font_outline_color")); - int outline_size = get_theme_constant(SNAME("outline_size")); + Color font_outline_color = theme_cache.font_outline_color; + int outline_size = theme_cache.outline_size; if (outline_size > 0 && font_outline_color.a > 0) { text_buf->draw_outline(ci, text_ofs, outline_size, font_outline_color); } @@ -342,13 +379,13 @@ Size2 Button::get_minimum_size_for_text_and_icon(const String &p_text, Ref<Textu minsize.width = 0; } - if (!expand_icon && !p_icon.is_null()) { + if (!expand_icon && p_icon.is_valid()) { minsize.height = MAX(minsize.height, p_icon->get_height()); if (icon_alignment != HORIZONTAL_ALIGNMENT_CENTER) { minsize.width += p_icon->get_width(); if (!xl_text.is_empty() || !p_text.is_empty()) { - minsize.width += get_theme_constant(SNAME("hseparation")); + minsize.width += MAX(0, theme_cache.h_separation); } } else { minsize.width = MAX(minsize.width, p_icon->get_width()); @@ -356,12 +393,12 @@ Size2 Button::get_minimum_size_for_text_and_icon(const String &p_text, Ref<Textu } if (!xl_text.is_empty() || !p_text.is_empty()) { - Ref<Font> font = get_theme_font(SNAME("font")); - float font_height = font->get_height(get_theme_font_size(SNAME("font_size"))); + Ref<Font> font = theme_cache.font; + float font_height = font->get_height(theme_cache.font_size); minsize.height = MAX(font_height, minsize.height); } - return get_theme_stylebox(SNAME("normal"))->get_minimum_size() + minsize; + return theme_cache.normal->get_minimum_size() + minsize; } void Button::_shape(Ref<TextParagraph> p_paragraph, String p_text) { @@ -373,10 +410,15 @@ void Button::_shape(Ref<TextParagraph> p_paragraph, String p_text) { p_text = xl_text; } - Ref<Font> font = get_theme_font(SNAME("font")); - int font_size = get_theme_font_size(SNAME("font_size")); - p_paragraph->clear(); + + Ref<Font> font = theme_cache.font; + int font_size = theme_cache.font_size; + if (font.is_null() || font_size == 0) { + // Can't shape without a valid font and a non-zero size. + return; + } + if (text_direction == Control::TEXT_DIRECTION_INHERITED) { p_paragraph->set_direction(is_layout_rtl() ? TextServer::DIRECTION_RTL : TextServer::DIRECTION_LTR); } else { @@ -391,7 +433,7 @@ void Button::set_text_overrun_behavior(TextServer::OverrunBehavior p_behavior) { overrun_behavior = p_behavior; _shape(); - update(); + queue_redraw(); update_minimum_size(); } } @@ -406,7 +448,7 @@ void Button::set_text(const String &p_text) { xl_text = atr(text); _shape(); - update(); + queue_redraw(); update_minimum_size(); } } @@ -420,7 +462,7 @@ void Button::set_text_direction(Control::TextDirection p_text_direction) { if (text_direction != p_text_direction) { text_direction = p_text_direction; _shape(); - update(); + queue_redraw(); } } @@ -432,7 +474,7 @@ void Button::set_language(const String &p_language) { if (language != p_language) { language = p_language; _shape(); - update(); + queue_redraw(); } } @@ -443,7 +485,7 @@ String Button::get_language() const { void Button::set_icon(const Ref<Texture2D> &p_icon) { if (icon != p_icon) { icon = p_icon; - update(); + queue_redraw(); update_minimum_size(); } } @@ -455,7 +497,7 @@ Ref<Texture2D> Button::get_icon() const { void Button::set_expand_icon(bool p_enabled) { if (expand_icon != p_enabled) { expand_icon = p_enabled; - update(); + queue_redraw(); update_minimum_size(); } } @@ -467,7 +509,7 @@ bool Button::is_expand_icon() const { void Button::set_flat(bool p_enabled) { if (flat != p_enabled) { flat = p_enabled; - update(); + queue_redraw(); } } @@ -478,7 +520,7 @@ bool Button::is_flat() const { void Button::set_clip_text(bool p_enabled) { if (clip_text != p_enabled) { clip_text = p_enabled; - update(); + queue_redraw(); update_minimum_size(); } } @@ -490,7 +532,7 @@ bool Button::get_clip_text() const { void Button::set_text_alignment(HorizontalAlignment p_alignment) { if (alignment != p_alignment) { alignment = p_alignment; - update(); + queue_redraw(); } } @@ -501,7 +543,7 @@ HorizontalAlignment Button::get_text_alignment() const { void Button::set_icon_alignment(HorizontalAlignment p_alignment) { icon_alignment = p_alignment; update_minimum_size(); - update(); + queue_redraw(); } HorizontalAlignment Button::get_icon_alignment() const { @@ -546,7 +588,7 @@ void Button::_bind_methods() { Button::Button(const String &p_text) { text_buf.instantiate(); - text_buf->set_break_flags(TextServer::BREAK_MANDATORY); + text_buf->set_break_flags(TextServer::BREAK_MANDATORY | TextServer::BREAK_TRIM_EDGE_SPACES); set_mouse_filter(MOUSE_FILTER_STOP); set_text(p_text); diff --git a/scene/gui/button.h b/scene/gui/button.h index 23b5c78166..9d9f9763db 100644 --- a/scene/gui/button.h +++ b/scene/gui/button.h @@ -54,10 +54,48 @@ private: HorizontalAlignment icon_alignment = HORIZONTAL_ALIGNMENT_LEFT; float _internal_margin[4] = {}; + struct ThemeCache { + Ref<StyleBox> normal; + Ref<StyleBox> normal_mirrored; + Ref<StyleBox> pressed; + Ref<StyleBox> pressed_mirrored; + Ref<StyleBox> hover; + Ref<StyleBox> hover_mirrored; + Ref<StyleBox> hover_pressed; + Ref<StyleBox> hover_pressed_mirrored; + Ref<StyleBox> disabled; + Ref<StyleBox> disabled_mirrored; + Ref<StyleBox> focus; + + Color font_color; + Color font_focus_color; + Color font_pressed_color; + Color font_hover_color; + Color font_hover_pressed_color; + Color font_disabled_color; + + Ref<Font> font; + int font_size = 0; + int outline_size = 0; + Color font_outline_color; + + Color icon_normal_color; + Color icon_focus_color; + Color icon_pressed_color; + Color icon_hover_color; + Color icon_hover_pressed_color; + Color icon_disabled_color; + + Ref<Texture2D> icon; + + int h_separation = 0; + } theme_cache; + void _shape(Ref<TextParagraph> p_paragraph = Ref<TextParagraph>(), String p_text = ""); protected: void _set_internal_margin(Side p_side, float p_value); + virtual void _update_theme_item_cache() override; void _notification(int p_what); static void _bind_methods(); diff --git a/scene/gui/check_box.cpp b/scene/gui/check_box.cpp index cb80f5b5ef..f5eb0b957f 100644 --- a/scene/gui/check_box.cpp +++ b/scene/gui/check_box.cpp @@ -33,39 +33,30 @@ #include "servers/rendering_server.h" Size2 CheckBox::get_icon_size() const { - Ref<Texture2D> checked = Control::get_theme_icon(SNAME("checked")); - Ref<Texture2D> unchecked = Control::get_theme_icon(SNAME("unchecked")); - Ref<Texture2D> radio_checked = Control::get_theme_icon(SNAME("radio_checked")); - Ref<Texture2D> radio_unchecked = Control::get_theme_icon(SNAME("radio_unchecked")); - Ref<Texture2D> checked_disabled = Control::get_theme_icon(SNAME("checked_disabled")); - Ref<Texture2D> unchecked_disabled = Control::get_theme_icon(SNAME("unchecked_disabled")); - Ref<Texture2D> radio_checked_disabled = Control::get_theme_icon(SNAME("radio_checked_disabled")); - Ref<Texture2D> radio_unchecked_disabled = Control::get_theme_icon(SNAME("radio_unchecked_disabled")); - Size2 tex_size = Size2(0, 0); - if (!checked.is_null()) { - tex_size = Size2(checked->get_width(), checked->get_height()); + if (!theme_cache.checked.is_null()) { + tex_size = Size2(theme_cache.checked->get_width(), theme_cache.checked->get_height()); } - if (!unchecked.is_null()) { - tex_size = Size2(MAX(tex_size.width, unchecked->get_width()), MAX(tex_size.height, unchecked->get_height())); + if (!theme_cache.unchecked.is_null()) { + tex_size = Size2(MAX(tex_size.width, theme_cache.unchecked->get_width()), MAX(tex_size.height, theme_cache.unchecked->get_height())); } - if (!radio_checked.is_null()) { - tex_size = Size2(MAX(tex_size.width, radio_checked->get_width()), MAX(tex_size.height, radio_checked->get_height())); + if (!theme_cache.radio_checked.is_null()) { + tex_size = Size2(MAX(tex_size.width, theme_cache.radio_checked->get_width()), MAX(tex_size.height, theme_cache.radio_checked->get_height())); } - if (!radio_unchecked.is_null()) { - tex_size = Size2(MAX(tex_size.width, radio_unchecked->get_width()), MAX(tex_size.height, radio_unchecked->get_height())); + if (!theme_cache.radio_unchecked.is_null()) { + tex_size = Size2(MAX(tex_size.width, theme_cache.radio_unchecked->get_width()), MAX(tex_size.height, theme_cache.radio_unchecked->get_height())); } - if (!checked_disabled.is_null()) { - tex_size = Size2(MAX(tex_size.width, checked_disabled->get_width()), MAX(tex_size.height, checked_disabled->get_height())); + if (!theme_cache.checked_disabled.is_null()) { + tex_size = Size2(MAX(tex_size.width, theme_cache.checked_disabled->get_width()), MAX(tex_size.height, theme_cache.checked_disabled->get_height())); } - if (!unchecked_disabled.is_null()) { - tex_size = Size2(MAX(tex_size.width, unchecked_disabled->get_width()), MAX(tex_size.height, unchecked_disabled->get_height())); + if (!theme_cache.unchecked_disabled.is_null()) { + tex_size = Size2(MAX(tex_size.width, theme_cache.unchecked_disabled->get_width()), MAX(tex_size.height, theme_cache.unchecked_disabled->get_height())); } - if (!radio_checked_disabled.is_null()) { - tex_size = Size2(MAX(tex_size.width, radio_checked_disabled->get_width()), MAX(tex_size.height, radio_checked_disabled->get_height())); + if (!theme_cache.radio_checked_disabled.is_null()) { + tex_size = Size2(MAX(tex_size.width, theme_cache.radio_checked_disabled->get_width()), MAX(tex_size.height, theme_cache.radio_checked_disabled->get_height())); } - if (!radio_unchecked_disabled.is_null()) { - tex_size = Size2(MAX(tex_size.width, radio_unchecked_disabled->get_width()), MAX(tex_size.height, radio_unchecked_disabled->get_height())); + if (!theme_cache.radio_unchecked_disabled.is_null()) { + tex_size = Size2(MAX(tex_size.width, theme_cache.radio_unchecked_disabled->get_width()), MAX(tex_size.height, theme_cache.radio_unchecked_disabled->get_height())); } return tex_size; } @@ -75,14 +66,30 @@ Size2 CheckBox::get_minimum_size() const { Size2 tex_size = get_icon_size(); minsize.width += tex_size.width; if (get_text().length() > 0) { - minsize.width += get_theme_constant(SNAME("h_separation")); + minsize.width += MAX(0, theme_cache.h_separation); } - Ref<StyleBox> sb = get_theme_stylebox(SNAME("normal")); - minsize.height = MAX(minsize.height, tex_size.height + sb->get_margin(SIDE_TOP) + sb->get_margin(SIDE_BOTTOM)); + minsize.height = MAX(minsize.height, tex_size.height + theme_cache.normal_style->get_margin(SIDE_TOP) + theme_cache.normal_style->get_margin(SIDE_BOTTOM)); return minsize; } +void CheckBox::_update_theme_item_cache() { + Button::_update_theme_item_cache(); + + theme_cache.h_separation = get_theme_constant(SNAME("h_separation")); + theme_cache.check_v_offset = get_theme_constant(SNAME("check_v_offset")); + theme_cache.normal_style = get_theme_stylebox(SNAME("normal")); + + theme_cache.checked = get_theme_icon(SNAME("checked")); + theme_cache.unchecked = get_theme_icon(SNAME("unchecked")); + theme_cache.radio_checked = get_theme_icon(SNAME("radio_checked")); + theme_cache.radio_unchecked = get_theme_icon(SNAME("radio_unchecked")); + theme_cache.checked_disabled = get_theme_icon(SNAME("checked_disabled")); + theme_cache.unchecked_disabled = get_theme_icon(SNAME("unchecked_disabled")); + theme_cache.radio_checked_disabled = get_theme_icon(SNAME("radio_checked_disabled")); + theme_cache.radio_unchecked_disabled = get_theme_icon(SNAME("radio_unchecked_disabled")); +} + void CheckBox::_notification(int p_what) { switch (p_what) { case NOTIFICATION_THEME_CHANGED: @@ -100,22 +107,39 @@ void CheckBox::_notification(int p_what) { case NOTIFICATION_DRAW: { RID ci = get_canvas_item(); - Ref<Texture2D> on = Control::get_theme_icon(vformat("%s%s", is_radio() ? "radio_checked" : "checked", is_disabled() ? "_disabled" : "")); - Ref<Texture2D> off = Control::get_theme_icon(vformat("%s%s", is_radio() ? "radio_unchecked" : "unchecked", is_disabled() ? "_disabled" : "")); - Ref<StyleBox> sb = get_theme_stylebox(SNAME("normal")); + Ref<Texture2D> on_tex; + Ref<Texture2D> off_tex; + + if (is_radio()) { + if (is_disabled()) { + on_tex = theme_cache.radio_checked_disabled; + off_tex = theme_cache.radio_unchecked_disabled; + } else { + on_tex = theme_cache.radio_checked; + off_tex = theme_cache.radio_unchecked; + } + } else { + if (is_disabled()) { + on_tex = theme_cache.checked_disabled; + off_tex = theme_cache.unchecked_disabled; + } else { + on_tex = theme_cache.checked; + off_tex = theme_cache.unchecked; + } + } Vector2 ofs; if (is_layout_rtl()) { - ofs.x = get_size().x - sb->get_margin(SIDE_RIGHT) - get_icon_size().width; + ofs.x = get_size().x - theme_cache.normal_style->get_margin(SIDE_RIGHT) - get_icon_size().width; } else { - ofs.x = sb->get_margin(SIDE_LEFT); + ofs.x = theme_cache.normal_style->get_margin(SIDE_LEFT); } - ofs.y = int((get_size().height - get_icon_size().height) / 2) + get_theme_constant(SNAME("check_v_adjust")); + ofs.y = int((get_size().height - get_icon_size().height) / 2) + theme_cache.check_v_offset; if (is_pressed()) { - on->draw(ci, ofs); + on_tex->draw(ci, ofs); } else { - off->draw(ci, ofs); + off_tex->draw(ci, ofs); } } break; } diff --git a/scene/gui/check_box.h b/scene/gui/check_box.h index fcdb2ce08c..8438d0e589 100644 --- a/scene/gui/check_box.h +++ b/scene/gui/check_box.h @@ -36,9 +36,26 @@ class CheckBox : public Button { GDCLASS(CheckBox, Button); + struct ThemeCache { + int h_separation = 0; + int check_v_offset = 0; + Ref<StyleBox> normal_style; + + Ref<Texture2D> checked; + Ref<Texture2D> unchecked; + Ref<Texture2D> radio_checked; + Ref<Texture2D> radio_unchecked; + Ref<Texture2D> checked_disabled; + Ref<Texture2D> unchecked_disabled; + Ref<Texture2D> radio_checked_disabled; + Ref<Texture2D> radio_unchecked_disabled; + } theme_cache; + protected: Size2 get_icon_size() const; Size2 get_minimum_size() const override; + + virtual void _update_theme_item_cache() override; void _notification(int p_what); bool is_radio(); diff --git a/scene/gui/check_button.cpp b/scene/gui/check_button.cpp index a09873ea4f..9466512699 100644 --- a/scene/gui/check_button.cpp +++ b/scene/gui/check_button.cpp @@ -34,14 +34,33 @@ #include "servers/rendering_server.h" Size2 CheckButton::get_icon_size() const { - Ref<Texture2D> on = Control::get_theme_icon(is_disabled() ? "on_disabled" : "on"); - Ref<Texture2D> off = Control::get_theme_icon(is_disabled() ? "off_disabled" : "off"); + Ref<Texture2D> on_tex; + Ref<Texture2D> off_tex; + + if (is_layout_rtl()) { + if (is_disabled()) { + on_tex = theme_cache.checked_disabled_mirrored; + off_tex = theme_cache.unchecked_disabled_mirrored; + } else { + on_tex = theme_cache.checked_mirrored; + off_tex = theme_cache.unchecked_mirrored; + } + } else { + if (is_disabled()) { + on_tex = theme_cache.checked_disabled; + off_tex = theme_cache.unchecked_disabled; + } else { + on_tex = theme_cache.checked; + off_tex = theme_cache.unchecked; + } + } + Size2 tex_size = Size2(0, 0); - if (!on.is_null()) { - tex_size = Size2(on->get_width(), on->get_height()); + if (!on_tex.is_null()) { + tex_size = Size2(on_tex->get_width(), on_tex->get_height()); } - if (!off.is_null()) { - tex_size = Size2(MAX(tex_size.width, off->get_width()), MAX(tex_size.height, off->get_height())); + if (!off_tex.is_null()) { + tex_size = Size2(MAX(tex_size.width, off_tex->get_width()), MAX(tex_size.height, off_tex->get_height())); } return tex_size; @@ -52,14 +71,30 @@ Size2 CheckButton::get_minimum_size() const { Size2 tex_size = get_icon_size(); minsize.width += tex_size.width; if (get_text().length() > 0) { - minsize.width += get_theme_constant(SNAME("h_separation")); + minsize.width += MAX(0, theme_cache.h_separation); } - Ref<StyleBox> sb = get_theme_stylebox(SNAME("normal")); - minsize.height = MAX(minsize.height, tex_size.height + sb->get_margin(SIDE_TOP) + sb->get_margin(SIDE_BOTTOM)); + minsize.height = MAX(minsize.height, tex_size.height + theme_cache.normal_style->get_margin(SIDE_TOP) + theme_cache.normal_style->get_margin(SIDE_BOTTOM)); return minsize; } +void CheckButton::_update_theme_item_cache() { + Button::_update_theme_item_cache(); + + theme_cache.h_separation = get_theme_constant(SNAME("h_separation")); + theme_cache.check_v_offset = get_theme_constant(SNAME("check_v_offset")); + theme_cache.normal_style = get_theme_stylebox(SNAME("normal")); + + theme_cache.checked = get_theme_icon(SNAME("checked")); + theme_cache.unchecked = get_theme_icon(SNAME("unchecked")); + theme_cache.checked_disabled = get_theme_icon(SNAME("checked_disabled")); + theme_cache.unchecked_disabled = get_theme_icon(SNAME("unchecked_disabled")); + theme_cache.checked_mirrored = get_theme_icon(SNAME("checked_mirrored")); + theme_cache.unchecked_mirrored = get_theme_icon(SNAME("unchecked_mirrored")); + theme_cache.checked_disabled_mirrored = get_theme_icon(SNAME("checked_disabled_mirrored")); + theme_cache.unchecked_disabled_mirrored = get_theme_icon(SNAME("unchecked_disabled_mirrored")); +} + void CheckButton::_notification(int p_what) { switch (p_what) { case NOTIFICATION_THEME_CHANGED: @@ -78,34 +113,41 @@ void CheckButton::_notification(int p_what) { RID ci = get_canvas_item(); bool rtl = is_layout_rtl(); - Ref<Texture2D> on; - if (rtl) { - on = Control::get_theme_icon(is_disabled() ? "on_disabled_mirrored" : "on_mirrored"); - } else { - on = Control::get_theme_icon(is_disabled() ? "on_disabled" : "on"); - } - Ref<Texture2D> off; + Ref<Texture2D> on_tex; + Ref<Texture2D> off_tex; + if (rtl) { - off = Control::get_theme_icon(is_disabled() ? "off_disabled_mirrored" : "off_mirrored"); + if (is_disabled()) { + on_tex = theme_cache.checked_disabled_mirrored; + off_tex = theme_cache.unchecked_disabled_mirrored; + } else { + on_tex = theme_cache.checked_mirrored; + off_tex = theme_cache.unchecked_mirrored; + } } else { - off = Control::get_theme_icon(is_disabled() ? "off_disabled" : "off"); + if (is_disabled()) { + on_tex = theme_cache.checked_disabled; + off_tex = theme_cache.unchecked_disabled; + } else { + on_tex = theme_cache.checked; + off_tex = theme_cache.unchecked; + } } - Ref<StyleBox> sb = get_theme_stylebox(SNAME("normal")); Vector2 ofs; Size2 tex_size = get_icon_size(); if (rtl) { - ofs.x = sb->get_margin(SIDE_LEFT); + ofs.x = theme_cache.normal_style->get_margin(SIDE_LEFT); } else { - ofs.x = get_size().width - (tex_size.width + sb->get_margin(SIDE_RIGHT)); + ofs.x = get_size().width - (tex_size.width + theme_cache.normal_style->get_margin(SIDE_RIGHT)); } - ofs.y = (get_size().height - tex_size.height) / 2 + get_theme_constant(SNAME("check_v_adjust")); + ofs.y = (get_size().height - tex_size.height) / 2 + theme_cache.check_v_offset; if (is_pressed()) { - on->draw(ci, ofs); + on_tex->draw(ci, ofs); } else { - off->draw(ci, ofs); + off_tex->draw(ci, ofs); } } break; } diff --git a/scene/gui/check_button.h b/scene/gui/check_button.h index 7d4bb8bdfc..3878c9628a 100644 --- a/scene/gui/check_button.h +++ b/scene/gui/check_button.h @@ -36,9 +36,26 @@ class CheckButton : public Button { GDCLASS(CheckButton, Button); + struct ThemeCache { + int h_separation = 0; + int check_v_offset = 0; + Ref<StyleBox> normal_style; + + Ref<Texture2D> checked; + Ref<Texture2D> unchecked; + Ref<Texture2D> checked_disabled; + Ref<Texture2D> unchecked_disabled; + Ref<Texture2D> checked_mirrored; + Ref<Texture2D> unchecked_mirrored; + Ref<Texture2D> checked_disabled_mirrored; + Ref<Texture2D> unchecked_disabled_mirrored; + } theme_cache; + protected: Size2 get_icon_size() const; virtual Size2 get_minimum_size() const override; + + virtual void _update_theme_item_cache() override; void _notification(int p_what); public: diff --git a/scene/gui/code_edit.cpp b/scene/gui/code_edit.cpp index 8968c1cc17..f6e0e4216d 100644 --- a/scene/gui/code_edit.cpp +++ b/scene/gui/code_edit.cpp @@ -268,7 +268,7 @@ void CodeEdit::gui_input(const Ref<InputEvent> &p_gui_input) { if (is_code_completion_scroll_pressed && mb->get_button_index() == MouseButton::LEFT) { is_code_completion_scroll_pressed = false; - update(); + queue_redraw(); return; } @@ -281,13 +281,13 @@ void CodeEdit::gui_input(const Ref<InputEvent> &p_gui_input) { case MouseButton::WHEEL_UP: { if (code_completion_current_selected > 0) { code_completion_current_selected--; - update(); + queue_redraw(); } } break; case MouseButton::WHEEL_DOWN: { if (code_completion_current_selected < code_completion_options.size() - 1) { code_completion_current_selected++; - update(); + queue_redraw(); } } break; case MouseButton::LEFT: { @@ -295,7 +295,7 @@ void CodeEdit::gui_input(const Ref<InputEvent> &p_gui_input) { if (mb->is_double_click()) { confirm_code_completion(); } - update(); + queue_redraw(); } break; default: break; @@ -310,7 +310,7 @@ void CodeEdit::gui_input(const Ref<InputEvent> &p_gui_input) { is_code_completion_scroll_pressed = true; _update_scroll_selected_line(mb->get_position().y); - update(); + queue_redraw(); } return; @@ -344,7 +344,7 @@ void CodeEdit::gui_input(const Ref<InputEvent> &p_gui_input) { } } else { if (mb->get_button_index() == MouseButton::LEFT) { - if (mb->is_command_pressed() && !symbol_lookup_word.is_empty()) { + if (mb->is_command_or_control_pressed() && !symbol_lookup_word.is_empty()) { Vector2i mpos = mb->get_position(); if (is_layout_rtl()) { mpos.x = get_size().x - mpos.x; @@ -371,7 +371,7 @@ void CodeEdit::gui_input(const Ref<InputEvent> &p_gui_input) { } if (symbol_lookup_on_click_enabled) { - if (mm->is_command_pressed() && mm->get_button_mask() == MouseButton::NONE && !is_dragging_cursor()) { + if (mm->is_command_or_control_pressed() && mm->get_button_mask() == MouseButton::NONE && !is_dragging_cursor()) { symbol_lookup_new_word = get_word_at_pos(mpos); if (symbol_lookup_new_word != symbol_lookup_word) { emit_signal(SNAME("symbol_validate"), symbol_lookup_new_word); @@ -384,12 +384,12 @@ void CodeEdit::gui_input(const Ref<InputEvent> &p_gui_input) { bool scroll_hovered = code_completion_scroll_rect.has_point(mpos); if (is_code_completion_scroll_hovered != scroll_hovered) { is_code_completion_scroll_hovered = scroll_hovered; - update(); + queue_redraw(); } if (is_code_completion_scroll_pressed) { _update_scroll_selected_line(mpos.y); - update(); + queue_redraw(); return; } } @@ -432,7 +432,7 @@ void CodeEdit::gui_input(const Ref<InputEvent> &p_gui_input) { /* Allow unicode handling if: */ /* No Modifiers are pressed (except shift) */ - bool allow_unicode_handling = !(k->is_command_pressed() || k->is_ctrl_pressed() || k->is_alt_pressed() || k->is_meta_pressed()); + bool allow_unicode_handling = !(k->is_command_or_control_pressed() || k->is_ctrl_pressed() || k->is_alt_pressed() || k->is_meta_pressed()); /* AUTO-COMPLETE */ if (code_completion_enabled && k->is_action("ui_text_completion_query", true)) { @@ -448,7 +448,7 @@ void CodeEdit::gui_input(const Ref<InputEvent> &p_gui_input) { } else { code_completion_current_selected = code_completion_options.size() - 1; } - update(); + queue_redraw(); accept_event(); return; } @@ -458,31 +458,31 @@ void CodeEdit::gui_input(const Ref<InputEvent> &p_gui_input) { } else { code_completion_current_selected = 0; } - update(); + queue_redraw(); accept_event(); return; } if (k->is_action("ui_page_up", true)) { code_completion_current_selected = MAX(0, code_completion_current_selected - code_completion_max_lines); - update(); + queue_redraw(); accept_event(); return; } if (k->is_action("ui_page_down", true)) { code_completion_current_selected = MIN(code_completion_options.size() - 1, code_completion_current_selected + code_completion_max_lines); - update(); + queue_redraw(); accept_event(); return; } if (k->is_action("ui_home", true)) { code_completion_current_selected = 0; - update(); + queue_redraw(); accept_event(); return; } if (k->is_action("ui_end", true)) { code_completion_current_selected = code_completion_options.size() - 1; - update(); + queue_redraw(); accept_event(); return; } @@ -1106,7 +1106,7 @@ bool CodeEdit::is_auto_brace_completion_enabled() const { void CodeEdit::set_highlight_matching_braces_enabled(bool p_enabled) { highlight_matching_braces_enabled = p_enabled; - update(); + queue_redraw(); } bool CodeEdit::is_highlight_matching_braces_enabled() const { @@ -1265,7 +1265,7 @@ void CodeEdit::set_line_as_breakpoint(int p_line, bool p_breakpointed) { breakpointed_lines.erase(p_line); } emit_signal(SNAME("breakpoint_toggled"), p_line); - update(); + queue_redraw(); } bool CodeEdit::is_line_breakpointed(int p_line) const { @@ -1280,8 +1280,8 @@ void CodeEdit::clear_breakpointed_lines() { } } -Array CodeEdit::get_breakpointed_lines() const { - Array ret; +PackedInt32Array CodeEdit::get_breakpointed_lines() const { + PackedInt32Array ret; for (int i = 0; i < get_line_count(); i++) { if (is_line_breakpointed(i)) { ret.append(i); @@ -1294,7 +1294,7 @@ Array CodeEdit::get_breakpointed_lines() const { void CodeEdit::set_line_as_bookmarked(int p_line, bool p_bookmarked) { int mask = get_line_gutter_metadata(p_line, main_gutter); set_line_gutter_metadata(p_line, main_gutter, p_bookmarked ? mask | MAIN_GUTTER_BOOKMARK : mask & ~MAIN_GUTTER_BOOKMARK); - update(); + queue_redraw(); } bool CodeEdit::is_line_bookmarked(int p_line) const { @@ -1309,8 +1309,8 @@ void CodeEdit::clear_bookmarked_lines() { } } -Array CodeEdit::get_bookmarked_lines() const { - Array ret; +PackedInt32Array CodeEdit::get_bookmarked_lines() const { + PackedInt32Array ret; for (int i = 0; i < get_line_count(); i++) { if (is_line_bookmarked(i)) { ret.append(i); @@ -1323,7 +1323,7 @@ Array CodeEdit::get_bookmarked_lines() const { void CodeEdit::set_line_as_executing(int p_line, bool p_executing) { int mask = get_line_gutter_metadata(p_line, main_gutter); set_line_gutter_metadata(p_line, main_gutter, p_executing ? mask | MAIN_GUTTER_EXECUTING : mask & ~MAIN_GUTTER_EXECUTING); - update(); + queue_redraw(); } bool CodeEdit::is_line_executing(int p_line) const { @@ -1338,8 +1338,8 @@ void CodeEdit::clear_executing_lines() { } } -Array CodeEdit::get_executing_lines() const { - Array ret; +PackedInt32Array CodeEdit::get_executing_lines() const { + PackedInt32Array ret; for (int i = 0; i < get_line_count(); i++) { if (is_line_executing(i)) { ret.append(i); @@ -1359,7 +1359,7 @@ bool CodeEdit::is_draw_line_numbers_enabled() const { void CodeEdit::set_line_numbers_zero_padded(bool p_zero_padded) { p_zero_padded ? line_number_padding = "0" : line_number_padding = " "; - update(); + queue_redraw(); } bool CodeEdit::is_line_numbers_zero_padded() const { @@ -1529,7 +1529,7 @@ void CodeEdit::fold_line(int p_line) { set_caret_line(p_line, false, false); set_caret_column(get_line(p_line).length(), false); } - update(); + queue_redraw(); } void CodeEdit::unfold_line(int p_line) { @@ -1552,14 +1552,14 @@ void CodeEdit::unfold_line(int p_line) { } _set_line_as_hidden(i, false); } - update(); + queue_redraw(); } void CodeEdit::fold_all_lines() { for (int i = 0; i < get_line_count(); i++) { fold_line(i); } - update(); + queue_redraw(); } void CodeEdit::unfold_all_lines() { @@ -1765,12 +1765,12 @@ Point2 CodeEdit::get_delimiter_end_position(int p_line, int p_column) const { void CodeEdit::set_code_hint(const String &p_hint) { code_hint = p_hint; code_hint_xpos = -0xFFFF; - update(); + queue_redraw(); } void CodeEdit::set_code_hint_draw_below(bool p_below) { code_hint_draw_below = p_below; - update(); + queue_redraw(); } /* Code Completion */ @@ -1929,7 +1929,7 @@ void CodeEdit::set_code_completion_selected_index(int p_index) { } ERR_FAIL_INDEX(p_index, code_completion_options.size()); code_completion_current_selected = p_index; - update(); + queue_redraw(); } void CodeEdit::confirm_code_completion(bool p_replace) { @@ -2043,13 +2043,13 @@ void CodeEdit::cancel_code_completion() { } code_completion_forced = false; code_completion_active = false; - update(); + queue_redraw(); } /* Line length guidelines */ void CodeEdit::set_line_length_guidelines(TypedArray<int> p_guideline_columns) { line_length_guideline_columns = p_guideline_columns; - update(); + queue_redraw(); } TypedArray<int> CodeEdit::get_line_length_guidelines() const { @@ -2769,7 +2769,7 @@ void CodeEdit::_filter_code_completion_candidates_impl() { i++; } - Array completion_options; + TypedArray<Dictionary> completion_options; GDVIRTUAL_CALL(_filter_code_completion_candidates, completion_options_sources, completion_options); @@ -2802,7 +2802,7 @@ void CodeEdit::_filter_code_completion_candidates_impl() { code_completion_longest_line = MIN(max_width, code_completion_max_width * font_size); code_completion_current_selected = 0; code_completion_active = true; - update(); + queue_redraw(); return; } @@ -3052,7 +3052,7 @@ void CodeEdit::_filter_code_completion_candidates_impl() { code_completion_longest_line = MIN(max_width, code_completion_max_width * font_size); code_completion_current_selected = 0; code_completion_active = true; - update(); + queue_redraw(); } void CodeEdit::_lines_edited_from(int p_from_line, int p_to_line) { diff --git a/scene/gui/code_edit.h b/scene/gui/code_edit.h index 08bd91a368..2065f3e681 100644 --- a/scene/gui/code_edit.h +++ b/scene/gui/code_edit.h @@ -266,7 +266,7 @@ protected: GDVIRTUAL1(_confirm_code_completion, bool) GDVIRTUAL1(_request_code_completion, bool) - GDVIRTUAL1RC(Array, _filter_code_completion_candidates, TypedArray<Dictionary>) + GDVIRTUAL1RC(TypedArray<Dictionary>, _filter_code_completion_candidates, TypedArray<Dictionary>) public: /* General overrides */ @@ -322,19 +322,19 @@ public: void set_line_as_breakpoint(int p_line, bool p_breakpointed); bool is_line_breakpointed(int p_line) const; void clear_breakpointed_lines(); - Array get_breakpointed_lines() const; + PackedInt32Array get_breakpointed_lines() const; // bookmarks void set_line_as_bookmarked(int p_line, bool p_bookmarked); bool is_line_bookmarked(int p_line) const; void clear_bookmarked_lines(); - Array get_bookmarked_lines() const; + PackedInt32Array get_bookmarked_lines() const; // executing lines void set_line_as_executing(int p_line, bool p_executing); bool is_line_executing(int p_line) const; void clear_executing_lines(); - Array get_executing_lines() const; + PackedInt32Array get_executing_lines() const; /* Line numbers */ void set_draw_line_numbers(bool p_draw); diff --git a/scene/gui/color_picker.cpp b/scene/gui/color_picker.cpp index 8cbe14c492..4a1f2ab7c6 100644 --- a/scene/gui/color_picker.cpp +++ b/scene/gui/color_picker.cpp @@ -292,6 +292,9 @@ bool ColorPicker::is_displaying_old_color() const { } void ColorPicker::set_edit_alpha(bool p_show) { + if (edit_alpha == p_show) { + return; + } edit_alpha = p_show; _update_controls(); @@ -300,7 +303,7 @@ void ColorPicker::set_edit_alpha(bool p_show) { } _update_color(); - sample->update(); + sample->queue_redraw(); } bool ColorPicker::is_editing_alpha() const { @@ -455,15 +458,15 @@ void ColorPicker::_update_color(bool p_update_sliders) { _update_text_value(); - sample->update(); - uv_edit->update(); - w_edit->update(); + sample->queue_redraw(); + uv_edit->queue_redraw(); + w_edit->queue_redraw(); for (int i = 0; i < current_slider_count; i++) { - sliders[i]->update(); + sliders[i]->queue_redraw(); } - alpha_slider->update(); - wheel->update(); - wheel_uv->update(); + alpha_slider->queue_redraw(); + wheel->queue_redraw(); + wheel_uv->queue_redraw(); updating = false; } @@ -509,6 +512,9 @@ Color ColorPicker::get_pick_color() const { void ColorPicker::set_picker_shape(PickerShapeType p_shape) { ERR_FAIL_INDEX(p_shape, SHAPE_MAX); + if (current_shape == p_shape) { + return; + } current_shape = p_shape; _copy_color_to_hsv(); @@ -530,7 +536,7 @@ void ColorPicker::_add_preset_button(int p_size, const Color &p_color) { btn_preset->set_preset_color(p_color); btn_preset->set_custom_minimum_size(Size2(p_size, p_size)); btn_preset->connect("gui_input", callable_mp(this, &ColorPicker::_preset_input).bind(p_color)); - btn_preset->set_tooltip(vformat(RTR("Color: #%s\nLMB: Apply color\nRMB: Remove preset"), p_color.to_html(p_color.a < 1))); + btn_preset->set_tooltip_text(vformat(RTR("Color: #%s\nLMB: Apply color\nRMB: Remove preset"), p_color.to_html(p_color.a < 1))); preset_container->add_child(btn_preset); } @@ -847,7 +853,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 (actual_shape == SHAPE_VHS_CIRCLE || actual_shape == SHAPE_OKHSL_CIRCLE) { - circle_mat->set_shader_uniform("v", v); + circle_mat->set_shader_parameter("v", v); } } } @@ -1080,7 +1086,7 @@ void ColorPicker::_screen_pick_pressed() { } else { screen->show(); } - screen->raise(); + screen->move_to_front(); #ifndef _MSC_VER #warning show modal no longer works, needs to be converted to a popup #endif @@ -1131,6 +1137,9 @@ void ColorPicker::_html_focus_exit() { } void ColorPicker::set_presets_enabled(bool p_enabled) { + if (presets_enabled == p_enabled) { + return; + } presets_enabled = p_enabled; if (!p_enabled) { btn_add_preset->set_disabled(true); @@ -1146,6 +1155,9 @@ bool ColorPicker::are_presets_enabled() const { } void ColorPicker::set_presets_visible(bool p_visible) { + if (presets_visible == p_visible) { + return; + } presets_visible = p_visible; preset_separator->set_visible(p_visible); preset_container->set_visible(p_visible); @@ -1224,7 +1236,7 @@ ColorPicker::ColorPicker() : btn_pick->set_flat(true); hb_smpl->add_child(btn_pick); btn_pick->set_toggle_mode(true); - btn_pick->set_tooltip(RTR("Pick a color from the editor window.")); + btn_pick->set_tooltip_text(RTR("Pick a color from the editor window.")); btn_pick->connect("pressed", callable_mp(this, &ColorPicker::_screen_pick_pressed)); VBoxContainer *vbl = memnew(VBoxContainer); @@ -1264,7 +1276,7 @@ ColorPicker::ColorPicker() : text_type = memnew(Button); hhb->add_child(text_type); text_type->set_text("#"); - text_type->set_tooltip(RTR("Switch between hexadecimal and code values.")); + text_type->set_tooltip_text(RTR("Switch between hexadecimal and code values.")); if (Engine::get_singleton()->is_editor_hint()) { text_type->connect("pressed", callable_mp(this, &ColorPicker::_text_type_toggled)); } else { @@ -1325,7 +1337,7 @@ ColorPicker::ColorPicker() : btn_add_preset = memnew(Button); btn_add_preset->set_icon_alignment(HORIZONTAL_ALIGNMENT_CENTER); - btn_add_preset->set_tooltip(RTR("Add current color as a preset.")); + btn_add_preset->set_tooltip_text(RTR("Add current color as a preset.")); btn_add_preset->connect("pressed", callable_mp(this, &ColorPicker::_add_preset_pressed)); preset_container->add_child(btn_add_preset); } @@ -1347,7 +1359,7 @@ void ColorPickerButton::_about_to_popup() { void ColorPickerButton::_color_changed(const Color &p_color) { color = p_color; - update(); + queue_redraw(); emit_signal(SNAME("color_changed"), color); } @@ -1419,12 +1431,15 @@ void ColorPickerButton::_notification(int p_what) { } void ColorPickerButton::set_pick_color(const Color &p_color) { + if (color == p_color) { + return; + } color = p_color; if (picker) { picker->set_pick_color(p_color); } - update(); + queue_redraw(); } Color ColorPickerButton::get_pick_color() const { @@ -1432,6 +1447,9 @@ Color ColorPickerButton::get_pick_color() const { } void ColorPickerButton::set_edit_alpha(bool p_show) { + if (edit_alpha == p_show) { + return; + } edit_alpha = p_show; if (picker) { picker->set_edit_alpha(p_show); diff --git a/scene/gui/color_rect.cpp b/scene/gui/color_rect.cpp index 2955f74a0c..143662efc6 100644 --- a/scene/gui/color_rect.cpp +++ b/scene/gui/color_rect.cpp @@ -31,8 +31,11 @@ #include "color_rect.h" void ColorRect::set_color(const Color &p_color) { + if (color == p_color) { + return; + } color = p_color; - update(); + queue_redraw(); } Color ColorRect::get_color() const { diff --git a/scene/gui/control.cpp b/scene/gui/control.cpp index 06aa913eb1..b4e603d5ed 100644 --- a/scene/gui/control.cpp +++ b/scene/gui/control.cpp @@ -43,6 +43,8 @@ #include "scene/main/canvas_layer.h" #include "scene/main/window.h" #include "scene/scene_string_names.h" +#include "scene/theme/theme_db.h" +#include "scene/theme/theme_owner.h" #include "servers/rendering_server.h" #include "servers/text_server.h" @@ -193,15 +195,15 @@ void Control::get_argument_options(const StringName &p_function, int p_idx, List List<StringName> sn; String pf = p_function; if (pf == "add_theme_color_override" || pf == "has_theme_color" || pf == "has_theme_color_override" || pf == "get_theme_color") { - Theme::get_default()->get_color_list(get_class(), &sn); + ThemeDB::get_singleton()->get_default_theme()->get_color_list(get_class(), &sn); } else if (pf == "add_theme_style_override" || pf == "has_theme_style" || pf == "has_theme_style_override" || pf == "get_theme_style") { - Theme::get_default()->get_stylebox_list(get_class(), &sn); + ThemeDB::get_singleton()->get_default_theme()->get_stylebox_list(get_class(), &sn); } else if (pf == "add_theme_font_override" || pf == "has_theme_font" || pf == "has_theme_font_override" || pf == "get_theme_font") { - Theme::get_default()->get_font_list(get_class(), &sn); + ThemeDB::get_singleton()->get_default_theme()->get_font_list(get_class(), &sn); } else if (pf == "add_theme_font_size_override" || pf == "has_theme_font_size" || pf == "has_theme_font_size_override" || pf == "get_theme_font_size") { - Theme::get_default()->get_font_size_list(get_class(), &sn); + ThemeDB::get_singleton()->get_default_theme()->get_font_size_list(get_class(), &sn); } else if (pf == "add_theme_constant_override" || pf == "has_theme_constant" || pf == "has_theme_constant_override" || pf == "get_theme_constant") { - Theme::get_default()->get_constant_list(get_class(), &sn); + ThemeDB::get_singleton()->get_default_theme()->get_constant_list(get_class(), &sn); } sn.sort_custom<StringName::AlphCompare>(); @@ -252,36 +254,36 @@ bool Control::_set(const StringName &p_name, const Variant &p_value) { if (name.begins_with("theme_override_icons/")) { String dname = name.get_slicec('/', 1); if (data.icon_override.has(dname)) { - data.icon_override[dname]->disconnect("changed", callable_mp(this, &Control::_theme_property_override_changed)); + data.icon_override[dname]->disconnect("changed", callable_mp(this, &Control::_notify_theme_override_changed)); } data.icon_override.erase(dname); - notification(NOTIFICATION_THEME_CHANGED); + _notify_theme_override_changed(); } else if (name.begins_with("theme_override_styles/")) { String dname = name.get_slicec('/', 1); if (data.style_override.has(dname)) { - data.style_override[dname]->disconnect("changed", callable_mp(this, &Control::_theme_property_override_changed)); + data.style_override[dname]->disconnect("changed", callable_mp(this, &Control::_notify_theme_override_changed)); } data.style_override.erase(dname); - notification(NOTIFICATION_THEME_CHANGED); + _notify_theme_override_changed(); } else if (name.begins_with("theme_override_fonts/")) { String dname = name.get_slicec('/', 1); if (data.font_override.has(dname)) { - data.font_override[dname]->disconnect("changed", callable_mp(this, &Control::_theme_property_override_changed)); + data.font_override[dname]->disconnect("changed", callable_mp(this, &Control::_notify_theme_override_changed)); } data.font_override.erase(dname); - notification(NOTIFICATION_THEME_CHANGED); + _notify_theme_override_changed(); } else if (name.begins_with("theme_override_font_sizes/")) { String dname = name.get_slicec('/', 1); data.font_size_override.erase(dname); - notification(NOTIFICATION_THEME_CHANGED); + _notify_theme_override_changed(); } else if (name.begins_with("theme_override_colors/")) { String dname = name.get_slicec('/', 1); data.color_override.erase(dname); - notification(NOTIFICATION_THEME_CHANGED); + _notify_theme_override_changed(); } else if (name.begins_with("theme_override_constants/")) { String dname = name.get_slicec('/', 1); data.constant_override.erase(dname); - notification(NOTIFICATION_THEME_CHANGED); + _notify_theme_override_changed(); } else { return false; } @@ -344,7 +346,7 @@ bool Control::_get(const StringName &p_name, Variant &r_ret) const { } void Control::_get_property_list(List<PropertyInfo> *p_list) const { - Ref<Theme> theme = Theme::get_default(); + Ref<Theme> theme = ThemeDB::get_singleton()->get_default_theme(); p_list->push_back(PropertyInfo(Variant::NIL, TTRC("Theme Overrides"), PROPERTY_HINT_NONE, "theme_override_", PROPERTY_USAGE_GROUP)); @@ -422,16 +424,16 @@ void Control::_get_property_list(List<PropertyInfo> *p_list) const { } } -void Control::_validate_property(PropertyInfo &property) const { +void Control::_validate_property(PropertyInfo &p_property) const { // Update theme type variation options. - if (property.name == "theme_type_variation") { + if (p_property.name == "theme_type_variation") { List<StringName> names; // Only the default theme and the project theme are used for the list of options. // This is an imposed limitation to simplify the logic needed to leverage those options. - Theme::get_default()->get_type_variation_list(get_class_name(), &names); - if (Theme::get_project_default().is_valid()) { - Theme::get_project_default()->get_type_variation_list(get_class_name(), &names); + ThemeDB::get_singleton()->get_default_theme()->get_type_variation_list(get_class_name(), &names); + if (ThemeDB::get_singleton()->get_project_theme().is_valid()) { + ThemeDB::get_singleton()->get_project_theme()->get_type_variation_list(get_class_name(), &names); } names.sort_custom<StringName::AlphCompare>(); @@ -447,18 +449,18 @@ void Control::_validate_property(PropertyInfo &property) const { unique_names.append(E); } - property.hint_string = hint_string; + p_property.hint_string = hint_string; } - if (property.name == "mouse_force_pass_scroll_events") { + if (p_property.name == "mouse_force_pass_scroll_events") { // Disable force pass if the control is not stopping the event. if (data.mouse_filter != MOUSE_FILTER_STOP) { - property.usage |= PROPERTY_USAGE_READ_ONLY; + p_property.usage |= PROPERTY_USAGE_READ_ONLY; } } - if (property.name == "scale") { - property.hint = PROPERTY_HINT_LINK; + if (p_property.name == "scale") { + p_property.hint = PROPERTY_HINT_LINK; } // Validate which positioning properties should be displayed depending on the parent and the layout mode. @@ -467,33 +469,33 @@ void Control::_validate_property(PropertyInfo &property) const { // If there is no parent, display both anchor and container options. // Set the layout mode to be disabled with the proper value. - if (property.name == "layout_mode") { - property.hint_string = "Position,Anchors,Container,Uncontrolled"; - property.usage |= PROPERTY_USAGE_READ_ONLY; + if (p_property.name == "layout_mode") { + p_property.hint_string = "Position,Anchors,Container,Uncontrolled"; + p_property.usage |= PROPERTY_USAGE_READ_ONLY; } // Use the layout mode to display or hide advanced anchoring properties. bool use_custom_anchors = _get_anchors_layout_preset() == -1; // Custom "preset". - if (!use_custom_anchors && (property.name.begins_with("anchor_") || property.name.begins_with("offset_") || property.name.begins_with("grow_"))) { - property.usage ^= PROPERTY_USAGE_EDITOR; + if (!use_custom_anchors && (p_property.name.begins_with("anchor_") || p_property.name.begins_with("offset_") || p_property.name.begins_with("grow_"))) { + p_property.usage ^= PROPERTY_USAGE_EDITOR; } } else if (Object::cast_to<Container>(parent_node)) { // If the parent is a container, display only container-related properties. - if (property.name.begins_with("anchor_") || property.name.begins_with("offset_") || property.name.begins_with("grow_") || property.name == "anchors_preset" || - property.name == "position" || property.name == "rotation" || property.name == "scale" || property.name == "size" || property.name == "pivot_offset") { - property.usage ^= PROPERTY_USAGE_EDITOR; + if (p_property.name.begins_with("anchor_") || p_property.name.begins_with("offset_") || p_property.name.begins_with("grow_") || p_property.name == "anchors_preset" || + p_property.name == "position" || p_property.name == "rotation" || p_property.name == "scale" || p_property.name == "size" || p_property.name == "pivot_offset") { + p_property.usage ^= PROPERTY_USAGE_EDITOR; - } else if (property.name == "layout_mode") { + } else if (p_property.name == "layout_mode") { // Set the layout mode to be disabled with the proper value. - property.hint_string = "Position,Anchors,Container,Uncontrolled"; - property.usage |= PROPERTY_USAGE_READ_ONLY; - } else if (property.name == "size_flags_horizontal" || property.name == "size_flags_vertical") { + p_property.hint_string = "Position,Anchors,Container,Uncontrolled"; + p_property.usage |= PROPERTY_USAGE_READ_ONLY; + } else if (p_property.name == "size_flags_horizontal" || p_property.name == "size_flags_vertical") { // Filter allowed size flags based on the parent container configuration. Container *parent_container = Object::cast_to<Container>(parent_node); Vector<int> size_flags; - if (property.name == "size_flags_horizontal") { + if (p_property.name == "size_flags_horizontal") { size_flags = parent_container->get_allowed_size_flags_horizontal(); - } else if (property.name == "size_flags_vertical") { + } else if (p_property.name == "size_flags_vertical") { size_flags = parent_container->get_allowed_size_flags_vertical(); } @@ -522,30 +524,30 @@ void Control::_validate_property(PropertyInfo &property) const { } if (hint_string.is_empty()) { - property.hint_string = ""; - property.usage |= PROPERTY_USAGE_READ_ONLY; + p_property.hint_string = ""; + p_property.usage |= PROPERTY_USAGE_READ_ONLY; } else { - property.hint_string = hint_string; + p_property.hint_string = hint_string; } } } else { // If the parent is NOT a container or not a control at all, display only anchoring-related properties. - if (property.name.begins_with("size_flags_")) { - property.usage ^= PROPERTY_USAGE_EDITOR; + if (p_property.name.begins_with("size_flags_")) { + p_property.usage ^= PROPERTY_USAGE_EDITOR; - } else if (property.name == "layout_mode") { + } else if (p_property.name == "layout_mode") { // Set the layout mode to be enabled with proper options. - property.hint_string = "Position,Anchors"; + p_property.hint_string = "Position,Anchors"; } // Use the layout mode to display or hide advanced anchoring properties. bool use_anchors = _get_layout_mode() == LayoutMode::LAYOUT_MODE_ANCHORS; - if (!use_anchors && property.name == "anchors_preset") { - property.usage ^= PROPERTY_USAGE_EDITOR; + if (!use_anchors && p_property.name == "anchors_preset") { + p_property.usage ^= PROPERTY_USAGE_EDITOR; } bool use_custom_anchors = use_anchors && _get_anchors_layout_preset() == -1; // Custom "preset". - if (!use_custom_anchors && (property.name.begins_with("anchor_") || property.name.begins_with("offset_") || property.name.begins_with("grow_"))) { - property.usage ^= PROPERTY_USAGE_EDITOR; + if (!use_custom_anchors && (p_property.name.begins_with("anchor_") || p_property.name.begins_with("offset_") || p_property.name.begins_with("grow_"))) { + p_property.usage ^= PROPERTY_USAGE_EDITOR; } } @@ -555,14 +557,34 @@ void Control::_validate_property(PropertyInfo &property) const { } bool property_is_managed_by_container = false; for (unsigned i = 0; i < properties_managed_by_container_count; i++) { - property_is_managed_by_container = properties_managed_by_container[i] == property.name; + property_is_managed_by_container = properties_managed_by_container[i] == p_property.name; if (property_is_managed_by_container) { break; } } if (property_is_managed_by_container) { - property.usage |= PROPERTY_USAGE_READ_ONLY; + p_property.usage |= PROPERTY_USAGE_READ_ONLY; + } +} + +bool Control::_property_can_revert(const StringName &p_name) const { + if (p_name == "layout_mode" || p_name == "anchors_preset") { + return true; + } + + return false; +} + +bool Control::_property_get_revert(const StringName &p_name, Variant &r_property) const { + if (p_name == "layout_mode") { + r_property = _get_default_layout_mode(); + return true; + } else if (p_name == "anchors_preset") { + r_property = LayoutPreset::PRESET_TOP_LEFT; + return true; } + + return false; } // Global relations. @@ -692,7 +714,7 @@ void Control::set_anchor(Side p_side, real_t p_anchor, bool p_keep_offset, bool _size_changed(); } - update(); + queue_redraw(); } real_t Control::get_anchor(Side p_side) const { @@ -703,6 +725,9 @@ real_t Control::get_anchor(Side p_side) const { void Control::set_offset(Side p_side, real_t p_value) { ERR_FAIL_INDEX((int)p_side, 4); + if (data.offset[p_side] == p_value) { + return; + } data.offset[p_side] = p_value; _size_changed(); @@ -720,6 +745,10 @@ void Control::set_anchor_and_offset(Side p_side, real_t p_anchor, real_t p_pos, } void Control::set_begin(const Size2 &p_point) { + if (data.offset[0] == p_point.x && data.offset[1] == p_point.y) { + return; + } + data.offset[0] = p_point.x; data.offset[1] = p_point.y; _size_changed(); @@ -730,6 +759,10 @@ Size2 Control::get_begin() const { } void Control::set_end(const Size2 &p_point) { + if (data.offset[2] == p_point.x && data.offset[3] == p_point.y) { + return; + } + data.offset[2] = p_point.x; data.offset[3] = p_point.y; _size_changed(); @@ -740,6 +773,10 @@ Size2 Control::get_end() const { } void Control::set_h_grow_direction(GrowDirection p_direction) { + if (data.h_grow == p_direction) { + return; + } + ERR_FAIL_INDEX((int)p_direction, 3); data.h_grow = p_direction; @@ -751,6 +788,10 @@ Control::GrowDirection Control::get_h_grow_direction() const { } void Control::set_v_grow_direction(GrowDirection p_direction) { + if (data.v_grow == p_direction) { + return; + } + ERR_FAIL_INDEX((int)p_direction, 3); data.v_grow = p_direction; @@ -794,24 +835,15 @@ void Control::_compute_offsets(Rect2 p_rect, const real_t p_anchors[4], real_t ( void Control::_set_layout_mode(LayoutMode p_mode) { bool list_changed = false; - if (p_mode == LayoutMode::LAYOUT_MODE_POSITION || p_mode == LayoutMode::LAYOUT_MODE_ANCHORS) { - if ((int)get_meta("_edit_layout_mode", p_mode) != (int)p_mode) { - list_changed = true; - } - - set_meta("_edit_layout_mode", (int)p_mode); + if (data.stored_layout_mode != p_mode) { + list_changed = true; + data.stored_layout_mode = p_mode; + } - if (p_mode == LayoutMode::LAYOUT_MODE_POSITION) { - remove_meta("_edit_layout_mode"); - remove_meta("_edit_use_custom_anchors"); - set_anchors_and_offsets_preset(LayoutPreset::PRESET_TOP_LEFT, LayoutPresetMode::PRESET_MODE_KEEP_SIZE); - set_grow_direction_preset(LayoutPreset::PRESET_TOP_LEFT); - } - } else { - if (has_meta("_edit_layout_mode")) { - remove_meta("_edit_layout_mode"); - list_changed = true; - } + if (data.stored_layout_mode == LayoutMode::LAYOUT_MODE_POSITION) { + data.stored_use_custom_anchors = false; + set_anchors_and_offsets_preset(LayoutPreset::PRESET_TOP_LEFT, LayoutPresetMode::PRESET_MODE_KEEP_SIZE); + set_grow_direction_preset(LayoutPreset::PRESET_TOP_LEFT); } if (list_changed) { @@ -832,33 +864,43 @@ Control::LayoutMode Control::_get_layout_mode() const { if (_get_anchors_layout_preset() != (int)LayoutPreset::PRESET_TOP_LEFT) { return LayoutMode::LAYOUT_MODE_ANCHORS; } - // Otherwise check what was saved. - if (has_meta("_edit_layout_mode")) { - return (LayoutMode)(int)get_meta("_edit_layout_mode"); + + // Otherwise fallback on what's stored. + return data.stored_layout_mode; +} + +Control::LayoutMode Control::_get_default_layout_mode() const { + Node *parent_node = get_parent_control(); + // In these modes the property is read-only. + if (!parent_node) { + return LayoutMode::LAYOUT_MODE_UNCONTROLLED; + } else if (Object::cast_to<Container>(parent_node)) { + return LayoutMode::LAYOUT_MODE_CONTAINER; } - // Or fallback on default. + + // Otherwise fallback on the position mode. return LayoutMode::LAYOUT_MODE_POSITION; } void Control::_set_anchors_layout_preset(int p_preset) { bool list_changed = false; - if (get_meta("_edit_layout_mode", LayoutMode::LAYOUT_MODE_ANCHORS).operator int() != LayoutMode::LAYOUT_MODE_ANCHORS) { + if (data.stored_layout_mode != LayoutMode::LAYOUT_MODE_ANCHORS) { list_changed = true; - set_meta("_edit_layout_mode", LayoutMode::LAYOUT_MODE_ANCHORS); + data.stored_layout_mode = LayoutMode::LAYOUT_MODE_ANCHORS; } if (p_preset == -1) { - if (!get_meta("_edit_use_custom_anchors", false)) { - set_meta("_edit_use_custom_anchors", true); + if (!data.stored_use_custom_anchors) { + data.stored_use_custom_anchors = true; notify_property_list_changed(); } return; // Keep settings as is. } - if (get_meta("_edit_use_custom_anchors", true)) { + if (data.stored_use_custom_anchors) { list_changed = true; - remove_meta("_edit_use_custom_anchors"); + data.stored_use_custom_anchors = false; } LayoutPreset preset = (LayoutPreset)p_preset; @@ -899,7 +941,7 @@ void Control::_set_anchors_layout_preset(int p_preset) { int Control::_get_anchors_layout_preset() const { // If the custom preset was selected by user, use it. - if ((bool)get_meta("_edit_use_custom_anchors", false)) { + if (data.stored_use_custom_anchors) { return -1; } @@ -1406,6 +1448,10 @@ Rect2 Control::get_anchorable_rect() const { } void Control::set_scale(const Vector2 &p_scale) { + if (data.scale == p_scale) { + return; + } + data.scale = p_scale; // Avoid having 0 scale values, can lead to errors in physics and rendering. if (data.scale.x == 0) { @@ -1414,7 +1460,7 @@ void Control::set_scale(const Vector2 &p_scale) { if (data.scale.y == 0) { data.scale.y = CMP_EPSILON; } - update(); + queue_redraw(); _notify_transform(); } @@ -1423,8 +1469,12 @@ Vector2 Control::get_scale() const { } void Control::set_rotation(real_t p_radians) { + if (data.rotation == p_radians) { + return; + } + data.rotation = p_radians; - update(); + queue_redraw(); _notify_transform(); } @@ -1433,8 +1483,12 @@ real_t Control::get_rotation() const { } void Control::set_pivot_offset(const Vector2 &p_pivot) { + if (data.pivot_offset == p_pivot) { + return; + } + data.pivot_offset = p_pivot; - update(); + queue_redraw(); _notify_transform(); } @@ -2183,8 +2237,11 @@ Control::CursorShape Control::get_cursor_shape(const Point2 &p_pos) const { } void Control::set_disable_visibility_clip(bool p_ignore) { + if (data.disable_visibility_clip == p_ignore) { + return; + } data.disable_visibility_clip = p_ignore; - update(); + queue_redraw(); } bool Control::is_visibility_clip_disabled() const { @@ -2192,8 +2249,11 @@ bool Control::is_visibility_clip_disabled() const { } void Control::set_clip_contents(bool p_clip) { + if (data.clip_contents == p_clip) { + return; + } data.clip_contents = p_clip; - update(); + queue_redraw(); } bool Control::is_clipping_contents() { @@ -2202,64 +2262,40 @@ bool Control::is_clipping_contents() { // Theming. -void Control::_propagate_theme_changed(Node *p_at, Control *p_owner, Window *p_owner_window, bool p_assign) { - Control *c = Object::cast_to<Control>(p_at); - - if (c && c != p_owner && c->data.theme.is_valid()) { // has a theme, this can't be propagated - return; - } - - Window *w = c == nullptr ? Object::cast_to<Window>(p_at) : nullptr; - - if (w && w != p_owner_window && w->theme.is_valid()) { // has a theme, this can't be propagated - return; +void Control::_theme_changed() { + if (is_inside_tree()) { + data.theme_owner->propagate_theme_changed(this, this, true, false); } +} - for (int i = 0; i < p_at->get_child_count(); i++) { - CanvasItem *child = Object::cast_to<CanvasItem>(p_at->get_child(i)); - if (child) { - _propagate_theme_changed(child, p_owner, p_owner_window, p_assign); - } else { - Window *window = Object::cast_to<Window>(p_at->get_child(i)); - if (window) { - _propagate_theme_changed(window, p_owner, p_owner_window, p_assign); - } - } +void Control::_notify_theme_override_changed() { + if (!data.bulk_theme_override && is_inside_tree()) { + notification(NOTIFICATION_THEME_CHANGED); } +} - if (c) { - if (p_assign) { - c->data.theme_owner = p_owner; - c->data.theme_owner_window = p_owner_window; - } - c->notification(Control::NOTIFICATION_THEME_CHANGED); - c->emit_signal(SceneStringNames::get_singleton()->theme_changed); - } +void Control::_invalidate_theme_cache() { + data.theme_icon_cache.clear(); + data.theme_style_cache.clear(); + data.theme_font_cache.clear(); + data.theme_font_size_cache.clear(); + data.theme_color_cache.clear(); + data.theme_constant_cache.clear(); +} - if (w) { - if (p_assign) { - w->theme_owner = p_owner; - w->theme_owner_window = p_owner_window; - } - w->notification(Window::NOTIFICATION_THEME_CHANGED); - w->emit_signal(SceneStringNames::get_singleton()->theme_changed); - } +void Control::_update_theme_item_cache() { } -void Control::_theme_changed() { - _propagate_theme_changed(this, this, nullptr, false); +void Control::set_theme_owner_node(Node *p_node) { + data.theme_owner->set_owner_node(p_node); } -void Control::_theme_property_override_changed() { - notification(NOTIFICATION_THEME_CHANGED); - emit_signal(SceneStringNames::get_singleton()->theme_changed); - update_minimum_size(); // Overrides are likely to affect minimum size. +Node *Control::get_theme_owner_node() const { + return data.theme_owner->get_owner_node(); } -void Control::_notify_theme_changed() { - if (!data.bulk_theme_override) { - notification(NOTIFICATION_THEME_CHANGED); - } +bool Control::has_theme_owner_node() const { + return data.theme_owner->has_owner_node(); } void Control::set_theme(const Ref<Theme> &p_theme) { @@ -2272,28 +2308,25 @@ void Control::set_theme(const Ref<Theme> &p_theme) { } data.theme = p_theme; - if (!p_theme.is_null()) { - data.theme_owner = this; - data.theme_owner_window = nullptr; - _propagate_theme_changed(this, this, nullptr); - } else { - Control *parent_c = Object::cast_to<Control>(get_parent()); + if (data.theme.is_valid()) { + data.theme_owner->propagate_theme_changed(this, this, is_inside_tree(), true); + data.theme->connect("changed", callable_mp(this, &Control::_theme_changed), CONNECT_DEFERRED); + return; + } - if (parent_c && (parent_c->data.theme_owner || parent_c->data.theme_owner_window)) { - Control::_propagate_theme_changed(this, parent_c->data.theme_owner, parent_c->data.theme_owner_window); - } else { - Window *parent_w = cast_to<Window>(get_parent()); - if (parent_w && (parent_w->theme_owner || parent_w->theme_owner_window)) { - Control::_propagate_theme_changed(this, parent_w->theme_owner, parent_w->theme_owner_window); - } else { - Control::_propagate_theme_changed(this, nullptr, nullptr); - } - } + Control *parent_c = Object::cast_to<Control>(get_parent()); + if (parent_c && parent_c->has_theme_owner_node()) { + data.theme_owner->propagate_theme_changed(this, parent_c->get_theme_owner_node(), is_inside_tree(), true); + return; } - if (data.theme.is_valid()) { - data.theme->connect("changed", callable_mp(this, &Control::_theme_changed), CONNECT_DEFERRED); + Window *parent_w = cast_to<Window>(get_parent()); + if (parent_w && parent_w->has_theme_owner_node()) { + data.theme_owner->propagate_theme_changed(this, parent_w->get_theme_owner_node(), is_inside_tree(), true); + return; } + + data.theme_owner->propagate_theme_changed(this, nullptr, is_inside_tree(), true); } Ref<Theme> Control::get_theme() const { @@ -2301,8 +2334,13 @@ Ref<Theme> Control::get_theme() const { } void Control::set_theme_type_variation(const StringName &p_theme_type) { + if (data.theme_type_variation == p_theme_type) { + return; + } data.theme_type_variation = p_theme_type; - _propagate_theme_changed(this, data.theme_owner, data.theme_owner_window); + if (is_inside_tree()) { + notification(NOTIFICATION_THEME_CHANGED); + } } StringName Control::get_theme_type_variation() const { @@ -2311,130 +2349,6 @@ StringName Control::get_theme_type_variation() const { /// Theme property lookup. -template <class T> -T Control::get_theme_item_in_types(Control *p_theme_owner, Window *p_theme_owner_window, Theme::DataType p_data_type, const StringName &p_name, List<StringName> p_theme_types) { - ERR_FAIL_COND_V_MSG(p_theme_types.size() == 0, T(), "At least one theme type must be specified."); - - // First, look through each control or window node in the branch, until no valid parent can be found. - // Only nodes with a theme resource attached are considered. - Control *theme_owner = p_theme_owner; - Window *theme_owner_window = p_theme_owner_window; - - while (theme_owner || theme_owner_window) { - // For each theme resource check the theme types provided and see if p_name exists with any of them. - for (const StringName &E : p_theme_types) { - if (theme_owner && theme_owner->data.theme->has_theme_item(p_data_type, p_name, E)) { - return theme_owner->data.theme->get_theme_item(p_data_type, p_name, E); - } - - if (theme_owner_window && theme_owner_window->theme->has_theme_item(p_data_type, p_name, E)) { - return theme_owner_window->theme->get_theme_item(p_data_type, p_name, E); - } - } - - Node *parent = theme_owner ? theme_owner->get_parent() : theme_owner_window->get_parent(); - Control *parent_c = Object::cast_to<Control>(parent); - if (parent_c) { - theme_owner = parent_c->data.theme_owner; - theme_owner_window = parent_c->data.theme_owner_window; - } else { - Window *parent_w = Object::cast_to<Window>(parent); - if (parent_w) { - theme_owner = parent_w->theme_owner; - theme_owner_window = parent_w->theme_owner_window; - } else { - theme_owner = nullptr; - theme_owner_window = nullptr; - } - } - } - - // Secondly, check the project-defined Theme resource. - if (Theme::get_project_default().is_valid()) { - for (const StringName &E : p_theme_types) { - if (Theme::get_project_default()->has_theme_item(p_data_type, p_name, E)) { - return Theme::get_project_default()->get_theme_item(p_data_type, p_name, E); - } - } - } - - // Lastly, fall back on the items defined in the default Theme, if they exist. - for (const StringName &E : p_theme_types) { - if (Theme::get_default()->has_theme_item(p_data_type, p_name, E)) { - return Theme::get_default()->get_theme_item(p_data_type, p_name, E); - } - } - // If they don't exist, use any type to return the default/empty value. - return Theme::get_default()->get_theme_item(p_data_type, p_name, p_theme_types[0]); -} - -bool Control::has_theme_item_in_types(Control *p_theme_owner, Window *p_theme_owner_window, Theme::DataType p_data_type, const StringName &p_name, List<StringName> p_theme_types) { - ERR_FAIL_COND_V_MSG(p_theme_types.size() == 0, false, "At least one theme type must be specified."); - - // First, look through each control or window node in the branch, until no valid parent can be found. - // Only nodes with a theme resource attached are considered. - Control *theme_owner = p_theme_owner; - Window *theme_owner_window = p_theme_owner_window; - - while (theme_owner || theme_owner_window) { - // For each theme resource check the theme types provided and see if p_name exists with any of them. - for (const StringName &E : p_theme_types) { - if (theme_owner && theme_owner->data.theme->has_theme_item(p_data_type, p_name, E)) { - return true; - } - - if (theme_owner_window && theme_owner_window->theme->has_theme_item(p_data_type, p_name, E)) { - return true; - } - } - - Node *parent = theme_owner ? theme_owner->get_parent() : theme_owner_window->get_parent(); - Control *parent_c = Object::cast_to<Control>(parent); - if (parent_c) { - theme_owner = parent_c->data.theme_owner; - theme_owner_window = parent_c->data.theme_owner_window; - } else { - Window *parent_w = Object::cast_to<Window>(parent); - if (parent_w) { - theme_owner = parent_w->theme_owner; - theme_owner_window = parent_w->theme_owner_window; - } else { - theme_owner = nullptr; - theme_owner_window = nullptr; - } - } - } - - // Secondly, check the project-defined Theme resource. - if (Theme::get_project_default().is_valid()) { - for (const StringName &E : p_theme_types) { - if (Theme::get_project_default()->has_theme_item(p_data_type, p_name, E)) { - return true; - } - } - } - - // Lastly, fall back on the items defined in the default Theme, if they exist. - for (const StringName &E : p_theme_types) { - if (Theme::get_default()->has_theme_item(p_data_type, p_name, E)) { - return true; - } - } - return false; -} - -void Control::_get_theme_type_dependencies(const StringName &p_theme_type, List<StringName> *p_list) const { - if (p_theme_type == StringName() || p_theme_type == get_class_name() || p_theme_type == data.theme_type_variation) { - if (Theme::get_project_default().is_valid() && Theme::get_project_default()->get_type_variation_base(data.theme_type_variation) != StringName()) { - Theme::get_project_default()->get_type_dependencies(get_class_name(), data.theme_type_variation, p_list); - } else { - Theme::get_default()->get_type_dependencies(get_class_name(), data.theme_type_variation, p_list); - } - } else { - Theme::get_default()->get_type_dependencies(p_theme_type, StringName(), p_list); - } -} - Ref<Texture2D> Control::get_theme_icon(const StringName &p_name, const StringName &p_theme_type) const { if (p_theme_type == StringName() || p_theme_type == get_class_name() || p_theme_type == data.theme_type_variation) { const Ref<Texture2D> *tex = data.icon_override.getptr(p_name); @@ -2443,9 +2357,15 @@ Ref<Texture2D> Control::get_theme_icon(const StringName &p_name, const StringNam } } + if (data.theme_icon_cache.has(p_theme_type) && data.theme_icon_cache[p_theme_type].has(p_name)) { + return data.theme_icon_cache[p_theme_type][p_name]; + } + List<StringName> theme_types; - _get_theme_type_dependencies(p_theme_type, &theme_types); - return get_theme_item_in_types<Ref<Texture2D>>(data.theme_owner, data.theme_owner_window, Theme::DATA_TYPE_ICON, p_name, theme_types); + data.theme_owner->get_theme_type_dependencies(this, p_theme_type, &theme_types); + Ref<Texture2D> icon = data.theme_owner->get_theme_item_in_types(Theme::DATA_TYPE_ICON, p_name, theme_types); + data.theme_icon_cache[p_theme_type][p_name] = icon; + return icon; } Ref<StyleBox> Control::get_theme_stylebox(const StringName &p_name, const StringName &p_theme_type) const { @@ -2456,9 +2376,15 @@ Ref<StyleBox> Control::get_theme_stylebox(const StringName &p_name, const String } } + if (data.theme_style_cache.has(p_theme_type) && data.theme_style_cache[p_theme_type].has(p_name)) { + return data.theme_style_cache[p_theme_type][p_name]; + } + List<StringName> theme_types; - _get_theme_type_dependencies(p_theme_type, &theme_types); - return get_theme_item_in_types<Ref<StyleBox>>(data.theme_owner, data.theme_owner_window, Theme::DATA_TYPE_STYLEBOX, p_name, theme_types); + data.theme_owner->get_theme_type_dependencies(this, p_theme_type, &theme_types); + Ref<StyleBox> style = data.theme_owner->get_theme_item_in_types(Theme::DATA_TYPE_STYLEBOX, p_name, theme_types); + data.theme_style_cache[p_theme_type][p_name] = style; + return style; } Ref<Font> Control::get_theme_font(const StringName &p_name, const StringName &p_theme_type) const { @@ -2469,9 +2395,15 @@ Ref<Font> Control::get_theme_font(const StringName &p_name, const StringName &p_ } } + if (data.theme_font_cache.has(p_theme_type) && data.theme_font_cache[p_theme_type].has(p_name)) { + return data.theme_font_cache[p_theme_type][p_name]; + } + List<StringName> theme_types; - _get_theme_type_dependencies(p_theme_type, &theme_types); - return get_theme_item_in_types<Ref<Font>>(data.theme_owner, data.theme_owner_window, Theme::DATA_TYPE_FONT, p_name, theme_types); + data.theme_owner->get_theme_type_dependencies(this, p_theme_type, &theme_types); + Ref<Font> font = data.theme_owner->get_theme_item_in_types(Theme::DATA_TYPE_FONT, p_name, theme_types); + data.theme_font_cache[p_theme_type][p_name] = font; + return font; } int Control::get_theme_font_size(const StringName &p_name, const StringName &p_theme_type) const { @@ -2482,9 +2414,15 @@ int Control::get_theme_font_size(const StringName &p_name, const StringName &p_t } } + if (data.theme_font_size_cache.has(p_theme_type) && data.theme_font_size_cache[p_theme_type].has(p_name)) { + return data.theme_font_size_cache[p_theme_type][p_name]; + } + List<StringName> theme_types; - _get_theme_type_dependencies(p_theme_type, &theme_types); - return get_theme_item_in_types<int>(data.theme_owner, data.theme_owner_window, Theme::DATA_TYPE_FONT_SIZE, p_name, theme_types); + data.theme_owner->get_theme_type_dependencies(this, p_theme_type, &theme_types); + int font_size = data.theme_owner->get_theme_item_in_types(Theme::DATA_TYPE_FONT_SIZE, p_name, theme_types); + data.theme_font_size_cache[p_theme_type][p_name] = font_size; + return font_size; } Color Control::get_theme_color(const StringName &p_name, const StringName &p_theme_type) const { @@ -2495,9 +2433,15 @@ Color Control::get_theme_color(const StringName &p_name, const StringName &p_the } } + if (data.theme_color_cache.has(p_theme_type) && data.theme_color_cache[p_theme_type].has(p_name)) { + return data.theme_color_cache[p_theme_type][p_name]; + } + List<StringName> theme_types; - _get_theme_type_dependencies(p_theme_type, &theme_types); - return get_theme_item_in_types<Color>(data.theme_owner, data.theme_owner_window, Theme::DATA_TYPE_COLOR, p_name, theme_types); + data.theme_owner->get_theme_type_dependencies(this, p_theme_type, &theme_types); + Color color = data.theme_owner->get_theme_item_in_types(Theme::DATA_TYPE_COLOR, p_name, theme_types); + data.theme_color_cache[p_theme_type][p_name] = color; + return color; } int Control::get_theme_constant(const StringName &p_name, const StringName &p_theme_type) const { @@ -2508,9 +2452,15 @@ int Control::get_theme_constant(const StringName &p_name, const StringName &p_th } } + if (data.theme_constant_cache.has(p_theme_type) && data.theme_constant_cache[p_theme_type].has(p_name)) { + return data.theme_constant_cache[p_theme_type][p_name]; + } + List<StringName> theme_types; - _get_theme_type_dependencies(p_theme_type, &theme_types); - return get_theme_item_in_types<int>(data.theme_owner, data.theme_owner_window, Theme::DATA_TYPE_CONSTANT, p_name, theme_types); + data.theme_owner->get_theme_type_dependencies(this, p_theme_type, &theme_types); + int constant = data.theme_owner->get_theme_item_in_types(Theme::DATA_TYPE_CONSTANT, p_name, theme_types); + data.theme_constant_cache[p_theme_type][p_name] = constant; + return constant; } bool Control::has_theme_icon(const StringName &p_name, const StringName &p_theme_type) const { @@ -2521,8 +2471,8 @@ bool Control::has_theme_icon(const StringName &p_name, const StringName &p_theme } List<StringName> theme_types; - _get_theme_type_dependencies(p_theme_type, &theme_types); - return has_theme_item_in_types(data.theme_owner, data.theme_owner_window, Theme::DATA_TYPE_ICON, p_name, theme_types); + data.theme_owner->get_theme_type_dependencies(this, p_theme_type, &theme_types); + return data.theme_owner->has_theme_item_in_types(Theme::DATA_TYPE_ICON, p_name, theme_types); } bool Control::has_theme_stylebox(const StringName &p_name, const StringName &p_theme_type) const { @@ -2533,8 +2483,8 @@ bool Control::has_theme_stylebox(const StringName &p_name, const StringName &p_t } List<StringName> theme_types; - _get_theme_type_dependencies(p_theme_type, &theme_types); - return has_theme_item_in_types(data.theme_owner, data.theme_owner_window, Theme::DATA_TYPE_STYLEBOX, p_name, theme_types); + data.theme_owner->get_theme_type_dependencies(this, p_theme_type, &theme_types); + return data.theme_owner->has_theme_item_in_types(Theme::DATA_TYPE_STYLEBOX, p_name, theme_types); } bool Control::has_theme_font(const StringName &p_name, const StringName &p_theme_type) const { @@ -2545,8 +2495,8 @@ bool Control::has_theme_font(const StringName &p_name, const StringName &p_theme } List<StringName> theme_types; - _get_theme_type_dependencies(p_theme_type, &theme_types); - return has_theme_item_in_types(data.theme_owner, data.theme_owner_window, Theme::DATA_TYPE_FONT, p_name, theme_types); + data.theme_owner->get_theme_type_dependencies(this, p_theme_type, &theme_types); + return data.theme_owner->has_theme_item_in_types(Theme::DATA_TYPE_FONT, p_name, theme_types); } bool Control::has_theme_font_size(const StringName &p_name, const StringName &p_theme_type) const { @@ -2557,8 +2507,8 @@ bool Control::has_theme_font_size(const StringName &p_name, const StringName &p_ } List<StringName> theme_types; - _get_theme_type_dependencies(p_theme_type, &theme_types); - return has_theme_item_in_types(data.theme_owner, data.theme_owner_window, Theme::DATA_TYPE_FONT_SIZE, p_name, theme_types); + data.theme_owner->get_theme_type_dependencies(this, p_theme_type, &theme_types); + return data.theme_owner->has_theme_item_in_types(Theme::DATA_TYPE_FONT_SIZE, p_name, theme_types); } bool Control::has_theme_color(const StringName &p_name, const StringName &p_theme_type) const { @@ -2569,8 +2519,8 @@ bool Control::has_theme_color(const StringName &p_name, const StringName &p_them } List<StringName> theme_types; - _get_theme_type_dependencies(p_theme_type, &theme_types); - return has_theme_item_in_types(data.theme_owner, data.theme_owner_window, Theme::DATA_TYPE_COLOR, p_name, theme_types); + data.theme_owner->get_theme_type_dependencies(this, p_theme_type, &theme_types); + return data.theme_owner->has_theme_item_in_types(Theme::DATA_TYPE_COLOR, p_name, theme_types); } bool Control::has_theme_constant(const StringName &p_name, const StringName &p_theme_type) const { @@ -2581,8 +2531,8 @@ bool Control::has_theme_constant(const StringName &p_name, const StringName &p_t } List<StringName> theme_types; - _get_theme_type_dependencies(p_theme_type, &theme_types); - return has_theme_item_in_types(data.theme_owner, data.theme_owner_window, Theme::DATA_TYPE_CONSTANT, p_name, theme_types); + data.theme_owner->get_theme_type_dependencies(this, p_theme_type, &theme_types); + return data.theme_owner->has_theme_item_in_types(Theme::DATA_TYPE_CONSTANT, p_name, theme_types); } /// Local property overrides. @@ -2591,93 +2541,93 @@ void Control::add_theme_icon_override(const StringName &p_name, const Ref<Textur ERR_FAIL_COND(!p_icon.is_valid()); if (data.icon_override.has(p_name)) { - data.icon_override[p_name]->disconnect("changed", callable_mp(this, &Control::_theme_property_override_changed)); + data.icon_override[p_name]->disconnect("changed", callable_mp(this, &Control::_notify_theme_override_changed)); } data.icon_override[p_name] = p_icon; - data.icon_override[p_name]->connect("changed", callable_mp(this, &Control::_theme_property_override_changed), CONNECT_REFERENCE_COUNTED); - _notify_theme_changed(); + data.icon_override[p_name]->connect("changed", callable_mp(this, &Control::_notify_theme_override_changed), CONNECT_REFERENCE_COUNTED); + _notify_theme_override_changed(); } void Control::add_theme_style_override(const StringName &p_name, const Ref<StyleBox> &p_style) { ERR_FAIL_COND(!p_style.is_valid()); if (data.style_override.has(p_name)) { - data.style_override[p_name]->disconnect("changed", callable_mp(this, &Control::_theme_property_override_changed)); + data.style_override[p_name]->disconnect("changed", callable_mp(this, &Control::_notify_theme_override_changed)); } data.style_override[p_name] = p_style; - data.style_override[p_name]->connect("changed", callable_mp(this, &Control::_theme_property_override_changed), CONNECT_REFERENCE_COUNTED); - _notify_theme_changed(); + data.style_override[p_name]->connect("changed", callable_mp(this, &Control::_notify_theme_override_changed), CONNECT_REFERENCE_COUNTED); + _notify_theme_override_changed(); } void Control::add_theme_font_override(const StringName &p_name, const Ref<Font> &p_font) { ERR_FAIL_COND(!p_font.is_valid()); if (data.font_override.has(p_name)) { - data.font_override[p_name]->disconnect("changed", callable_mp(this, &Control::_theme_property_override_changed)); + data.font_override[p_name]->disconnect("changed", callable_mp(this, &Control::_notify_theme_override_changed)); } data.font_override[p_name] = p_font; - data.font_override[p_name]->connect("changed", callable_mp(this, &Control::_theme_property_override_changed), CONNECT_REFERENCE_COUNTED); - _notify_theme_changed(); + data.font_override[p_name]->connect("changed", callable_mp(this, &Control::_notify_theme_override_changed), CONNECT_REFERENCE_COUNTED); + _notify_theme_override_changed(); } void Control::add_theme_font_size_override(const StringName &p_name, int p_font_size) { data.font_size_override[p_name] = p_font_size; - _notify_theme_changed(); + _notify_theme_override_changed(); } void Control::add_theme_color_override(const StringName &p_name, const Color &p_color) { data.color_override[p_name] = p_color; - _notify_theme_changed(); + _notify_theme_override_changed(); } void Control::add_theme_constant_override(const StringName &p_name, int p_constant) { data.constant_override[p_name] = p_constant; - _notify_theme_changed(); + _notify_theme_override_changed(); } void Control::remove_theme_icon_override(const StringName &p_name) { if (data.icon_override.has(p_name)) { - data.icon_override[p_name]->disconnect("changed", callable_mp(this, &Control::_theme_property_override_changed)); + data.icon_override[p_name]->disconnect("changed", callable_mp(this, &Control::_notify_theme_override_changed)); } data.icon_override.erase(p_name); - _notify_theme_changed(); + _notify_theme_override_changed(); } void Control::remove_theme_style_override(const StringName &p_name) { if (data.style_override.has(p_name)) { - data.style_override[p_name]->disconnect("changed", callable_mp(this, &Control::_theme_property_override_changed)); + data.style_override[p_name]->disconnect("changed", callable_mp(this, &Control::_notify_theme_override_changed)); } data.style_override.erase(p_name); - _notify_theme_changed(); + _notify_theme_override_changed(); } void Control::remove_theme_font_override(const StringName &p_name) { if (data.font_override.has(p_name)) { - data.font_override[p_name]->disconnect("changed", callable_mp(this, &Control::_theme_property_override_changed)); + data.font_override[p_name]->disconnect("changed", callable_mp(this, &Control::_notify_theme_override_changed)); } data.font_override.erase(p_name); - _notify_theme_changed(); + _notify_theme_override_changed(); } void Control::remove_theme_font_size_override(const StringName &p_name) { data.font_size_override.erase(p_name); - _notify_theme_changed(); + _notify_theme_override_changed(); } void Control::remove_theme_color_override(const StringName &p_name) { data.color_override.erase(p_name); - _notify_theme_changed(); + _notify_theme_override_changed(); } void Control::remove_theme_constant_override(const StringName &p_name) { data.constant_override.erase(p_name); - _notify_theme_changed(); + _notify_theme_override_changed(); } bool Control::has_theme_icon_override(const StringName &p_name) const { @@ -2712,157 +2662,16 @@ bool Control::has_theme_constant_override(const StringName &p_name) const { /// Default theme properties. -float Control::fetch_theme_default_base_scale(Control *p_theme_owner, Window *p_theme_owner_window) { - // First, look through each control or window node in the branch, until no valid parent can be found. - // Only nodes with a theme resource attached are considered. - // For each theme resource see if their assigned theme has the default value defined and valid. - Control *theme_owner = p_theme_owner; - Window *theme_owner_window = p_theme_owner_window; - - while (theme_owner || theme_owner_window) { - if (theme_owner && theme_owner->data.theme->has_default_base_scale()) { - return theme_owner->data.theme->get_default_base_scale(); - } - - if (theme_owner_window && theme_owner_window->theme->has_default_base_scale()) { - return theme_owner_window->theme->get_default_base_scale(); - } - - Node *parent = theme_owner ? theme_owner->get_parent() : theme_owner_window->get_parent(); - Control *parent_c = Object::cast_to<Control>(parent); - if (parent_c) { - theme_owner = parent_c->data.theme_owner; - theme_owner_window = parent_c->data.theme_owner_window; - } else { - Window *parent_w = Object::cast_to<Window>(parent); - if (parent_w) { - theme_owner = parent_w->theme_owner; - theme_owner_window = parent_w->theme_owner_window; - } else { - theme_owner = nullptr; - theme_owner_window = nullptr; - } - } - } - - // Secondly, check the project-defined Theme resource. - if (Theme::get_project_default().is_valid()) { - if (Theme::get_project_default()->has_default_base_scale()) { - return Theme::get_project_default()->get_default_base_scale(); - } - } - - // Lastly, fall back on the default Theme. - if (Theme::get_default()->has_default_base_scale()) { - return Theme::get_default()->get_default_base_scale(); - } - return Theme::get_fallback_base_scale(); -} - float Control::get_theme_default_base_scale() const { - return fetch_theme_default_base_scale(data.theme_owner, data.theme_owner_window); -} - -Ref<Font> Control::fetch_theme_default_font(Control *p_theme_owner, Window *p_theme_owner_window) { - // First, look through each control or window node in the branch, until no valid parent can be found. - // Only nodes with a theme resource attached are considered. - // For each theme resource see if their assigned theme has the default value defined and valid. - Control *theme_owner = p_theme_owner; - Window *theme_owner_window = p_theme_owner_window; - - while (theme_owner || theme_owner_window) { - if (theme_owner && theme_owner->data.theme->has_default_font()) { - return theme_owner->data.theme->get_default_font(); - } - - if (theme_owner_window && theme_owner_window->theme->has_default_font()) { - return theme_owner_window->theme->get_default_font(); - } - - Node *parent = theme_owner ? theme_owner->get_parent() : theme_owner_window->get_parent(); - Control *parent_c = Object::cast_to<Control>(parent); - if (parent_c) { - theme_owner = parent_c->data.theme_owner; - theme_owner_window = parent_c->data.theme_owner_window; - } else { - Window *parent_w = Object::cast_to<Window>(parent); - if (parent_w) { - theme_owner = parent_w->theme_owner; - theme_owner_window = parent_w->theme_owner_window; - } else { - theme_owner = nullptr; - theme_owner_window = nullptr; - } - } - } - - // Secondly, check the project-defined Theme resource. - if (Theme::get_project_default().is_valid()) { - if (Theme::get_project_default()->has_default_font()) { - return Theme::get_project_default()->get_default_font(); - } - } - - // Lastly, fall back on the default Theme. - if (Theme::get_default()->has_default_font()) { - return Theme::get_default()->get_default_font(); - } - return Theme::get_fallback_font(); + return data.theme_owner->get_theme_default_base_scale(); } Ref<Font> Control::get_theme_default_font() const { - return fetch_theme_default_font(data.theme_owner, data.theme_owner_window); -} - -int Control::fetch_theme_default_font_size(Control *p_theme_owner, Window *p_theme_owner_window) { - // First, look through each control or window node in the branch, until no valid parent can be found. - // Only nodes with a theme resource attached are considered. - // For each theme resource see if their assigned theme has the default value defined and valid. - Control *theme_owner = p_theme_owner; - Window *theme_owner_window = p_theme_owner_window; - - while (theme_owner || theme_owner_window) { - if (theme_owner && theme_owner->data.theme->has_default_font_size()) { - return theme_owner->data.theme->get_default_font_size(); - } - - if (theme_owner_window && theme_owner_window->theme->has_default_font_size()) { - return theme_owner_window->theme->get_default_font_size(); - } - - Node *parent = theme_owner ? theme_owner->get_parent() : theme_owner_window->get_parent(); - Control *parent_c = Object::cast_to<Control>(parent); - if (parent_c) { - theme_owner = parent_c->data.theme_owner; - theme_owner_window = parent_c->data.theme_owner_window; - } else { - Window *parent_w = Object::cast_to<Window>(parent); - if (parent_w) { - theme_owner = parent_w->theme_owner; - theme_owner_window = parent_w->theme_owner_window; - } else { - theme_owner = nullptr; - theme_owner_window = nullptr; - } - } - } - - // Secondly, check the project-defined Theme resource. - if (Theme::get_project_default().is_valid()) { - if (Theme::get_project_default()->has_default_font_size()) { - return Theme::get_project_default()->get_default_font_size(); - } - } - - // Lastly, fall back on the default Theme. - if (Theme::get_default()->has_default_font_size()) { - return Theme::get_default()->get_default_font_size(); - } - return Theme::get_fallback_font_size(); + return data.theme_owner->get_theme_default_font(); } int Control::get_theme_default_font_size() const { - return fetch_theme_default_font_size(data.theme_owner, data.theme_owner_window); + return data.theme_owner->get_theme_default_font_size(); } /// Bulk actions. @@ -2875,18 +2684,18 @@ void Control::end_bulk_theme_override() { ERR_FAIL_COND(!data.bulk_theme_override); data.bulk_theme_override = false; - _notify_theme_changed(); + _notify_theme_override_changed(); } // Internationalization. -Array Control::structured_text_parser(TextServer::StructuredTextParser p_parser_type, const Array &p_args, const String &p_text) const { +TypedArray<Vector2i> Control::structured_text_parser(TextServer::StructuredTextParser p_parser_type, const Array &p_args, const String &p_text) const { if (p_parser_type == TextServer::STRUCTURED_TEXT_CUSTOM) { - Array ret; + TypedArray<Vector2i> ret; if (GDVIRTUAL_CALL(_structured_text_parser, p_args, p_text, ret)) { return ret; } else { - return Array(); + return TypedArray<Vector2i>(); } } else { return TS->parse_structured_text(p_parser_type, p_args, p_text); @@ -2894,6 +2703,9 @@ Array Control::structured_text_parser(TextServer::StructuredTextParser p_parser_ } void Control::set_layout_direction(Control::LayoutDirection p_direction) { + if (data.layout_dir == p_direction) { + return; + } ERR_FAIL_INDEX((int)p_direction, 4); data.layout_dir = p_direction; @@ -2954,12 +2766,12 @@ bool Control::is_auto_translating() const { // Extra properties. -void Control::set_tooltip(const String &p_tooltip) { - data.tooltip = p_tooltip; +void Control::set_tooltip_text(const String &p_hint) { + data.tooltip = p_hint; update_configuration_warnings(); } -String Control::_get_tooltip() const { +String Control::get_tooltip_text() const { return data.tooltip; } @@ -2977,36 +2789,25 @@ Control *Control::make_custom_tooltip(const String &p_text) const { // Base object overrides. -void Control::add_child_notify(Node *p_child) { - Control *child_c = Object::cast_to<Control>(p_child); - - if (child_c && child_c->data.theme.is_null() && (data.theme_owner || data.theme_owner_window)) { - _propagate_theme_changed(child_c, data.theme_owner, data.theme_owner_window); //need to propagate here, since many controls may require setting up stuff - } - - Window *child_w = Object::cast_to<Window>(p_child); - - if (child_w && child_w->theme.is_null() && (data.theme_owner || data.theme_owner_window)) { - _propagate_theme_changed(child_w, data.theme_owner, data.theme_owner_window); //need to propagate here, since many controls may require setting up stuff - } -} - -void Control::remove_child_notify(Node *p_child) { - Control *child_c = Object::cast_to<Control>(p_child); +void Control::_notification(int p_notification) { + switch (p_notification) { + case NOTIFICATION_POSTINITIALIZE: { + _invalidate_theme_cache(); + _update_theme_item_cache(); + } break; - if (child_c && (child_c->data.theme_owner || child_c->data.theme_owner_window) && child_c->data.theme.is_null()) { - _propagate_theme_changed(child_c, nullptr, nullptr); - } + case NOTIFICATION_PARENTED: { + data.theme_owner->assign_theme_on_parented(this); + } break; - Window *child_w = Object::cast_to<Window>(p_child); + case NOTIFICATION_UNPARENTED: { + data.theme_owner->clear_theme_on_unparented(this); + } break; - if (child_w && (child_w->theme_owner || child_w->theme_owner_window) && child_w->theme.is_null()) { - _propagate_theme_changed(child_w, nullptr, nullptr); - } -} + case NOTIFICATION_ENTER_TREE: { + notification(NOTIFICATION_THEME_CHANGED); + } break; -void Control::_notification(int p_notification) { - switch (p_notification) { case NOTIFICATION_POST_ENTER_TREE: { data.minimum_size_valid = false; data.is_rtl_dirty = true; @@ -3020,7 +2821,7 @@ void Control::_notification(int p_notification) { case NOTIFICATION_READY: { #ifdef DEBUG_ENABLED - connect("ready", callable_mp(this, &Control::_clear_size_warning), CONNECT_DEFERRED | CONNECT_ONESHOT); + connect("ready", callable_mp(this, &Control::_clear_size_warning), CONNECT_DEFERRED | CONNECT_ONE_SHOT); #endif } break; @@ -3029,18 +2830,6 @@ void Control::_notification(int p_notification) { data.parent_window = Object::cast_to<Window>(get_parent()); data.is_rtl_dirty = true; - if (data.theme.is_null()) { - if (data.parent && (data.parent->data.theme_owner || data.parent->data.theme_owner_window)) { - data.theme_owner = data.parent->data.theme_owner; - data.theme_owner_window = data.parent->data.theme_owner_window; - notification(NOTIFICATION_THEME_CHANGED); - } else if (data.parent_window && (data.parent_window->theme_owner || data.parent_window->theme_owner_window)) { - data.theme_owner = data.parent_window->theme_owner; - data.theme_owner_window = data.parent_window->theme_owner_window; - notification(NOTIFICATION_THEME_CHANGED); - } - } - CanvasItem *node = this; bool has_parent_control = false; @@ -3106,9 +2895,9 @@ void Control::_notification(int p_notification) { // some parents need to know the order of the children to draw (like TabContainer) // update if necessary if (data.parent) { - data.parent->update(); + data.parent->queue_redraw(); } - update(); + queue_redraw(); if (data.RI) { get_viewport()->_gui_set_root_order_dirty(); @@ -3135,17 +2924,20 @@ void Control::_notification(int p_notification) { case NOTIFICATION_FOCUS_ENTER: { emit_signal(SceneStringNames::get_singleton()->focus_entered); - update(); + queue_redraw(); } break; case NOTIFICATION_FOCUS_EXIT: { emit_signal(SceneStringNames::get_singleton()->focus_exited); - update(); + queue_redraw(); } break; case NOTIFICATION_THEME_CHANGED: { + emit_signal(SceneStringNames::get_singleton()->theme_changed); + _invalidate_theme_cache(); + _update_theme_item_cache(); update_minimum_size(); - update(); + queue_redraw(); } break; case NOTIFICATION_VISIBILITY_CHANGED: { @@ -3164,6 +2956,8 @@ void Control::_notification(int p_notification) { case NOTIFICATION_LAYOUT_DIRECTION_CHANGED: { if (is_inside_tree()) { data.is_rtl_dirty = true; + _invalidate_theme_cache(); + _update_theme_item_cache(); _size_changed(); } } break; @@ -3291,9 +3085,9 @@ void Control::_bind_methods() { ClassDB::bind_method(D_METHOD("set_v_grow_direction", "direction"), &Control::set_v_grow_direction); ClassDB::bind_method(D_METHOD("get_v_grow_direction"), &Control::get_v_grow_direction); - ClassDB::bind_method(D_METHOD("set_tooltip", "tooltip"), &Control::set_tooltip); + ClassDB::bind_method(D_METHOD("set_tooltip_text", "hint"), &Control::set_tooltip_text); + ClassDB::bind_method(D_METHOD("get_tooltip_text"), &Control::get_tooltip_text); ClassDB::bind_method(D_METHOD("get_tooltip", "at_position"), &Control::get_tooltip, DEFVAL(Point2())); - ClassDB::bind_method(D_METHOD("_get_tooltip"), &Control::_get_tooltip); ClassDB::bind_method(D_METHOD("set_default_cursor_shape", "shape"), &Control::set_default_cursor_shape); ClassDB::bind_method(D_METHOD("get_default_cursor_shape"), &Control::get_default_cursor_shape); @@ -3340,7 +3134,7 @@ void Control::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::BOOL, "clip_contents"), "set_clip_contents", "is_clipping_contents"); ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "custom_minimum_size", PROPERTY_HINT_NONE, "suffix:px"), "set_custom_minimum_size", "get_custom_minimum_size"); ADD_PROPERTY(PropertyInfo(Variant::INT, "layout_direction", PROPERTY_HINT_ENUM, "Inherited,Locale,Left-to-Right,Right-to-Left"), "set_layout_direction", "get_layout_direction"); - ADD_PROPERTY(PropertyInfo(Variant::INT, "layout_mode", PROPERTY_HINT_ENUM, "Position,Anchors,Container,Uncontrolled", PROPERTY_USAGE_EDITOR | PROPERTY_USAGE_INTERNAL), "_set_layout_mode", "_get_layout_mode"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "layout_mode", PROPERTY_HINT_ENUM, "Position,Anchors,Container,Uncontrolled", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_INTERNAL), "_set_layout_mode", "_get_layout_mode"); ADD_PROPERTY_DEFAULT("layout_mode", LayoutMode::LAYOUT_MODE_POSITION); const String anchors_presets_options = "Custom:-1,PresetFullRect:15," @@ -3348,14 +3142,14 @@ void Control::_bind_methods() { "PresetCenterLeft:4,PresetCenterTop:5,PresetCenterRight:6,PresetCenterBottom:7,PresetCenter:8," "PresetLeftWide:9,PresetTopWide:10,PresetRightWide:11,PresetBottomWide:12,PresetVCenterWide:13,PresetHCenterWide:14"; - ADD_PROPERTY(PropertyInfo(Variant::INT, "anchors_preset", PROPERTY_HINT_ENUM, anchors_presets_options, PROPERTY_USAGE_EDITOR | PROPERTY_USAGE_INTERNAL), "_set_anchors_layout_preset", "_get_anchors_layout_preset"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "anchors_preset", PROPERTY_HINT_ENUM, anchors_presets_options, PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_INTERNAL), "_set_anchors_layout_preset", "_get_anchors_layout_preset"); ADD_PROPERTY_DEFAULT("anchors_preset", -1); ADD_SUBGROUP_INDENT("Anchor Points", "anchor_", 1); - ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "anchor_left", PROPERTY_HINT_RANGE, "0,1,0.001,or_lesser,or_greater"), "_set_anchor", "get_anchor", SIDE_LEFT); - ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "anchor_top", PROPERTY_HINT_RANGE, "0,1,0.001,or_lesser,or_greater"), "_set_anchor", "get_anchor", SIDE_TOP); - ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "anchor_right", PROPERTY_HINT_RANGE, "0,1,0.001,or_lesser,or_greater"), "_set_anchor", "get_anchor", SIDE_RIGHT); - ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "anchor_bottom", PROPERTY_HINT_RANGE, "0,1,0.001,or_lesser,or_greater"), "_set_anchor", "get_anchor", SIDE_BOTTOM); + ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "anchor_left", PROPERTY_HINT_RANGE, "0,1,0.001,or_less,or_greater"), "_set_anchor", "get_anchor", SIDE_LEFT); + ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "anchor_top", PROPERTY_HINT_RANGE, "0,1,0.001,or_less,or_greater"), "_set_anchor", "get_anchor", SIDE_TOP); + ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "anchor_right", PROPERTY_HINT_RANGE, "0,1,0.001,or_less,or_greater"), "_set_anchor", "get_anchor", SIDE_RIGHT); + ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "anchor_bottom", PROPERTY_HINT_RANGE, "0,1,0.001,or_less,or_greater"), "_set_anchor", "get_anchor", SIDE_BOTTOM); ADD_SUBGROUP_INDENT("Anchor Offsets", "offset_", 1); ADD_PROPERTYI(PropertyInfo(Variant::INT, "offset_left", PROPERTY_HINT_RANGE, "-4096,4096,suffix:px"), "set_offset", "get_offset", SIDE_LEFT); @@ -3371,7 +3165,7 @@ void Control::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "size", PROPERTY_HINT_NONE, "suffix:px", PROPERTY_USAGE_EDITOR), "_set_size", "get_size"); ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "position", PROPERTY_HINT_NONE, "suffix:px", PROPERTY_USAGE_EDITOR), "_set_position", "get_position"); ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "global_position", PROPERTY_HINT_NONE, "suffix:px", PROPERTY_USAGE_NONE), "_set_global_position", "get_global_position"); - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "rotation", PROPERTY_HINT_RANGE, "-360,360,0.1,or_lesser,or_greater,radians"), "set_rotation", "get_rotation"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "rotation", PROPERTY_HINT_RANGE, "-360,360,0.1,or_less,or_greater,radians"), "set_rotation", "get_rotation"); ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "scale"), "set_scale", "get_scale"); ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "pivot_offset", PROPERTY_HINT_NONE, "suffix:px"), "set_pivot_offset", "get_pivot_offset"); @@ -3383,8 +3177,8 @@ void Control::_bind_methods() { ADD_GROUP("Auto Translate", ""); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "auto_translate"), "set_auto_translate", "is_auto_translating"); - ADD_GROUP("Hint", "hint_"); - ADD_PROPERTY(PropertyInfo(Variant::STRING, "hint_tooltip", PROPERTY_HINT_MULTILINE_TEXT), "set_tooltip", "_get_tooltip"); + ADD_GROUP("Tooltip", "tooltip_"); + ADD_PROPERTY(PropertyInfo(Variant::STRING, "tooltip_text", PROPERTY_HINT_MULTILINE_TEXT), "set_tooltip_text", "get_tooltip_text"); ADD_GROUP("Focus", "focus_"); ADD_PROPERTYI(PropertyInfo(Variant::NODE_PATH, "focus_neighbor_left", PROPERTY_HINT_NODE_PATH_VALID_TYPES, "Control"), "set_focus_neighbor", "get_focus_neighbor", SIDE_LEFT); @@ -3507,3 +3301,30 @@ void Control::_bind_methods() { GDVIRTUAL_BIND(_gui_input, "event"); } + +Control::Control() { + data.theme_owner = memnew(ThemeOwner); +} + +Control::~Control() { + memdelete(data.theme_owner); + + // Resources need to be disconnected. + for (KeyValue<StringName, Ref<Texture2D>> &E : data.icon_override) { + E.value->disconnect("changed", callable_mp(this, &Control::_notify_theme_override_changed)); + } + for (KeyValue<StringName, Ref<StyleBox>> &E : data.style_override) { + E.value->disconnect("changed", callable_mp(this, &Control::_notify_theme_override_changed)); + } + for (KeyValue<StringName, Ref<Font>> &E : data.font_override) { + E.value->disconnect("changed", callable_mp(this, &Control::_notify_theme_override_changed)); + } + + // Then override maps can be simply cleared. + data.icon_override.clear(); + data.style_override.clear(); + data.font_override.clear(); + data.font_size_override.clear(); + data.color_override.clear(); + data.constant_override.clear(); +} diff --git a/scene/gui/control.h b/scene/gui/control.h index 9f17eccc3b..3fb1494d66 100644 --- a/scene/gui/control.h +++ b/scene/gui/control.h @@ -41,6 +41,7 @@ class Viewport; class Label; class Panel; +class ThemeOwner; class Control : public CanvasItem { GDCLASS(Control, CanvasItem); @@ -170,6 +171,9 @@ private: // Positioning and sizing. + LayoutMode stored_layout_mode = LayoutMode::LAYOUT_MODE_POSITION; + bool stored_use_custom_anchors = false; + real_t offset[4] = { 0.0, 0.0, 0.0, 0.0 }; real_t anchor[4] = { ANCHOR_BEGIN, ANCHOR_BEGIN, ANCHOR_BEGIN, ANCHOR_BEGIN }; FocusMode focus_mode = FOCUS_NONE; @@ -216,9 +220,8 @@ private: // Theming. + ThemeOwner *theme_owner = nullptr; Ref<Theme> theme; - Control *theme_owner = nullptr; - Window *theme_owner_window = nullptr; StringName theme_type_variation; bool bulk_theme_override = false; @@ -229,6 +232,13 @@ private: Theme::ThemeColorMap color_override; Theme::ThemeConstantMap constant_override; + mutable HashMap<StringName, Theme::ThemeIconMap> theme_icon_cache; + mutable HashMap<StringName, Theme::ThemeStyleMap> theme_style_cache; + mutable HashMap<StringName, Theme::ThemeFontMap> theme_font_cache; + mutable HashMap<StringName, Theme::ThemeFontSizeMap> theme_font_size_cache; + mutable HashMap<StringName, Theme::ThemeColorMap> theme_color_cache; + mutable HashMap<StringName, Theme::ThemeConstantMap> theme_constant_cache; + // Internationalization. LayoutDirection layout_dir = LAYOUT_DIRECTION_INHERITED; @@ -251,7 +261,6 @@ private: // Global relations. friend class Viewport; - friend class Window; // Positioning and sizing. @@ -268,6 +277,7 @@ private: void _set_layout_mode(LayoutMode p_mode); LayoutMode _get_layout_mode() const; + LayoutMode _get_default_layout_mode() const; void _set_anchors_layout_preset(int p_preset); int _get_anchors_layout_preset() const; @@ -289,19 +299,12 @@ private: // Theming. void _theme_changed(); - void _theme_property_override_changed(); - void _notify_theme_changed(); - - static void _propagate_theme_changed(Node *p_at, Control *p_owner, Window *p_owner_window, bool p_assign = true); - - template <class T> - static T get_theme_item_in_types(Control *p_theme_owner, Window *p_theme_owner_window, Theme::DataType p_data_type, const StringName &p_name, List<StringName> p_theme_types); - static bool has_theme_item_in_types(Control *p_theme_owner, Window *p_theme_owner_window, Theme::DataType p_data_type, const StringName &p_name, List<StringName> p_theme_types); - _FORCE_INLINE_ void _get_theme_type_dependencies(const StringName &p_theme_type, List<StringName> *p_list) const; + void _notify_theme_override_changed(); + void _invalidate_theme_cache(); // Extra properties. - String _get_tooltip() const; + String get_tooltip_text() const; protected: // Dynamic properties. @@ -309,24 +312,28 @@ protected: bool _set(const StringName &p_name, const Variant &p_value); bool _get(const StringName &p_name, Variant &r_ret) const; void _get_property_list(List<PropertyInfo> *p_list) const; - virtual void _validate_property(PropertyInfo &property) const override; + void _validate_property(PropertyInfo &p_property) const; + + bool _property_can_revert(const StringName &p_name) const; + bool _property_get_revert(const StringName &p_name, Variant &r_property) const; + + // Theming. + + virtual void _update_theme_item_cache(); // Internationalization. - virtual Array structured_text_parser(TextServer::StructuredTextParser p_parser_type, const Array &p_args, const String &p_text) const; + virtual TypedArray<Vector2i> structured_text_parser(TextServer::StructuredTextParser p_parser_type, const Array &p_args, const String &p_text) const; // Base object overrides. - virtual void add_child_notify(Node *p_child) override; - virtual void remove_child_notify(Node *p_child) override; - void _notification(int p_notification); static void _bind_methods(); // Exposed virtual methods. GDVIRTUAL1RC(bool, _has_point, Vector2) - GDVIRTUAL2RC(Array, _structured_text_parser, Array, String) + GDVIRTUAL2RC(TypedArray<Vector2i>, _structured_text_parser, Array, String) GDVIRTUAL0RC(Vector2, _get_minimum_size) GDVIRTUAL1RC(Variant, _get_drag_data, Vector2) @@ -524,6 +531,10 @@ public: // Theming. + void set_theme_owner_node(Node *p_node); + Node *get_theme_owner_node() const; + bool has_theme_owner_node() const; + void set_theme(const Ref<Theme> &p_theme); Ref<Theme> get_theme() const; @@ -568,10 +579,6 @@ public: bool has_theme_color(const StringName &p_name, const StringName &p_theme_type = StringName()) const; bool has_theme_constant(const StringName &p_name, const StringName &p_theme_type = StringName()) const; - static float fetch_theme_default_base_scale(Control *p_theme_owner, Window *p_theme_owner_window); - static Ref<Font> fetch_theme_default_font(Control *p_theme_owner, Window *p_theme_owner_window); - static int fetch_theme_default_font_size(Control *p_theme_owner, Window *p_theme_owner_window); - float get_theme_default_base_scale() const; Ref<Font> get_theme_default_font() const; int get_theme_default_font_size() const; @@ -590,11 +597,12 @@ public: // Extra properties. - void set_tooltip(const String &p_tooltip); + void set_tooltip_text(const String &text); virtual String get_tooltip(const Point2 &p_pos) const; virtual Control *make_custom_tooltip(const String &p_text) const; - Control() {} + Control(); + ~Control(); }; VARIANT_ENUM_CAST(Control::FocusMode); diff --git a/scene/gui/dialogs.cpp b/scene/gui/dialogs.cpp index f075510aa4..f5edaf02d8 100644 --- a/scene/gui/dialogs.cpp +++ b/scene/gui/dialogs.cpp @@ -50,12 +50,27 @@ void AcceptDialog::_parent_focused() { } } +void AcceptDialog::_update_theme_item_cache() { + Window::_update_theme_item_cache(); + + theme_cache.panel_style = get_theme_stylebox(SNAME("panel")); + theme_cache.buttons_separation = get_theme_constant(SNAME("buttons_separation")); +} + void AcceptDialog::_notification(int p_what) { switch (p_what) { - case NOTIFICATION_VISIBILITY_CHANGED: { + case NOTIFICATION_POST_ENTER_TREE: { if (is_visible()) { get_ok_button()->grab_focus(); + } + } break; + case NOTIFICATION_VISIBILITY_CHANGED: { + if (is_visible()) { + if (get_ok_button()->is_inside_tree()) { + get_ok_button()->grab_focus(); + } _update_child_rects(); + parent_visible = get_parent_visible_window(); if (parent_visible) { parent_visible->connect("focus_entered", callable_mp(this, &AcceptDialog::_parent_focused)); @@ -69,7 +84,12 @@ void AcceptDialog::_notification(int p_what) { } break; case NOTIFICATION_THEME_CHANGED: { - bg->add_theme_style_override("panel", bg->get_theme_stylebox(SNAME("panel"), SNAME("AcceptDialog"))); + bg_panel->add_theme_style_override("panel", theme_cache.panel_style); + + child_controls_changed(); + if (is_visible()) { + _update_child_rects(); + } } break; case NOTIFICATION_EXIT_TREE: { @@ -126,11 +146,16 @@ void AcceptDialog::_cancel_pressed() { } String AcceptDialog::get_text() const { - return label->get_text(); + return message_label->get_text(); } void AcceptDialog::set_text(String p_text) { - label->set_text(p_text); + if (message_label->get_text() == p_text) { + return; + } + + message_label->set_text(p_text); + child_controls_changed(); if (is_visible()) { _update_child_rects(); @@ -154,19 +179,24 @@ bool AcceptDialog::get_close_on_escape() const { } void AcceptDialog::set_autowrap(bool p_autowrap) { - label->set_autowrap_mode(p_autowrap ? TextServer::AUTOWRAP_WORD : TextServer::AUTOWRAP_OFF); + message_label->set_autowrap_mode(p_autowrap ? TextServer::AUTOWRAP_WORD : TextServer::AUTOWRAP_OFF); } bool AcceptDialog::has_autowrap() { - return label->get_autowrap_mode() != TextServer::AUTOWRAP_OFF; + return message_label->get_autowrap_mode() != TextServer::AUTOWRAP_OFF; } void AcceptDialog::set_ok_button_text(String p_ok_button_text) { - ok->set_text(p_ok_button_text); + ok_button->set_text(p_ok_button_text); + + child_controls_changed(); + if (is_visible()) { + _update_child_rects(); + } } String AcceptDialog::get_ok_button_text() const { - return ok->get_text(); + return ok_button->get_text(); } void AcceptDialog::register_text_enter(Control *p_line_edit) { @@ -178,69 +208,79 @@ void AcceptDialog::register_text_enter(Control *p_line_edit) { } void AcceptDialog::_update_child_rects() { - Size2 label_size = label->get_minimum_size(); - if (label->get_text().is_empty()) { - label_size.height = 0; - } - int margin = hbc->get_theme_constant(SNAME("margin"), SNAME("Dialogs")); Size2 size = get_size(); - Size2 hminsize = hbc->get_combined_minimum_size(); + float h_margins = theme_cache.panel_style->get_margin(SIDE_LEFT) + theme_cache.panel_style->get_margin(SIDE_RIGHT); + float v_margins = theme_cache.panel_style->get_margin(SIDE_TOP) + theme_cache.panel_style->get_margin(SIDE_BOTTOM); + + // Fill the entire size of the window with the background. + bg_panel->set_position(Point2()); + bg_panel->set_size(size); + + // Place the buttons from the bottom edge to their minimum required size. + Size2 buttons_minsize = buttons_hbox->get_combined_minimum_size(); + Size2 buttons_size = Size2(size.x - h_margins, buttons_minsize.y); + Point2 buttons_position = Point2(theme_cache.panel_style->get_margin(SIDE_LEFT), size.y - theme_cache.panel_style->get_margin(SIDE_BOTTOM) - buttons_size.y); + buttons_hbox->set_position(buttons_position); + buttons_hbox->set_size(buttons_size); - Vector2 cpos(margin, margin + label_size.height); - Vector2 csize(size.x - margin * 2, size.y - margin * 3 - hminsize.y - label_size.height); + // Place the content from the top to fill the rest of the space (minus the separation). + Point2 content_position = Point2(theme_cache.panel_style->get_margin(SIDE_LEFT), theme_cache.panel_style->get_margin(SIDE_TOP)); + Size2 content_size = Size2(size.x - h_margins, size.y - v_margins - buttons_size.y - theme_cache.buttons_separation); for (int i = 0; i < get_child_count(); i++) { Control *c = Object::cast_to<Control>(get_child(i)); if (!c) { continue; } - - if (c == hbc || c == label || c == bg || c->is_set_as_top_level()) { + if (c == buttons_hbox || c == bg_panel || c->is_set_as_top_level()) { continue; } - c->set_position(cpos); - c->set_size(csize); + c->set_position(content_position); + c->set_size(content_size); } - - cpos.y += csize.y + margin; - csize.y = hminsize.y; - - hbc->set_position(cpos); - hbc->set_size(csize); - - bg->set_position(Point2()); - bg->set_size(size); } Size2 AcceptDialog::_get_contents_minimum_size() const { - int margin = hbc->get_theme_constant(SNAME("margin"), SNAME("Dialogs")); - Size2 minsize = label->get_combined_minimum_size(); - + // First, we then iterate over the label and any other custom controls + // to try and find the size that encompasses all content. + Size2 content_minsize; for (int i = 0; i < get_child_count(); i++) { Control *c = Object::cast_to<Control>(get_child(i)); if (!c) { continue; } - if (c == hbc || c == label || c->is_set_as_top_level()) { + // Buttons will be included afterwards. + // The panel only displays the stylebox and doesn't contribute to the size. + if (c == buttons_hbox || c == bg_panel || c->is_set_as_top_level()) { continue; } - Size2 cminsize = c->get_combined_minimum_size(); - minsize.x = MAX(cminsize.x, minsize.x); - minsize.y = MAX(cminsize.y, minsize.y); + Size2 child_minsize = c->get_combined_minimum_size(); + content_minsize.x = MAX(child_minsize.x, content_minsize.x); + content_minsize.y = MAX(child_minsize.y, content_minsize.y); + } + + // Then we take the background panel as it provides the offsets, + // which are always added to the minimum size. + if (theme_cache.panel_style.is_valid()) { + content_minsize += theme_cache.panel_style->get_minimum_size(); } - Size2 hminsize = hbc->get_combined_minimum_size(); - minsize.x = MAX(hminsize.x, minsize.x); - minsize.y += hminsize.y; - minsize.x += margin * 2; - minsize.y += margin * 3; //one as separation between hbc and child + // Then we add buttons. Horizontally we're interested in whichever + // value is the biggest. Vertically buttons add to the overall size. + Size2 buttons_minsize = buttons_hbox->get_combined_minimum_size(); + content_minsize.x = MAX(buttons_minsize.x, content_minsize.x); + content_minsize.y += buttons_minsize.y; + // Plus there is a separation size added on top. + content_minsize.y += theme_cache.buttons_separation; - Size2 wmsize = get_min_size(); - minsize.x = MAX(wmsize.x, minsize.x); - return minsize; + // Last, we make sure that we aren't below the minimum window size. + Size2 window_minsize = get_min_size(); + content_minsize.x = MAX(window_minsize.x, content_minsize.x); + content_minsize.y = MAX(window_minsize.y, content_minsize.y); + return content_minsize; } void AcceptDialog::_custom_action(const String &p_action) { @@ -251,13 +291,19 @@ void AcceptDialog::_custom_action(const String &p_action) { Button *AcceptDialog::add_button(const String &p_text, bool p_right, const String &p_action) { Button *button = memnew(Button); button->set_text(p_text); + if (p_right) { - hbc->add_child(button); - hbc->add_spacer(); + buttons_hbox->add_child(button); + buttons_hbox->add_spacer(); } else { - hbc->add_child(button); - hbc->move_child(button, 0); - hbc->add_spacer(true); + buttons_hbox->add_child(button); + buttons_hbox->move_child(button, 0); + buttons_hbox->add_spacer(true); + } + + child_controls_changed(); + if (is_visible()) { + _update_child_rects(); } if (!p_action.is_empty()) { @@ -272,24 +318,19 @@ Button *AcceptDialog::add_cancel_button(const String &p_cancel) { if (p_cancel.is_empty()) { c = "Cancel"; } + Button *b = swap_cancel_ok ? add_button(c, true) : add_button(c); + b->connect("pressed", callable_mp(this, &AcceptDialog::_cancel_pressed)); + return b; } void AcceptDialog::remove_button(Control *p_button) { Button *button = Object::cast_to<Button>(p_button); ERR_FAIL_NULL(button); - ERR_FAIL_COND_MSG(button->get_parent() != hbc, vformat("Cannot remove button %s as it does not belong to this dialog.", button->get_name())); - ERR_FAIL_COND_MSG(button == ok, "Cannot remove dialog's OK button."); - - Node *right_spacer = hbc->get_child(button->get_index() + 1); - // Should always be valid but let's avoid crashing - if (right_spacer) { - hbc->remove_child(right_spacer); - memdelete(right_spacer); - } - hbc->remove_child(button); + ERR_FAIL_COND_MSG(button->get_parent() != buttons_hbox, vformat("Cannot remove button %s as it does not belong to this dialog.", button->get_name())); + ERR_FAIL_COND_MSG(button == ok_button, "Cannot remove dialog's OK button."); if (button->is_connected("pressed", callable_mp(this, &AcceptDialog::_custom_action))) { button->disconnect("pressed", callable_mp(this, &AcceptDialog::_custom_action)); @@ -297,6 +338,19 @@ void AcceptDialog::remove_button(Control *p_button) { if (button->is_connected("pressed", callable_mp(this, &AcceptDialog::_cancel_pressed))) { button->disconnect("pressed", callable_mp(this, &AcceptDialog::_cancel_pressed)); } + + Node *right_spacer = buttons_hbox->get_child(button->get_index() + 1); + // Should always be valid but let's avoid crashing. + if (right_spacer) { + buttons_hbox->remove_child(right_spacer); + memdelete(right_spacer); + } + buttons_hbox->remove_child(button); + + child_controls_changed(); + if (is_visible()) { + _update_child_rects(); + } } void AcceptDialog::_bind_methods() { @@ -342,30 +396,25 @@ AcceptDialog::AcceptDialog() { set_exclusive(true); set_clamp_to_embedder(true); - bg = memnew(Panel); - add_child(bg, false, INTERNAL_MODE_FRONT); - - hbc = memnew(HBoxContainer); + bg_panel = memnew(Panel); + add_child(bg_panel, false, INTERNAL_MODE_FRONT); - int margin = hbc->get_theme_constant(SNAME("margin"), SNAME("Dialogs")); - int button_margin = hbc->get_theme_constant(SNAME("button_margin"), SNAME("Dialogs")); + buttons_hbox = memnew(HBoxContainer); - label = memnew(Label); - label->set_anchor(SIDE_RIGHT, Control::ANCHOR_END); - label->set_anchor(SIDE_BOTTOM, Control::ANCHOR_END); - label->set_begin(Point2(margin, margin)); - label->set_end(Point2(-margin, -button_margin - 10)); - add_child(label, false, INTERNAL_MODE_FRONT); + message_label = memnew(Label); + message_label->set_anchor(SIDE_RIGHT, Control::ANCHOR_END); + message_label->set_anchor(SIDE_BOTTOM, Control::ANCHOR_END); + add_child(message_label, false, INTERNAL_MODE_FRONT); - add_child(hbc, false, INTERNAL_MODE_FRONT); + add_child(buttons_hbox, false, INTERNAL_MODE_FRONT); - hbc->add_spacer(); - ok = memnew(Button); - ok->set_text("OK"); - hbc->add_child(ok); - hbc->add_spacer(); + buttons_hbox->add_spacer(); + ok_button = memnew(Button); + ok_button->set_text("OK"); + buttons_hbox->add_child(ok_button); + buttons_hbox->add_spacer(); - ok->connect("pressed", callable_mp(this, &AcceptDialog::_ok_pressed)); + ok_button->connect("pressed", callable_mp(this, &AcceptDialog::_ok_pressed)); set_title(TTRC("Alert!")); diff --git a/scene/gui/dialogs.h b/scene/gui/dialogs.h index 9ebf5ddfb2..81e82d851e 100644 --- a/scene/gui/dialogs.h +++ b/scene/gui/dialogs.h @@ -45,13 +45,20 @@ class AcceptDialog : public Window { GDCLASS(AcceptDialog, Window); Window *parent_visible = nullptr; - Panel *bg = nullptr; - HBoxContainer *hbc = nullptr; - Label *label = nullptr; - Button *ok = nullptr; + + Panel *bg_panel = nullptr; + Label *message_label = nullptr; + HBoxContainer *buttons_hbox = nullptr; + Button *ok_button = nullptr; + bool hide_on_ok = true; bool close_on_escape = true; + struct ThemeCache { + Ref<StyleBox> panel_style; + int buttons_separation = 0; + } theme_cache; + void _custom_action(const String &p_action); void _update_child_rects(); @@ -62,6 +69,7 @@ class AcceptDialog : public Window { protected: virtual Size2 _get_contents_minimum_size() const override; + virtual void _update_theme_item_cache() override; void _notification(int p_what); static void _bind_methods(); @@ -75,12 +83,12 @@ protected: void _cancel_pressed(); public: - Label *get_label() { return label; } + Label *get_label() { return message_label; } static void set_swap_cancel_ok(bool p_swap); void register_text_enter(Control *p_line_edit); - Button *get_ok_button() { return ok; } + Button *get_ok_button() { return ok_button; } Button *add_button(const String &p_text, bool p_right = false, const String &p_action = ""); Button *add_cancel_button(const String &p_cancel = ""); void remove_button(Control *p_button); diff --git a/scene/gui/file_dialog.cpp b/scene/gui/file_dialog.cpp index e26976a402..57f27e299f 100644 --- a/scene/gui/file_dialog.cpp +++ b/scene/gui/file_dialog.cpp @@ -59,36 +59,26 @@ VBoxContainer *FileDialog::get_vbox() { return vbox; } -void FileDialog::_theme_changed() { - Color font_color = vbox->get_theme_color(SNAME("font_color"), SNAME("Button")); - Color font_hover_color = vbox->get_theme_color(SNAME("font_hover_color"), SNAME("Button")); - Color font_focus_color = vbox->get_theme_color(SNAME("font_focus_color"), SNAME("Button")); - Color font_pressed_color = vbox->get_theme_color(SNAME("font_pressed_color"), SNAME("Button")); - - dir_up->add_theme_color_override("icon_normal_color", font_color); - dir_up->add_theme_color_override("icon_hover_color", font_hover_color); - dir_up->add_theme_color_override("icon_focus_color", font_focus_color); - dir_up->add_theme_color_override("icon_pressed_color", font_pressed_color); - - dir_prev->add_theme_color_override("icon_color_normal", font_color); - dir_prev->add_theme_color_override("icon_color_hover", font_hover_color); - dir_prev->add_theme_color_override("icon_focus_color", font_focus_color); - dir_prev->add_theme_color_override("icon_color_pressed", font_pressed_color); - - dir_next->add_theme_color_override("icon_color_normal", font_color); - dir_next->add_theme_color_override("icon_color_hover", font_hover_color); - dir_next->add_theme_color_override("icon_focus_color", font_focus_color); - dir_next->add_theme_color_override("icon_color_pressed", font_pressed_color); - - refresh->add_theme_color_override("icon_normal_color", font_color); - refresh->add_theme_color_override("icon_hover_color", font_hover_color); - refresh->add_theme_color_override("icon_focus_color", font_focus_color); - refresh->add_theme_color_override("icon_pressed_color", font_pressed_color); - - show_hidden->add_theme_color_override("icon_normal_color", font_color); - show_hidden->add_theme_color_override("icon_hover_color", font_hover_color); - show_hidden->add_theme_color_override("icon_focus_color", font_focus_color); - show_hidden->add_theme_color_override("icon_pressed_color", font_pressed_color); +void FileDialog::_update_theme_item_cache() { + ConfirmationDialog::_update_theme_item_cache(); + + theme_cache.parent_folder = get_theme_icon(SNAME("parent_folder")); + theme_cache.forward_folder = get_theme_icon(SNAME("forward_folder")); + theme_cache.back_folder = get_theme_icon(SNAME("back_folder")); + theme_cache.reload = get_theme_icon(SNAME("reload")); + theme_cache.toggle_hidden = get_theme_icon(SNAME("toggle_hidden")); + theme_cache.folder = get_theme_icon(SNAME("folder")); + theme_cache.file = get_theme_icon(SNAME("file")); + + theme_cache.folder_icon_color = get_theme_color(SNAME("folder_icon_color")); + theme_cache.file_icon_color = get_theme_color(SNAME("file_icon_color")); + theme_cache.file_disabled_color = get_theme_color(SNAME("file_disabled_color")); + + // TODO: Define own colors? + theme_cache.icon_normal_color = get_theme_color(SNAME("font_color"), SNAME("Button")); + theme_cache.icon_hover_color = get_theme_color(SNAME("font_hover_color"), SNAME("Button")); + theme_cache.icon_focus_color = get_theme_color(SNAME("font_focus_color"), SNAME("Button")); + theme_cache.icon_pressed_color = get_theme_color(SNAME("font_pressed_color"), SNAME("Button")); } void FileDialog::_notification(int p_what) { @@ -97,20 +87,46 @@ void FileDialog::_notification(int p_what) { if (!is_visible()) { set_process_shortcut_input(false); } + + invalidate(); // Put it here to preview in the editor. } break; - case NOTIFICATION_ENTER_TREE: { - dir_up->set_icon(vbox->get_theme_icon(SNAME("parent_folder"), SNAME("FileDialog"))); + case NOTIFICATION_THEME_CHANGED: { + dir_up->set_icon(theme_cache.parent_folder); if (vbox->is_layout_rtl()) { - dir_prev->set_icon(vbox->get_theme_icon(SNAME("forward_folder"), SNAME("FileDialog"))); - dir_next->set_icon(vbox->get_theme_icon(SNAME("back_folder"), SNAME("FileDialog"))); + dir_prev->set_icon(theme_cache.forward_folder); + dir_next->set_icon(theme_cache.back_folder); } else { - dir_prev->set_icon(vbox->get_theme_icon(SNAME("back_folder"), SNAME("FileDialog"))); - dir_next->set_icon(vbox->get_theme_icon(SNAME("forward_folder"), SNAME("FileDialog"))); + dir_prev->set_icon(theme_cache.back_folder); + dir_next->set_icon(theme_cache.forward_folder); } - refresh->set_icon(vbox->get_theme_icon(SNAME("reload"), SNAME("FileDialog"))); - show_hidden->set_icon(vbox->get_theme_icon(SNAME("toggle_hidden"), SNAME("FileDialog"))); - _theme_changed(); + refresh->set_icon(theme_cache.reload); + show_hidden->set_icon(theme_cache.toggle_hidden); + + dir_up->add_theme_color_override("icon_normal_color", theme_cache.icon_normal_color); + dir_up->add_theme_color_override("icon_hover_color", theme_cache.icon_hover_color); + dir_up->add_theme_color_override("icon_focus_color", theme_cache.icon_focus_color); + dir_up->add_theme_color_override("icon_pressed_color", theme_cache.icon_pressed_color); + + dir_prev->add_theme_color_override("icon_color_normal", theme_cache.icon_normal_color); + dir_prev->add_theme_color_override("icon_color_hover", theme_cache.icon_hover_color); + dir_prev->add_theme_color_override("icon_focus_color", theme_cache.icon_focus_color); + dir_prev->add_theme_color_override("icon_color_pressed", theme_cache.icon_pressed_color); + + dir_next->add_theme_color_override("icon_color_normal", theme_cache.icon_normal_color); + dir_next->add_theme_color_override("icon_color_hover", theme_cache.icon_hover_color); + dir_next->add_theme_color_override("icon_focus_color", theme_cache.icon_focus_color); + dir_next->add_theme_color_override("icon_color_pressed", theme_cache.icon_pressed_color); + + refresh->add_theme_color_override("icon_normal_color", theme_cache.icon_normal_color); + refresh->add_theme_color_override("icon_hover_color", theme_cache.icon_hover_color); + refresh->add_theme_color_override("icon_focus_color", theme_cache.icon_focus_color); + refresh->add_theme_color_override("icon_pressed_color", theme_cache.icon_pressed_color); + + show_hidden->add_theme_color_override("icon_normal_color", theme_cache.icon_normal_color); + show_hidden->add_theme_color_override("icon_hover_color", theme_cache.icon_hover_color); + show_hidden->add_theme_color_override("icon_focus_color", theme_cache.icon_focus_color); + show_hidden->add_theme_color_override("icon_pressed_color", theme_cache.icon_pressed_color); } break; case NOTIFICATION_TRANSLATION_CHANGED: { @@ -129,7 +145,7 @@ void FileDialog::shortcut_input(const Ref<InputEvent> &p_event) { switch (k->get_keycode()) { case Key::H: { - if (k->is_command_pressed()) { + if (k->is_command_or_control_pressed()) { set_show_hidden_files(!show_hidden_files); } else { handled = false; @@ -163,7 +179,7 @@ Vector<String> FileDialog::get_selected_files() const { TreeItem *item = tree->get_root(); while ((item = tree->get_next_selected(item))) { - list.push_back(dir_access->get_current_dir().plus_file(item->get_text(0))); + list.push_back(dir_access->get_current_dir().path_join(item->get_text(0))); }; return list; @@ -192,7 +208,7 @@ void FileDialog::update_dir() { } void FileDialog::_dir_submitted(String p_dir) { - _change_dir(root_prefix.plus_file(p_dir)); + _change_dir(root_prefix.path_join(p_dir)); file->set_text(""); _push_history(); } @@ -202,17 +218,13 @@ void FileDialog::_file_submitted(const String &p_file) { } void FileDialog::_save_confirm_pressed() { - String f = dir_access->get_current_dir().plus_file(file->get_text()); + String f = dir_access->get_current_dir().path_join(file->get_text()); emit_signal(SNAME("file_selected"), f); hide(); } void FileDialog::_post_popup() { ConfirmationDialog::_post_popup(); - if (invalidated) { - update_file_list(); - invalidated = false; - } if (mode == FILE_MODE_SAVE_FILE) { file->grab_focus(); } else { @@ -252,7 +264,7 @@ void FileDialog::_action_pressed() { Vector<String> files; while (ti) { - files.push_back(fbase.plus_file(ti->get_text(0))); + files.push_back(fbase.path_join(ti->get_text(0))); ti = tree->get_next_selected(ti); } @@ -265,7 +277,7 @@ void FileDialog::_action_pressed() { } String file_text = file->get_text(); - String f = file_text.is_absolute_path() ? file_text : dir_access->get_current_dir().plus_file(file_text); + String f = file_text.is_absolute_path() ? file_text : dir_access->get_current_dir().path_join(file_text); if ((mode == FILE_MODE_OPEN_ANY || mode == FILE_MODE_OPEN_FILE) && dir_access->file_exists(f)) { emit_signal(SNAME("file_selected"), f); @@ -278,7 +290,7 @@ void FileDialog::_action_pressed() { if (item) { Dictionary d = item->get_metadata(0); if (d["dir"] && d["name"] != "..") { - path = path.plus_file(d["name"]); + path = path.path_join(d["name"]); } } @@ -506,10 +518,6 @@ void FileDialog::update_file_list() { } TreeItem *root = tree->create_item(); - Ref<Texture2D> folder = vbox->get_theme_icon(SNAME("folder"), SNAME("FileDialog")); - Ref<Texture2D> file_icon = vbox->get_theme_icon(SNAME("file"), SNAME("FileDialog")); - const Color folder_color = vbox->get_theme_color(SNAME("folder_icon_modulate"), SNAME("FileDialog")); - const Color file_color = vbox->get_theme_color(SNAME("file_icon_modulate"), SNAME("FileDialog")); List<String> files; List<String> dirs; @@ -541,8 +549,8 @@ void FileDialog::update_file_list() { String &dir_name = dirs.front()->get(); TreeItem *ti = tree->create_item(root); ti->set_text(0, dir_name); - ti->set_icon(0, folder); - ti->set_icon_modulate(0, folder_color); + ti->set_icon(0, theme_cache.folder); + ti->set_icon_modulate(0, theme_cache.folder_icon_color); Dictionary d; d["name"] = dir_name; @@ -598,15 +606,15 @@ void FileDialog::update_file_list() { ti->set_text(0, files.front()->get()); if (get_icon_func) { - Ref<Texture2D> icon = get_icon_func(base_dir.plus_file(files.front()->get())); + Ref<Texture2D> icon = get_icon_func(base_dir.path_join(files.front()->get())); ti->set_icon(0, icon); } else { - ti->set_icon(0, file_icon); + ti->set_icon(0, theme_cache.file); } - ti->set_icon_modulate(0, file_color); + ti->set_icon_modulate(0, theme_cache.file_icon_color); if (mode == FILE_MODE_OPEN_DIR) { - ti->set_custom_color(0, vbox->get_theme_color(SNAME("files_disabled"), SNAME("FileDialog"))); + ti->set_custom_color(0, theme_cache.file_disabled_color); ti->set_selectable(0, false); } Dictionary d; @@ -685,6 +693,9 @@ void FileDialog::add_filter(const String &p_filter, const String &p_description) } void FileDialog::set_filters(const Vector<String> &p_filters) { + if (filters == p_filters) { + return; + } filters = p_filters; update_filters(); invalidate(); @@ -703,15 +714,19 @@ String FileDialog::get_current_file() const { } String FileDialog::get_current_path() const { - return dir->get_text().plus_file(file->get_text()); + return dir->get_text().path_join(file->get_text()); } void FileDialog::set_current_dir(const String &p_dir) { _change_dir(p_dir); + _push_history(); } void FileDialog::set_current_file(const String &p_file) { + if (file->get_text() == p_file) { + return; + } file->set_text(p_file); update_dir(); invalidate(); @@ -764,7 +779,9 @@ bool FileDialog::is_mode_overriding_title() const { void FileDialog::set_file_mode(FileMode p_mode) { ERR_FAIL_INDEX((int)p_mode, 5); - + if (mode == p_mode) { + return; + } mode = p_mode; switch (mode) { case FILE_MODE_OPEN_FILE: @@ -977,6 +994,9 @@ void FileDialog::_bind_methods() { } void FileDialog::set_show_hidden_files(bool p_show) { + if (show_hidden_files == p_show) { + return; + } show_hidden_files = p_show; invalidate(); } @@ -994,7 +1014,6 @@ FileDialog::FileDialog() { vbox = memnew(VBoxContainer); add_child(vbox, false, INTERNAL_MODE_FRONT); - vbox->connect("theme_changed", callable_mp(this, &FileDialog::_theme_changed)); mode = FILE_MODE_SAVE_FILE; set_title(TTRC("Save a File")); @@ -1003,13 +1022,13 @@ FileDialog::FileDialog() { dir_prev = memnew(Button); dir_prev->set_flat(true); - dir_prev->set_tooltip(RTR("Go to previous folder.")); + dir_prev->set_tooltip_text(RTR("Go to previous folder.")); dir_next = memnew(Button); dir_next->set_flat(true); - dir_next->set_tooltip(RTR("Go to next folder.")); + dir_next->set_tooltip_text(RTR("Go to next folder.")); dir_up = memnew(Button); dir_up->set_flat(true); - dir_up->set_tooltip(RTR("Go to parent folder.")); + dir_up->set_tooltip_text(RTR("Go to parent folder.")); hbc->add_child(dir_prev); hbc->add_child(dir_next); hbc->add_child(dir_up); @@ -1033,7 +1052,7 @@ FileDialog::FileDialog() { refresh = memnew(Button); refresh->set_flat(true); - refresh->set_tooltip(RTR("Refresh files.")); + refresh->set_tooltip_text(RTR("Refresh files.")); refresh->connect("pressed", callable_mp(this, &FileDialog::update_file_list)); hbc->add_child(refresh); @@ -1041,7 +1060,7 @@ FileDialog::FileDialog() { show_hidden->set_flat(true); show_hidden->set_toggle_mode(true); show_hidden->set_pressed(is_showing_hidden_files()); - show_hidden->set_tooltip(RTR("Toggle the visibility of hidden files.")); + show_hidden->set_tooltip_text(RTR("Toggle the visibility of hidden files.")); show_hidden->connect("toggled", callable_mp(this, &FileDialog::set_show_hidden_files)); hbc->add_child(show_hidden); diff --git a/scene/gui/file_dialog.h b/scene/gui/file_dialog.h index 4945094086..1add0a9cf5 100644 --- a/scene/gui/file_dialog.h +++ b/scene/gui/file_dialog.h @@ -109,6 +109,25 @@ private: bool invalidated = true; + struct ThemeCache { + Ref<Texture2D> parent_folder; + Ref<Texture2D> forward_folder; + Ref<Texture2D> back_folder; + Ref<Texture2D> reload; + Ref<Texture2D> toggle_hidden; + Ref<Texture2D> folder; + Ref<Texture2D> file; + + Color folder_icon_color; + Color file_icon_color; + Color file_disabled_color; + + Color icon_normal_color; + Color icon_hover_color; + Color icon_focus_color; + Color icon_pressed_color; + } theme_cache; + void update_dir(); void update_file_name(); void update_file_list(); @@ -143,7 +162,7 @@ private: virtual void _post_popup() override; protected: - void _theme_changed(); + virtual void _update_theme_item_cache() override; void _notification(int p_what); static void _bind_methods(); diff --git a/scene/gui/flow_container.cpp b/scene/gui/flow_container.cpp index 30b694da76..b0d15aa7f4 100644 --- a/scene/gui/flow_container.cpp +++ b/scene/gui/flow_container.cpp @@ -44,9 +44,6 @@ void FlowContainer::_resort() { return; } - int separation_horizontal = get_theme_constant(SNAME("h_separation")); - int separation_vertical = get_theme_constant(SNAME("v_separation")); - bool rtl = is_layout_rtl(); HashMap<Control *, Size2i> children_minsize_cache; @@ -74,14 +71,14 @@ void FlowContainer::_resort() { if (vertical) { /* VERTICAL */ if (children_in_current_line > 0) { - ofs.y += separation_vertical; + ofs.y += theme_cache.v_separation; } if (ofs.y + child_msc.y > current_container_size) { - line_length = ofs.y - separation_vertical; + line_length = ofs.y - theme_cache.v_separation; lines_data.push_back(_LineData{ children_in_current_line, line_height, line_length, current_container_size - line_length, line_stretch_ratio_total }); // Move in new column (vertical line). - ofs.x += line_height + separation_horizontal; + ofs.x += line_height + theme_cache.h_separation; ofs.y = 0; line_height = 0; line_stretch_ratio_total = 0; @@ -96,14 +93,14 @@ void FlowContainer::_resort() { } else { /* HORIZONTAL */ if (children_in_current_line > 0) { - ofs.x += separation_horizontal; + ofs.x += theme_cache.h_separation; } if (ofs.x + child_msc.x > current_container_size) { - line_length = ofs.x - separation_horizontal; + line_length = ofs.x - theme_cache.h_separation; lines_data.push_back(_LineData{ children_in_current_line, line_height, line_length, current_container_size - line_length, line_stretch_ratio_total }); // Move in new line. - ofs.y += line_height + separation_vertical; + ofs.y += line_height + theme_cache.v_separation; ofs.x = 0; line_height = 0; line_stretch_ratio_total = 0; @@ -146,11 +143,11 @@ void FlowContainer::_resort() { current_line_idx++; child_idx_in_line = 0; if (vertical) { - ofs.x += line_data.min_line_height + separation_horizontal; + ofs.x += line_data.min_line_height + theme_cache.h_separation; ofs.y = 0; } else { ofs.x = 0; - ofs.y += line_data.min_line_height + separation_vertical; + ofs.y += line_data.min_line_height + theme_cache.v_separation; } line_data = lines_data[current_line_idx]; } @@ -184,9 +181,9 @@ void FlowContainer::_resort() { fit_child_in_rect(child, child_rect); if (vertical) { /* VERTICAL */ - ofs.y += child_size.height + separation_vertical; + ofs.y += child_size.height + theme_cache.v_separation; } else { /* HORIZONTAL */ - ofs.x += child_size.width + separation_horizontal; + ofs.x += child_size.width + theme_cache.h_separation; } child_idx_in_line++; @@ -250,6 +247,13 @@ Vector<int> FlowContainer::get_allowed_size_flags_vertical() const { return flags; } +void FlowContainer::_update_theme_item_cache() { + Container::_update_theme_item_cache(); + + theme_cache.h_separation = get_theme_constant(SNAME("h_separation")); + theme_cache.v_separation = get_theme_constant(SNAME("v_separation")); +} + void FlowContainer::_notification(int p_what) { switch (p_what) { case NOTIFICATION_SORT_CHILDREN: { @@ -268,14 +272,36 @@ void FlowContainer::_notification(int p_what) { } } +void FlowContainer::_validate_property(PropertyInfo &p_property) const { + if (is_fixed && p_property.name == "vertical") { + p_property.usage = PROPERTY_USAGE_NONE; + } +} + int FlowContainer::get_line_count() const { return cached_line_count; } +void FlowContainer::set_vertical(bool p_vertical) { + ERR_FAIL_COND_MSG(is_fixed, "Can't change orientation of " + get_class() + "."); + vertical = p_vertical; + update_minimum_size(); + _resort(); +} + +bool FlowContainer::is_vertical() const { + return vertical; +} + FlowContainer::FlowContainer(bool p_vertical) { vertical = p_vertical; } void FlowContainer::_bind_methods() { ClassDB::bind_method(D_METHOD("get_line_count"), &FlowContainer::get_line_count); + + ClassDB::bind_method(D_METHOD("set_vertical", "vertical"), &FlowContainer::set_vertical); + ClassDB::bind_method(D_METHOD("is_vertical"), &FlowContainer::is_vertical); + + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "vertical"), "set_vertical", "is_vertical"); } diff --git a/scene/gui/flow_container.h b/scene/gui/flow_container.h index a2da43e071..536df27ad6 100644 --- a/scene/gui/flow_container.h +++ b/scene/gui/flow_container.h @@ -42,16 +42,28 @@ private: bool vertical = false; + struct ThemeCache { + int h_separation = 0; + int v_separation = 0; + } theme_cache; + void _resort(); protected: - void _notification(int p_what); + bool is_fixed = false; + + virtual void _update_theme_item_cache() override; + void _notification(int p_what); + void _validate_property(PropertyInfo &p_property) const; static void _bind_methods(); public: int get_line_count() const; + void set_vertical(bool p_vertical); + bool is_vertical() const; + virtual Size2 get_minimum_size() const override; virtual Vector<int> get_allowed_size_flags_horizontal() const override; @@ -65,7 +77,7 @@ class HFlowContainer : public FlowContainer { public: HFlowContainer() : - FlowContainer(false) {} + FlowContainer(false) { is_fixed = true; } }; class VFlowContainer : public FlowContainer { @@ -73,7 +85,7 @@ class VFlowContainer : public FlowContainer { public: VFlowContainer() : - FlowContainer(true) {} + FlowContainer(true) { is_fixed = true; } }; #endif // FLOW_CONTAINER_H diff --git a/scene/gui/gradient_edit.cpp b/scene/gui/gradient_edit.cpp deleted file mode 100644 index cc27a6b7c2..0000000000 --- a/scene/gui/gradient_edit.cpp +++ /dev/null @@ -1,446 +0,0 @@ -/*************************************************************************/ -/* gradient_edit.cpp */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ -/* */ -/* Permission is hereby granted, free of charge, to any person obtaining */ -/* a copy of this software and associated documentation files (the */ -/* "Software"), to deal in the Software without restriction, including */ -/* without limitation the rights to use, copy, modify, merge, publish, */ -/* distribute, sublicense, and/or sell copies of the Software, and to */ -/* permit persons to whom the Software is furnished to do so, subject to */ -/* the following conditions: */ -/* */ -/* The above copyright notice and this permission notice shall be */ -/* included in all copies or substantial portions of the Software. */ -/* */ -/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ -/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ -/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ -/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ -/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ -/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ -/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -/*************************************************************************/ - -#include "gradient_edit.h" - -#include "core/os/keyboard.h" - -GradientEdit::GradientEdit() { - set_focus_mode(FOCUS_ALL); - - popup = memnew(PopupPanel); - picker = memnew(ColorPicker); - popup->add_child(picker); - - gradient_cache.instantiate(); - preview_texture.instantiate(); - - preview_texture->set_width(1024); - add_child(popup, false, INTERNAL_MODE_FRONT); -} - -int GradientEdit::_get_point_from_pos(int x) { - int result = -1; - int total_w = get_size().width - get_size().height - draw_spacing; - float min_distance = 1e20; - for (int i = 0; i < points.size(); i++) { - // Check if we clicked at point. - float distance = ABS(x - points[i].offset * total_w); - float min = (draw_point_width / 2 * 1.7); //make it easier to grab - if (distance <= min && distance < min_distance) { - result = i; - min_distance = distance; - } - } - return result; -} - -void GradientEdit::_show_color_picker() { - if (grabbed == -1) { - return; - } - picker->set_pick_color(points[grabbed].color); - Size2 minsize = popup->get_contents_minimum_size(); - bool show_above = false; - if (get_global_position().y + get_size().y + minsize.y > get_viewport_rect().size.y) { - show_above = true; - } - if (show_above) { - popup->set_position(get_screen_position() - Vector2(0, minsize.y)); - } else { - popup->set_position(get_screen_position() + Vector2(0, get_size().y)); - } - popup->popup(); -} - -GradientEdit::~GradientEdit() { -} - -void GradientEdit::gui_input(const Ref<InputEvent> &p_event) { - ERR_FAIL_COND(p_event.is_null()); - - Ref<InputEventKey> k = p_event; - - if (k.is_valid() && k->is_pressed() && k->get_keycode() == Key::KEY_DELETE && grabbed != -1) { - points.remove_at(grabbed); - grabbed = -1; - grabbing = false; - update(); - emit_signal(SNAME("ramp_changed")); - accept_event(); - } - - Ref<InputEventMouseButton> mb = p_event; - // Show color picker on double click. - if (mb.is_valid() && mb->get_button_index() == MouseButton::LEFT && mb->is_double_click() && mb->is_pressed()) { - grabbed = _get_point_from_pos(mb->get_position().x); - _show_color_picker(); - accept_event(); - } - - // Delete point on right click. - if (mb.is_valid() && mb->get_button_index() == MouseButton::RIGHT && mb->is_pressed()) { - grabbed = _get_point_from_pos(mb->get_position().x); - if (grabbed != -1) { - points.remove_at(grabbed); - grabbed = -1; - grabbing = false; - update(); - emit_signal(SNAME("ramp_changed")); - accept_event(); - } - } - - // Hold alt key to duplicate selected color. - if (mb.is_valid() && mb->get_button_index() == MouseButton::LEFT && mb->is_pressed() && mb->is_alt_pressed()) { - int x = mb->get_position().x; - grabbed = _get_point_from_pos(x); - - if (grabbed != -1) { - int total_w = get_size().width - get_size().height - draw_spacing; - Gradient::Point new_point = points[grabbed]; - new_point.offset = CLAMP(x / float(total_w), 0, 1); - - points.push_back(new_point); - points.sort(); - for (int i = 0; i < points.size(); ++i) { - if (points[i].offset == new_point.offset) { - grabbed = i; - break; - } - } - - emit_signal(SNAME("ramp_changed")); - update(); - } - } - - // Select. - if (mb.is_valid() && mb->get_button_index() == MouseButton::LEFT && mb->is_pressed()) { - update(); - int x = mb->get_position().x; - int total_w = get_size().width - get_size().height - draw_spacing; - - //Check if color selector was clicked. - if (x > total_w + draw_spacing) { - _show_color_picker(); - return; - } - - grabbing = true; - - grabbed = _get_point_from_pos(x); - //grab or select - if (grabbed != -1) { - return; - } - - // Insert point. - Gradient::Point new_point; - new_point.offset = CLAMP(x / float(total_w), 0, 1); - - Gradient::Point prev; - Gradient::Point next; - - int pos = -1; - for (int i = 0; i < points.size(); i++) { - if (points[i].offset < new_point.offset) { - pos = i; - } - } - - if (pos == -1) { - prev.color = Color(0, 0, 0); - prev.offset = 0; - if (points.size()) { - next = points[0]; - } else { - next.color = Color(1, 1, 1); - next.offset = 1.0; - } - } else { - if (pos == points.size() - 1) { - next.color = Color(1, 1, 1); - next.offset = 1.0; - } else { - next = points[pos + 1]; - } - prev = points[pos]; - } - - new_point.color = prev.color.lerp(next.color, (new_point.offset - prev.offset) / (next.offset - prev.offset)); - - points.push_back(new_point); - points.sort(); - for (int i = 0; i < points.size(); i++) { - if (points[i].offset == new_point.offset) { - grabbed = i; - break; - } - } - - emit_signal(SNAME("ramp_changed")); - } - - if (mb.is_valid() && mb->get_button_index() == MouseButton::LEFT && !mb->is_pressed()) { - if (grabbing) { - grabbing = false; - emit_signal(SNAME("ramp_changed")); - } - update(); - } - - Ref<InputEventMouseMotion> mm = p_event; - - if (mm.is_valid() && grabbing) { - int total_w = get_size().width - get_size().height - draw_spacing; - - int x = mm->get_position().x; - - float newofs = CLAMP(x / float(total_w), 0, 1); - - // Snap to "round" coordinates if holding Ctrl. - // Be more precise if holding Shift as well. - if (mm->is_ctrl_pressed()) { - newofs = Math::snapped(newofs, mm->is_shift_pressed() ? 0.025 : 0.1); - } else if (mm->is_shift_pressed()) { - // Snap to nearest point if holding just Shift - const float snap_threshold = 0.03; - float smallest_ofs = snap_threshold; - bool found = false; - int nearest_point = 0; - for (int i = 0; i < points.size(); ++i) { - if (i != grabbed) { - float temp_ofs = ABS(points[i].offset - newofs); - if (temp_ofs < smallest_ofs) { - smallest_ofs = temp_ofs; - nearest_point = i; - if (found) { - break; - } - found = true; - } - } - } - if (found) { - if (points[nearest_point].offset < newofs) { - newofs = points[nearest_point].offset + 0.00001; - } else { - newofs = points[nearest_point].offset - 0.00001; - } - newofs = CLAMP(newofs, 0, 1); - } - } - - bool valid = true; - for (int i = 0; i < points.size(); i++) { - if (points[i].offset == newofs && i != grabbed) { - valid = false; - break; - } - } - - if (!valid || grabbed == -1) { - return; - } - points.write[grabbed].offset = newofs; - - points.sort(); - for (int i = 0; i < points.size(); i++) { - if (points[i].offset == newofs) { - grabbed = i; - break; - } - } - - emit_signal(SNAME("ramp_changed")); - - update(); - } -} - -void GradientEdit::_notification(int p_what) { - switch (p_what) { - case NOTIFICATION_ENTER_TREE: { - if (!picker->is_connected("color_changed", callable_mp(this, &GradientEdit::_color_changed))) { - picker->connect("color_changed", callable_mp(this, &GradientEdit::_color_changed)); - } - [[fallthrough]]; - } - case NOTIFICATION_THEME_CHANGED: { - draw_spacing = BASE_SPACING * get_theme_default_base_scale(); - draw_point_width = BASE_POINT_WIDTH * get_theme_default_base_scale(); - } break; - - case NOTIFICATION_DRAW: { - int w = get_size().x; - int h = get_size().y; - - if (w == 0 || h == 0) { - return; // Safety check. We have division by 'h'. And in any case there is nothing to draw with such size. - } - - int total_w = get_size().width - get_size().height - draw_spacing; - - // Draw checker pattern for ramp. - draw_texture_rect(get_theme_icon(SNAME("GuiMiniCheckerboard"), SNAME("EditorIcons")), Rect2(0, 0, total_w, h), true); - - // Draw color ramp. - gradient_cache->set_points(points); - gradient_cache->set_interpolation_mode(interpolation_mode); - preview_texture->set_gradient(gradient_cache); - draw_texture_rect(preview_texture, Rect2(0, 0, total_w, h)); - - // Draw point markers. - for (int i = 0; i < points.size(); i++) { - Color col = points[i].color.inverted(); - col.a = 0.9; - - draw_line(Vector2(points[i].offset * total_w, 0), Vector2(points[i].offset * total_w, h / 2), col); - Rect2 rect = Rect2(points[i].offset * total_w - draw_point_width / 2, h / 2, draw_point_width, h / 2); - draw_rect(rect, points[i].color, true); - draw_rect(rect, col, false); - if (grabbed == i) { - rect = rect.grow(-1); - if (has_focus()) { - draw_rect(rect, Color(1, 0, 0, 0.9), false); - } else { - draw_rect(rect, Color(0.6, 0, 0, 0.9), false); - } - - rect = rect.grow(-1); - draw_rect(rect, col, false); - } - } - - // Draw "button" for color selector. - draw_texture_rect(get_theme_icon(SNAME("GuiMiniCheckerboard"), SNAME("EditorIcons")), Rect2(total_w + draw_spacing, 0, h, h), true); - if (grabbed != -1) { - // Draw with selection color. - draw_rect(Rect2(total_w + draw_spacing, 0, h, h), points[grabbed].color); - } else { - // If no color selected draw grey color with 'X' on top. - draw_rect(Rect2(total_w + draw_spacing, 0, h, h), Color(0.5, 0.5, 0.5, 1)); - draw_line(Vector2(total_w + draw_spacing, 0), Vector2(total_w + draw_spacing + h, h), Color(1, 1, 1, 0.6)); - draw_line(Vector2(total_w + draw_spacing, h), Vector2(total_w + draw_spacing + h, 0), Color(1, 1, 1, 0.6)); - } - - // Draw borders around color ramp if in focus. - if (has_focus()) { - draw_line(Vector2(-1, -1), Vector2(total_w + 1, -1), Color(1, 1, 1, 0.6)); - draw_line(Vector2(total_w + 1, -1), Vector2(total_w + 1, h + 1), Color(1, 1, 1, 0.6)); - draw_line(Vector2(total_w + 1, h + 1), Vector2(-1, h + 1), Color(1, 1, 1, 0.6)); - draw_line(Vector2(-1, -1), Vector2(-1, h + 1), Color(1, 1, 1, 0.6)); - } - } break; - - case NOTIFICATION_VISIBILITY_CHANGED: { - if (!is_visible()) { - grabbing = false; - } - } break; - } -} - -Size2 GradientEdit::get_minimum_size() const { - return Vector2(0, 16); -} - -void GradientEdit::_color_changed(const Color &p_color) { - if (grabbed == -1) { - return; - } - points.write[grabbed].color = p_color; - update(); - emit_signal(SNAME("ramp_changed")); -} - -void GradientEdit::set_ramp(const Vector<float> &p_offsets, const Vector<Color> &p_colors) { - ERR_FAIL_COND(p_offsets.size() != p_colors.size()); - points.clear(); - for (int i = 0; i < p_offsets.size(); i++) { - Gradient::Point p; - p.offset = p_offsets[i]; - p.color = p_colors[i]; - points.push_back(p); - } - - points.sort(); - update(); -} - -Vector<float> GradientEdit::get_offsets() const { - Vector<float> ret; - for (int i = 0; i < points.size(); i++) { - ret.push_back(points[i].offset); - } - return ret; -} - -Vector<Color> GradientEdit::get_colors() const { - Vector<Color> ret; - for (int i = 0; i < points.size(); i++) { - ret.push_back(points[i].color); - } - return ret; -} - -void GradientEdit::set_points(Vector<Gradient::Point> &p_points) { - if (points.size() != p_points.size()) { - grabbed = -1; - } - points.clear(); - points = p_points; - points.sort(); -} - -Vector<Gradient::Point> &GradientEdit::get_points() { - return points; -} - -void GradientEdit::set_interpolation_mode(Gradient::InterpolationMode p_interp_mode) { - interpolation_mode = p_interp_mode; -} - -Gradient::InterpolationMode GradientEdit::get_interpolation_mode() { - return interpolation_mode; -} - -ColorPicker *GradientEdit::get_picker() { - return picker; -} - -PopupPanel *GradientEdit::get_popup() { - return popup; -} - -void GradientEdit::_bind_methods() { - ADD_SIGNAL(MethodInfo("ramp_changed")); -} diff --git a/scene/gui/gradient_edit.h b/scene/gui/gradient_edit.h deleted file mode 100644 index b7c99f1f1c..0000000000 --- a/scene/gui/gradient_edit.h +++ /dev/null @@ -1,86 +0,0 @@ -/*************************************************************************/ -/* gradient_edit.h */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ -/* */ -/* Permission is hereby granted, free of charge, to any person obtaining */ -/* a copy of this software and associated documentation files (the */ -/* "Software"), to deal in the Software without restriction, including */ -/* without limitation the rights to use, copy, modify, merge, publish, */ -/* distribute, sublicense, and/or sell copies of the Software, and to */ -/* permit persons to whom the Software is furnished to do so, subject to */ -/* the following conditions: */ -/* */ -/* The above copyright notice and this permission notice shall be */ -/* included in all copies or substantial portions of the Software. */ -/* */ -/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ -/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ -/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ -/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ -/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ -/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ -/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -/*************************************************************************/ - -#ifndef GRADIENT_EDIT_H -#define GRADIENT_EDIT_H - -#include "scene/gui/color_picker.h" -#include "scene/gui/popup.h" -#include "scene/resources/gradient.h" - -class GradientEdit : public Control { - GDCLASS(GradientEdit, Control); - - PopupPanel *popup = nullptr; - ColorPicker *picker = nullptr; - - bool grabbing = false; - int grabbed = -1; - Vector<Gradient::Point> points; - Gradient::InterpolationMode interpolation_mode = Gradient::GRADIENT_INTERPOLATE_LINEAR; - - Ref<Gradient> gradient_cache; - Ref<GradientTexture1D> preview_texture; - - // Make sure to use the scaled value below. - const int BASE_SPACING = 3; - const int BASE_POINT_WIDTH = 8; - - int draw_spacing = BASE_SPACING; - int draw_point_width = BASE_POINT_WIDTH; - - void _draw_checker(int x, int y, int w, int h); - void _color_changed(const Color &p_color); - int _get_point_from_pos(int x); - void _show_color_picker(); - -protected: - virtual void gui_input(const Ref<InputEvent> &p_event) override; - void _notification(int p_what); - static void _bind_methods(); - -public: - void set_ramp(const Vector<float> &p_offsets, const Vector<Color> &p_colors); - Vector<float> get_offsets() const; - Vector<Color> get_colors() const; - void set_points(Vector<Gradient::Point> &p_points); - Vector<Gradient::Point> &get_points(); - void set_interpolation_mode(Gradient::InterpolationMode p_interp_mode); - Gradient::InterpolationMode get_interpolation_mode(); - ColorPicker *get_picker(); - PopupPanel *get_popup(); - - virtual Size2 get_minimum_size() const override; - - GradientEdit(); - virtual ~GradientEdit(); -}; - -#endif // GRADIENT_EDIT_H diff --git a/scene/gui/graph_edit.cpp b/scene/gui/graph_edit.cpp index 09efee71a3..c8de8789fe 100644 --- a/scene/gui/graph_edit.cpp +++ b/scene/gui/graph_edit.cpp @@ -176,7 +176,7 @@ void GraphEditMinimap::gui_input(const Ref<InputEvent> &p_ev) { new_minimap_size.y = MIN(get_size().y - mm->get_relative().y, ge->get_size().y - 2.0 * minimap_padding.y); ge->set_minimap_size(new_minimap_size); - update(); + queue_redraw(); } else { Vector2 click_position = _convert_to_graph_position(mm->get_position() - minimap_padding) - graph_padding; _adjust_graph_scroll(click_position); @@ -201,10 +201,10 @@ Error GraphEdit::connect_node(const StringName &p_from, int p_from_port, const S c.to_port = p_to_port; c.activity = 0; connections.push_back(c); - top_layer->update(); - minimap->update(); - update(); - connections_layer->update(); + top_layer->queue_redraw(); + minimap->queue_redraw(); + queue_redraw(); + connections_layer->queue_redraw(); return OK; } @@ -223,10 +223,10 @@ void GraphEdit::disconnect_node(const StringName &p_from, int p_from_port, const for (const List<Connection>::Element *E = connections.front(); E; E = E->next()) { if (E->get().from == p_from && E->get().from_port == p_from_port && E->get().to == p_to && E->get().to_port == p_to_port) { connections.erase(E); - top_layer->update(); - minimap->update(); - update(); - connections_layer->update(); + top_layer->queue_redraw(); + minimap->queue_redraw(); + queue_redraw(); + connections_layer->queue_redraw(); return; } } @@ -253,9 +253,9 @@ void GraphEdit::_scroll_moved(double) { call_deferred(SNAME("_update_scroll_offset")); awaiting_scroll_offset_update = true; } - top_layer->update(); - minimap->update(); - update(); + top_layer->queue_redraw(); + minimap->queue_redraw(); + queue_redraw(); if (!setting_scroll_ofs) { //in godot, signals on change value are avoided as a convention emit_signal(SNAME("scroll_offset_changed"), get_scroll_ofs()); @@ -351,27 +351,40 @@ void GraphEdit::_graph_node_raised(Node *p_gn) { if (gn->is_comment()) { move_child(gn, 0); } else { - gn->raise(); + gn->move_to_front(); } - emit_signal(SNAME("node_selected"), p_gn); +} + +void GraphEdit::_graph_node_selected(Node *p_gn) { + GraphNode *gn = Object::cast_to<GraphNode>(p_gn); + ERR_FAIL_COND(!gn); + + emit_signal(SNAME("node_selected"), gn); +} + +void GraphEdit::_graph_node_deselected(Node *p_gn) { + GraphNode *gn = Object::cast_to<GraphNode>(p_gn); + ERR_FAIL_COND(!gn); + + emit_signal(SNAME("node_deselected"), gn); } void GraphEdit::_graph_node_moved(Node *p_gn) { GraphNode *gn = Object::cast_to<GraphNode>(p_gn); ERR_FAIL_COND(!gn); - top_layer->update(); - minimap->update(); - update(); - connections_layer->update(); + top_layer->queue_redraw(); + minimap->queue_redraw(); + queue_redraw(); + connections_layer->queue_redraw(); } void GraphEdit::_graph_node_slot_updated(int p_index, Node *p_gn) { GraphNode *gn = Object::cast_to<GraphNode>(p_gn); ERR_FAIL_COND(!gn); - top_layer->update(); - minimap->update(); - update(); - connections_layer->update(); + top_layer->queue_redraw(); + minimap->queue_redraw(); + queue_redraw(); + connections_layer->queue_redraw(); } void GraphEdit::add_child_notify(Node *p_child) { @@ -383,10 +396,12 @@ void GraphEdit::add_child_notify(Node *p_child) { if (gn) { gn->set_scale(Vector2(zoom, zoom)); gn->connect("position_offset_changed", callable_mp(this, &GraphEdit::_graph_node_moved).bind(gn)); + gn->connect("selected", callable_mp(this, &GraphEdit::_graph_node_selected).bind(gn)); + gn->connect("deselected", callable_mp(this, &GraphEdit::_graph_node_deselected).bind(gn)); gn->connect("slot_updated", callable_mp(this, &GraphEdit::_graph_node_slot_updated).bind(gn)); gn->connect("raise_request", callable_mp(this, &GraphEdit::_graph_node_raised).bind(gn)); - gn->connect("item_rect_changed", callable_mp((CanvasItem *)connections_layer, &CanvasItem::update)); - gn->connect("item_rect_changed", callable_mp((CanvasItem *)minimap, &GraphEditMinimap::update)); + gn->connect("item_rect_changed", callable_mp((CanvasItem *)connections_layer, &CanvasItem::queue_redraw)); + gn->connect("item_rect_changed", callable_mp((CanvasItem *)minimap, &GraphEditMinimap::queue_redraw)); _graph_node_moved(gn); gn->set_mouse_filter(MOUSE_FILTER_PASS); } @@ -409,15 +424,17 @@ void GraphEdit::remove_child_notify(Node *p_child) { GraphNode *gn = Object::cast_to<GraphNode>(p_child); if (gn) { gn->disconnect("position_offset_changed", callable_mp(this, &GraphEdit::_graph_node_moved)); + gn->disconnect("selected", callable_mp(this, &GraphEdit::_graph_node_selected)); + gn->disconnect("deselected", callable_mp(this, &GraphEdit::_graph_node_deselected)); gn->disconnect("slot_updated", callable_mp(this, &GraphEdit::_graph_node_slot_updated)); gn->disconnect("raise_request", callable_mp(this, &GraphEdit::_graph_node_raised)); // In case of the whole GraphEdit being destroyed these references can already be freed. if (connections_layer != nullptr && connections_layer->is_inside_tree()) { - gn->disconnect("item_rect_changed", callable_mp((CanvasItem *)connections_layer, &CanvasItem::update)); + gn->disconnect("item_rect_changed", callable_mp((CanvasItem *)connections_layer, &CanvasItem::queue_redraw)); } if (minimap != nullptr && minimap->is_inside_tree()) { - gn->disconnect("item_rect_changed", callable_mp((CanvasItem *)minimap, &GraphEditMinimap::update)); + gn->disconnect("item_rect_changed", callable_mp((CanvasItem *)minimap, &GraphEditMinimap::queue_redraw)); } } } @@ -500,8 +517,8 @@ void GraphEdit::_notification(int p_what) { case NOTIFICATION_RESIZED: { _update_scroll(); - top_layer->update(); - minimap->update(); + top_layer->queue_redraw(); + minimap->queue_redraw(); } break; } } @@ -607,13 +624,16 @@ void GraphEdit::_top_layer_input(const Ref<InputEvent> &p_ev) { connecting_color = Object::cast_to<GraphNode>(to)->get_connection_input_color(E.to_port); connecting_target = false; connecting_to = pos; - just_disconnected = true; - emit_signal(SNAME("disconnection_request"), E.from, E.from_port, E.to, E.to_port); - to = get_node(String(connecting_from)); //maybe it was erased - if (Object::cast_to<GraphNode>(to)) { - connecting = true; - emit_signal(SNAME("connection_drag_started"), connecting_from, connecting_index, false); + if (connecting_type >= 0) { + just_disconnected = true; + + emit_signal(SNAME("disconnection_request"), E.from, E.from_port, E.to, E.to_port); + to = get_node(String(connecting_from)); //maybe it was erased + if (Object::cast_to<GraphNode>(to)) { + connecting = true; + emit_signal(SNAME("connection_drag_started"), connecting_from, connecting_index, false); + } } return; } @@ -621,7 +641,6 @@ void GraphEdit::_top_layer_input(const Ref<InputEvent> &p_ev) { } } - connecting = true; connecting_from = gn->get_name(); connecting_index = j; connecting_out = true; @@ -629,8 +648,11 @@ void GraphEdit::_top_layer_input(const Ref<InputEvent> &p_ev) { connecting_color = gn->get_connection_output_color(j); connecting_target = false; connecting_to = pos; - just_disconnected = false; - emit_signal(SNAME("connection_drag_started"), connecting_from, connecting_index, true); + if (connecting_type >= 0) { + connecting = true; + just_disconnected = false; + emit_signal(SNAME("connection_drag_started"), connecting_from, connecting_index, true); + } return; } } @@ -657,11 +679,13 @@ void GraphEdit::_top_layer_input(const Ref<InputEvent> &p_ev) { connecting_to = pos; just_disconnected = true; - emit_signal(SNAME("disconnection_request"), E.from, E.from_port, E.to, E.to_port); - fr = get_node(String(connecting_from)); //maybe it was erased - if (Object::cast_to<GraphNode>(fr)) { - connecting = true; - emit_signal(SNAME("connection_drag_started"), connecting_from, connecting_index, true); + if (connecting_type >= 0) { + emit_signal(SNAME("disconnection_request"), E.from, E.from_port, E.to, E.to_port); + fr = get_node(String(connecting_from)); //maybe it was erased + if (Object::cast_to<GraphNode>(fr)) { + connecting = true; + emit_signal(SNAME("connection_drag_started"), connecting_from, connecting_index, true); + } } return; } @@ -669,7 +693,6 @@ void GraphEdit::_top_layer_input(const Ref<InputEvent> &p_ev) { } } - connecting = true; connecting_from = gn->get_name(); connecting_index = j; connecting_out = false; @@ -677,8 +700,11 @@ void GraphEdit::_top_layer_input(const Ref<InputEvent> &p_ev) { connecting_color = gn->get_connection_input_color(j); connecting_target = false; connecting_to = pos; - just_disconnected = false; - emit_signal(SNAME("connection_drag_started"), connecting_from, connecting_index, false); + if (connecting_type >= 0) { + connecting = true; + just_disconnected = false; + emit_signal(SNAME("connection_drag_started"), connecting_from, connecting_index, false); + } return; } } @@ -689,8 +715,8 @@ void GraphEdit::_top_layer_input(const Ref<InputEvent> &p_ev) { if (mm.is_valid() && connecting) { connecting_to = mm->get_position(); connecting_target = false; - top_layer->update(); - minimap->update(); + top_layer->queue_redraw(); + minimap->queue_redraw(); connecting_valid = just_disconnected || click_pos.distance_to(connecting_to / zoom) > 20.0; if (connecting_valid) { @@ -1127,7 +1153,7 @@ void GraphEdit::gui_input(const Ref<InputEvent> &p_ev) { drag_accum += mm->get_relative(); for (int i = get_child_count() - 1; i >= 0; i--) { GraphNode *gn = Object::cast_to<GraphNode>(get_child(i)); - if (gn && gn->is_selected()) { + if (gn && gn->is_selected() && gn->is_draggable()) { Vector2 pos = (gn->get_drag_from() * zoom + drag_accum) / zoom; // Snapping can be toggled temporarily by holding down Ctrl. @@ -1161,25 +1187,14 @@ void GraphEdit::gui_input(const Ref<InputEvent> &p_ev) { bool in_box = r.intersects(box_selecting_rect); if (in_box) { - if (!gn->is_selected() && box_selection_mode_additive) { - emit_signal(SNAME("node_selected"), gn); - } else if (gn->is_selected() && !box_selection_mode_additive) { - emit_signal(SNAME("node_deselected"), gn); - } gn->set_selected(box_selection_mode_additive); } else { - bool select = (previous_selected.find(gn) != nullptr); - if (gn->is_selected() && !select) { - emit_signal(SNAME("node_deselected"), gn); - } else if (!gn->is_selected() && select) { - emit_signal(SNAME("node_selected"), gn); - } - gn->set_selected(select); + gn->set_selected(previous_selected.find(gn) != nullptr); } } - top_layer->update(); - minimap->update(); + top_layer->queue_redraw(); + minimap->queue_redraw(); } Ref<InputEventMouseButton> b = p_ev; @@ -1193,16 +1208,10 @@ void GraphEdit::gui_input(const Ref<InputEvent> &p_ev) { continue; } - bool select = (previous_selected.find(gn) != nullptr); - if (gn->is_selected() && !select) { - emit_signal(SNAME("node_deselected"), gn); - } else if (!gn->is_selected() && select) { - emit_signal(SNAME("node_selected"), gn); - } - gn->set_selected(select); + gn->set_selected(previous_selected.find(gn) != nullptr); } - top_layer->update(); - minimap->update(); + top_layer->queue_redraw(); + minimap->queue_redraw(); } else { if (connecting) { force_connection_drag_end(); @@ -1222,7 +1231,6 @@ void GraphEdit::gui_input(const Ref<InputEvent> &p_ev) { Rect2 r = gn->get_rect(); r.size *= zoom; if (r.has_point(b->get_position())) { - emit_signal(SNAME("node_deselected"), gn); gn->set_selected(false); } } @@ -1248,27 +1256,30 @@ void GraphEdit::gui_input(const Ref<InputEvent> &p_ev) { dragging = false; - top_layer->update(); - minimap->update(); - update(); - connections_layer->update(); + top_layer->queue_redraw(); + minimap->queue_redraw(); + queue_redraw(); + connections_layer->queue_redraw(); } if (b->get_button_index() == MouseButton::LEFT && b->is_pressed()) { GraphNode *gn = nullptr; + // Find node which was clicked on. for (int i = get_child_count() - 1; i >= 0; i--) { GraphNode *gn_selected = Object::cast_to<GraphNode>(get_child(i)); - if (gn_selected) { - if (gn_selected->is_resizing()) { - continue; - } + if (!gn_selected) { + continue; + } - if (gn_selected->has_point((b->get_position() - gn_selected->get_position()) / zoom)) { - gn = gn_selected; - break; - } + if (gn_selected->is_resizing()) { + continue; + } + + if (gn_selected->has_point((b->get_position() - gn_selected->get_position()) / zoom)) { + gn = gn_selected; + break; } } @@ -1277,22 +1288,18 @@ void GraphEdit::gui_input(const Ref<InputEvent> &p_ev) { return; } + // Left-clicked on a node, select it. dragging = true; drag_accum = Vector2(); just_selected = !gn->is_selected(); if (!gn->is_selected() && !Input::get_singleton()->is_key_pressed(Key::CTRL)) { for (int i = 0; i < get_child_count(); i++) { GraphNode *o_gn = Object::cast_to<GraphNode>(get_child(i)); - if (o_gn) { - if (o_gn == gn) { - o_gn->set_selected(true); - } else { - if (o_gn->is_selected()) { - emit_signal(SNAME("node_deselected"), o_gn); - } - o_gn->set_selected(false); - } + if (!o_gn) { + continue; } + + o_gn->set_selected(o_gn == gn); } } @@ -1319,6 +1326,7 @@ void GraphEdit::gui_input(const Ref<InputEvent> &p_ev) { return; } + // Left-clicked on empty space, start box select. box_selecting = true; box_selecting_from = b->get_position(); if (b->is_ctrl_pressed()) { @@ -1351,9 +1359,7 @@ void GraphEdit::gui_input(const Ref<InputEvent> &p_ev) { if (!gn2) { continue; } - if (gn2->is_selected()) { - emit_signal(SNAME("node_deselected"), gn2); - } + gn2->set_selected(false); } } @@ -1361,11 +1367,12 @@ void GraphEdit::gui_input(const Ref<InputEvent> &p_ev) { } if (b->get_button_index() == MouseButton::LEFT && !b->is_pressed() && box_selecting) { + // Box selection ended. Nodes were selected during mouse movement. box_selecting = false; box_selecting_rect = Rect2(); previous_selected.clear(); - top_layer->update(); - minimap->update(); + top_layer->queue_redraw(); + minimap->queue_redraw(); } } @@ -1431,9 +1438,9 @@ void GraphEdit::set_connection_activity(const StringName &p_from, int p_from_por if (E.from == p_from && E.from_port == p_from_port && E.to == p_to && E.to_port == p_to_port) { if (Math::is_equal_approx(E.activity, p_activity)) { //update only if changed - top_layer->update(); - minimap->update(); - connections_layer->update(); + top_layer->queue_redraw(); + minimap->queue_redraw(); + connections_layer->queue_redraw(); } E.activity = p_activity; return; @@ -1443,19 +1450,19 @@ void GraphEdit::set_connection_activity(const StringName &p_from, int p_from_por void GraphEdit::clear_connections() { connections.clear(); - minimap->update(); - update(); - connections_layer->update(); + minimap->queue_redraw(); + queue_redraw(); + connections_layer->queue_redraw(); } void GraphEdit::force_connection_drag_end() { ERR_FAIL_COND_MSG(!connecting, "Drag end requested without active drag!"); connecting = false; connecting_valid = false; - top_layer->update(); - minimap->update(); - update(); - connections_layer->update(); + top_layer->queue_redraw(); + minimap->queue_redraw(); + queue_redraw(); + connections_layer->queue_redraw(); emit_signal(SNAME("connection_drag_ended")); } @@ -1489,14 +1496,14 @@ void GraphEdit::set_zoom_custom(float p_zoom, const Vector2 &p_center) { Vector2 sbofs = (Vector2(h_scroll->get_value(), v_scroll->get_value()) + p_center) / zoom; zoom = p_zoom; - top_layer->update(); + top_layer->queue_redraw(); zoom_minus->set_disabled(zoom == zoom_min); zoom_plus->set_disabled(zoom == zoom_max); _update_scroll(); - minimap->update(); - connections_layer->update(); + minimap->queue_redraw(); + connections_layer->queue_redraw(); if (is_visible_in_tree()) { Vector2 ofs = sbofs * zoom - p_center; @@ -1505,7 +1512,7 @@ void GraphEdit::set_zoom_custom(float p_zoom, const Vector2 &p_center) { } _update_zoom_label(); - update(); + queue_redraw(); } float GraphEdit::get_zoom() const { @@ -1591,10 +1598,10 @@ void GraphEdit::remove_valid_left_disconnect_type(int p_type) { valid_left_disconnect_types.erase(p_type); } -Array GraphEdit::_get_connection_list() const { +TypedArray<Dictionary> GraphEdit::_get_connection_list() const { List<Connection> conns; get_connection_list(&conns); - Array arr; + TypedArray<Dictionary> arr; for (const Connection &E : conns) { Dictionary d; d["from"] = E.from; @@ -1640,8 +1647,11 @@ bool GraphEdit::is_valid_connection_type(int p_type, int p_with_type) const { } void GraphEdit::set_use_snap(bool p_enable) { + if (snap_button->is_pressed() == p_enable) { + return; + } snap_button->set_pressed(p_enable); - update(); + queue_redraw(); } bool GraphEdit::is_using_snap() const { @@ -1655,15 +1665,15 @@ int GraphEdit::get_snap() const { void GraphEdit::set_snap(int p_snap) { ERR_FAIL_COND(p_snap < 5); snap_amount->set_value(p_snap); - update(); + queue_redraw(); } void GraphEdit::_snap_toggled() { - update(); + queue_redraw(); } void GraphEdit::_snap_value_changed(double) { - update(); + queue_redraw(); } void GraphEdit::set_minimap_size(Vector2 p_size) { @@ -1675,7 +1685,7 @@ void GraphEdit::set_minimap_size(Vector2 p_size) { minimap->set_offset(Side::SIDE_TOP, -minimap_size.y - MINIMAP_OFFSET); minimap->set_offset(Side::SIDE_RIGHT, -MINIMAP_OFFSET); minimap->set_offset(Side::SIDE_BOTTOM, -MINIMAP_OFFSET); - minimap->update(); + minimap->queue_redraw(); } Vector2 GraphEdit::get_minimap_size() const { @@ -1683,8 +1693,11 @@ Vector2 GraphEdit::get_minimap_size() const { } void GraphEdit::set_minimap_opacity(float p_opacity) { + if (minimap->get_modulate().a == p_opacity) { + return; + } minimap->set_modulate(Color(1, 1, 1, p_opacity)); - minimap->update(); + minimap->queue_redraw(); } float GraphEdit::get_minimap_opacity() const { @@ -1693,19 +1706,35 @@ float GraphEdit::get_minimap_opacity() const { } void GraphEdit::set_minimap_enabled(bool p_enable) { + if (minimap_button->is_pressed() == p_enable) { + return; + } minimap_button->set_pressed(p_enable); _minimap_toggled(); - minimap->update(); + minimap->queue_redraw(); } bool GraphEdit::is_minimap_enabled() const { return minimap_button->is_pressed(); } +void GraphEdit::set_arrange_nodes_button_hidden(bool p_enable) { + arrange_nodes_button_hidden = p_enable; + if (arrange_nodes_button_hidden) { + layout_button->hide(); + } else { + layout_button->show(); + } +} + +bool GraphEdit::is_arrange_nodes_button_hidden() const { + return arrange_nodes_button_hidden; +} + void GraphEdit::_minimap_toggled() { if (is_minimap_enabled()) { minimap->set_visible(true); - minimap->update(); + minimap->queue_redraw(); } else { minimap->set_visible(false); } @@ -1713,7 +1742,7 @@ void GraphEdit::_minimap_toggled() { void GraphEdit::set_connection_lines_curvature(float p_curvature) { lines_curvature = p_curvature; - update(); + queue_redraw(); } float GraphEdit::get_connection_lines_curvature() const { @@ -1721,8 +1750,11 @@ float GraphEdit::get_connection_lines_curvature() const { } void GraphEdit::set_connection_lines_thickness(float p_thickness) { + if (lines_thickness == p_thickness) { + return; + } lines_thickness = p_thickness; - update(); + queue_redraw(); } float GraphEdit::get_connection_lines_thickness() const { @@ -1730,8 +1762,11 @@ float GraphEdit::get_connection_lines_thickness() const { } void GraphEdit::set_connection_lines_antialiased(bool p_antialiased) { + if (lines_antialiased == p_antialiased) { + return; + } lines_antialiased = p_antialiased; - update(); + queue_redraw(); } bool GraphEdit::is_connection_lines_antialiased() const { @@ -2328,6 +2363,9 @@ void GraphEdit::_bind_methods() { ClassDB::bind_method(D_METHOD("set_minimap_enabled", "enable"), &GraphEdit::set_minimap_enabled); ClassDB::bind_method(D_METHOD("is_minimap_enabled"), &GraphEdit::is_minimap_enabled); + ClassDB::bind_method(D_METHOD("set_arrange_nodes_button_hidden", "enable"), &GraphEdit::set_arrange_nodes_button_hidden); + ClassDB::bind_method(D_METHOD("is_arrange_nodes_button_hidden"), &GraphEdit::is_arrange_nodes_button_hidden); + ClassDB::bind_method(D_METHOD("set_right_disconnects", "enable"), &GraphEdit::set_right_disconnects); ClassDB::bind_method(D_METHOD("is_right_disconnects_enabled"), &GraphEdit::is_right_disconnects_enabled); @@ -2367,6 +2405,9 @@ void GraphEdit::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "minimap_size", PROPERTY_HINT_NONE, "suffix:px"), "set_minimap_size", "get_minimap_size"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "minimap_opacity"), "set_minimap_opacity", "get_minimap_opacity"); + ADD_GROUP("UI", ""); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "arrange_nodes_button_hidden"), "set_arrange_nodes_button_hidden", "is_arrange_nodes_button_hidden"); + ADD_SIGNAL(MethodInfo("connection_request", PropertyInfo(Variant::STRING_NAME, "from"), PropertyInfo(Variant::INT, "from_slot"), PropertyInfo(Variant::STRING_NAME, "to"), PropertyInfo(Variant::INT, "to_slot"))); ADD_SIGNAL(MethodInfo("disconnection_request", PropertyInfo(Variant::STRING_NAME, "from"), PropertyInfo(Variant::INT, "from_slot"), PropertyInfo(Variant::STRING_NAME, "to"), PropertyInfo(Variant::INT, "to_slot"))); ADD_SIGNAL(MethodInfo("popup_request", PropertyInfo(Variant::VECTOR2, "position"))); @@ -2381,7 +2422,7 @@ void GraphEdit::_bind_methods() { ADD_SIGNAL(MethodInfo("begin_node_move")); ADD_SIGNAL(MethodInfo("end_node_move")); ADD_SIGNAL(MethodInfo("scroll_offset_changed", PropertyInfo(Variant::VECTOR2, "offset"))); - ADD_SIGNAL(MethodInfo("connection_drag_started", PropertyInfo(Variant::STRING, "from"), PropertyInfo(Variant::STRING, "slot"), PropertyInfo(Variant::BOOL, "is_output"))); + ADD_SIGNAL(MethodInfo("connection_drag_started", PropertyInfo(Variant::STRING, "from"), PropertyInfo(Variant::INT, "slot"), PropertyInfo(Variant::BOOL, "is_output"))); ADD_SIGNAL(MethodInfo("connection_drag_ended")); BIND_ENUM_CONSTANT(SCROLL_ZOOMS); @@ -2449,28 +2490,28 @@ GraphEdit::GraphEdit() { zoom_minus = memnew(Button); zoom_minus->set_flat(true); zoom_hb->add_child(zoom_minus); - zoom_minus->set_tooltip(RTR("Zoom Out")); + zoom_minus->set_tooltip_text(RTR("Zoom Out")); zoom_minus->connect("pressed", callable_mp(this, &GraphEdit::_zoom_minus)); zoom_minus->set_focus_mode(FOCUS_NONE); zoom_reset = memnew(Button); zoom_reset->set_flat(true); zoom_hb->add_child(zoom_reset); - zoom_reset->set_tooltip(RTR("Zoom Reset")); + zoom_reset->set_tooltip_text(RTR("Zoom Reset")); zoom_reset->connect("pressed", callable_mp(this, &GraphEdit::_zoom_reset)); zoom_reset->set_focus_mode(FOCUS_NONE); zoom_plus = memnew(Button); zoom_plus->set_flat(true); zoom_hb->add_child(zoom_plus); - zoom_plus->set_tooltip(RTR("Zoom In")); + zoom_plus->set_tooltip_text(RTR("Zoom In")); zoom_plus->connect("pressed", callable_mp(this, &GraphEdit::_zoom_plus)); zoom_plus->set_focus_mode(FOCUS_NONE); snap_button = memnew(Button); snap_button->set_flat(true); snap_button->set_toggle_mode(true); - snap_button->set_tooltip(RTR("Enable snap and show grid.")); + snap_button->set_tooltip_text(RTR("Enable snap and show grid.")); snap_button->connect("pressed", callable_mp(this, &GraphEdit::_snap_toggled)); snap_button->set_pressed(true); snap_button->set_focus_mode(FOCUS_NONE); @@ -2487,7 +2528,7 @@ GraphEdit::GraphEdit() { minimap_button = memnew(Button); minimap_button->set_flat(true); minimap_button->set_toggle_mode(true); - minimap_button->set_tooltip(RTR("Enable grid minimap.")); + minimap_button->set_tooltip_text(RTR("Enable grid minimap.")); minimap_button->connect("pressed", callable_mp(this, &GraphEdit::_minimap_toggled)); minimap_button->set_pressed(true); minimap_button->set_focus_mode(FOCUS_NONE); @@ -2496,7 +2537,7 @@ GraphEdit::GraphEdit() { layout_button = memnew(Button); layout_button->set_flat(true); zoom_hb->add_child(layout_button); - layout_button->set_tooltip(RTR("Arrange nodes.")); + layout_button->set_tooltip_text(RTR("Arrange nodes.")); layout_button->connect("pressed", callable_mp(this, &GraphEdit::arrange_nodes)); layout_button->set_focus_mode(FOCUS_NONE); diff --git a/scene/gui/graph_edit.h b/scene/gui/graph_edit.h index cf35aeb8b2..b6ce575009 100644 --- a/scene/gui/graph_edit.h +++ b/scene/gui/graph_edit.h @@ -133,6 +133,8 @@ private: void _pan_callback(Vector2 p_scroll_vec); void _zoom_callback(Vector2 p_scroll_vec, Vector2 p_origin, bool p_alt); + bool arrange_nodes_button_hidden = false; + bool connecting = false; String connecting_from; bool connecting_out = false; @@ -184,6 +186,8 @@ private: PackedVector2Array get_connection_line(const Vector2 &p_from, const Vector2 &p_to); void _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); + void _graph_node_selected(Node *p_gn); + void _graph_node_deselected(Node *p_gn); void _graph_node_raised(Node *p_gn); void _graph_node_moved(Node *p_gn); void _graph_node_slot_updated(int p_index, Node *p_gn); @@ -206,7 +210,7 @@ private: void _minimap_draw(); void _update_scroll_offset(); - Array _get_connection_list() const; + TypedArray<Dictionary> _get_connection_list() const; bool lines_on_bg = false; @@ -323,6 +327,9 @@ public: void set_minimap_enabled(bool p_enable); bool is_minimap_enabled() const; + void set_arrange_nodes_button_hidden(bool p_enable); + bool is_arrange_nodes_button_hidden() const; + GraphEditFilter *get_top_layer() const { return top_layer; } GraphEditMinimap *get_minimap() const { return minimap; } void get_connection_list(List<Connection> *r_connections) const; diff --git a/scene/gui/graph_node.cpp b/scene/gui/graph_node.cpp index 112b8c74af..f441144a8e 100644 --- a/scene/gui/graph_node.cpp +++ b/scene/gui/graph_node.cpp @@ -32,9 +32,7 @@ #include "core/string/translation.h" -#ifdef TOOLS_ENABLED #include "graph_edit.h" -#endif struct _MinSizeCache { int min_size; @@ -80,7 +78,7 @@ bool GraphNode::_set(const StringName &p_name, const Variant &p_value) { } set_slot(idx, si.enable_left, si.type_left, si.color_left, si.enable_right, si.type_right, si.color_right, si.custom_slot_left, si.custom_slot_right, si.draw_stylebox); - update(); + queue_redraw(); return true; } @@ -290,7 +288,7 @@ void GraphNode::_resort() { idx++; } - update(); + queue_redraw(); connpos_dirty = true; } @@ -418,7 +416,7 @@ void GraphNode::_notification(int p_what) { _shape(); update_minimum_size(); - update(); + queue_redraw(); } break; } } @@ -445,17 +443,16 @@ void GraphNode::_edit_set_position(const Point2 &p_position) { } set_position(p_position); } +#endif -void GraphNode::_validate_property(PropertyInfo &property) const { - Control::_validate_property(property); +void GraphNode::_validate_property(PropertyInfo &p_property) const { GraphEdit *graph = Object::cast_to<GraphEdit>(get_parent()); if (graph) { - if (property.name == "position") { - property.usage |= PROPERTY_USAGE_READ_ONLY; + if (p_property.name == "position") { + p_property.usage |= PROPERTY_USAGE_READ_ONLY; } } } -#endif void GraphNode::set_slot(int p_idx, bool p_enable_left, int p_type_left, const Color &p_color_left, bool p_enable_right, int p_type_right, const Color &p_color_right, const Ref<Texture2D> &p_custom_left, const Ref<Texture2D> &p_custom_right, bool p_draw_stylebox) { ERR_FAIL_COND_MSG(p_idx < 0, vformat("Cannot set slot with p_idx (%d) lesser than zero.", p_idx)); @@ -478,7 +475,7 @@ void GraphNode::set_slot(int p_idx, bool p_enable_left, int p_type_left, const C s.custom_slot_right = p_custom_right; s.draw_stylebox = p_draw_stylebox; slot_info[p_idx] = s; - update(); + queue_redraw(); connpos_dirty = true; emit_signal(SNAME("slot_updated"), p_idx); @@ -486,13 +483,13 @@ void GraphNode::set_slot(int p_idx, bool p_enable_left, int p_type_left, const C void GraphNode::clear_slot(int p_idx) { slot_info.erase(p_idx); - update(); + queue_redraw(); connpos_dirty = true; } void GraphNode::clear_all_slots() { slot_info.clear(); - update(); + queue_redraw(); connpos_dirty = true; } @@ -506,8 +503,12 @@ bool GraphNode::is_slot_enabled_left(int p_idx) const { void GraphNode::set_slot_enabled_left(int p_idx, bool p_enable_left) { ERR_FAIL_COND_MSG(p_idx < 0, vformat("Cannot set enable_left for the slot with p_idx (%d) lesser than zero.", p_idx)); + if (slot_info[p_idx].enable_left == p_enable_left) { + return; + } + slot_info[p_idx].enable_left = p_enable_left; - update(); + queue_redraw(); connpos_dirty = true; emit_signal(SNAME("slot_updated"), p_idx); @@ -516,8 +517,12 @@ void GraphNode::set_slot_enabled_left(int p_idx, bool p_enable_left) { void GraphNode::set_slot_type_left(int p_idx, int p_type_left) { ERR_FAIL_COND_MSG(!slot_info.has(p_idx), vformat("Cannot set type_left for the slot '%d' because it hasn't been enabled.", p_idx)); + if (slot_info[p_idx].type_left == p_type_left) { + return; + } + slot_info[p_idx].type_left = p_type_left; - update(); + queue_redraw(); connpos_dirty = true; emit_signal(SNAME("slot_updated"), p_idx); @@ -533,8 +538,12 @@ int GraphNode::get_slot_type_left(int p_idx) const { void GraphNode::set_slot_color_left(int p_idx, const Color &p_color_left) { ERR_FAIL_COND_MSG(!slot_info.has(p_idx), vformat("Cannot set color_left for the slot '%d' because it hasn't been enabled.", p_idx)); + if (slot_info[p_idx].color_left == p_color_left) { + return; + } + slot_info[p_idx].color_left = p_color_left; - update(); + queue_redraw(); connpos_dirty = true; emit_signal(SNAME("slot_updated"), p_idx); @@ -557,8 +566,12 @@ bool GraphNode::is_slot_enabled_right(int p_idx) const { void GraphNode::set_slot_enabled_right(int p_idx, bool p_enable_right) { ERR_FAIL_COND_MSG(p_idx < 0, vformat("Cannot set enable_right for the slot with p_idx (%d) lesser than zero.", p_idx)); + if (slot_info[p_idx].enable_right == p_enable_right) { + return; + } + slot_info[p_idx].enable_right = p_enable_right; - update(); + queue_redraw(); connpos_dirty = true; emit_signal(SNAME("slot_updated"), p_idx); @@ -567,8 +580,12 @@ void GraphNode::set_slot_enabled_right(int p_idx, bool p_enable_right) { void GraphNode::set_slot_type_right(int p_idx, int p_type_right) { ERR_FAIL_COND_MSG(!slot_info.has(p_idx), vformat("Cannot set type_right for the slot '%d' because it hasn't been enabled.", p_idx)); + if (slot_info[p_idx].type_right == p_type_right) { + return; + } + slot_info[p_idx].type_right = p_type_right; - update(); + queue_redraw(); connpos_dirty = true; emit_signal(SNAME("slot_updated"), p_idx); @@ -584,8 +601,12 @@ int GraphNode::get_slot_type_right(int p_idx) const { void GraphNode::set_slot_color_right(int p_idx, const Color &p_color_right) { ERR_FAIL_COND_MSG(!slot_info.has(p_idx), vformat("Cannot set color_right for the slot '%d' because it hasn't been enabled.", p_idx)); + if (slot_info[p_idx].color_right == p_color_right) { + return; + } + slot_info[p_idx].color_right = p_color_right; - update(); + queue_redraw(); connpos_dirty = true; emit_signal(SNAME("slot_updated"), p_idx); @@ -609,7 +630,7 @@ void GraphNode::set_slot_draw_stylebox(int p_idx, bool p_enable) { ERR_FAIL_COND_MSG(p_idx < 0, vformat("Cannot set draw_stylebox for the slot with p_idx (%d) lesser than zero.", p_idx)); slot_info[p_idx].draw_stylebox = p_enable; - update(); + queue_redraw(); connpos_dirty = true; emit_signal(SNAME("slot_updated"), p_idx); @@ -667,7 +688,7 @@ void GraphNode::set_title(const String &p_title) { title = p_title; _shape(); - update(); + queue_redraw(); update_minimum_size(); } @@ -680,7 +701,7 @@ void GraphNode::set_text_direction(Control::TextDirection p_text_direction) { if (text_direction != p_text_direction) { text_direction = p_text_direction; _shape(); - update(); + queue_redraw(); } } @@ -692,7 +713,7 @@ void GraphNode::set_language(const String &p_language) { if (language != p_language) { language = p_language; _shape(); - update(); + queue_redraw(); } } @@ -701,9 +722,13 @@ String GraphNode::get_language() const { } void GraphNode::set_position_offset(const Vector2 &p_offset) { + if (position_offset == p_offset) { + return; + } + position_offset = p_offset; emit_signal(SNAME("position_offset_changed")); - update(); + queue_redraw(); } Vector2 GraphNode::get_position_offset() const { @@ -711,8 +736,13 @@ Vector2 GraphNode::get_position_offset() const { } void GraphNode::set_selected(bool p_selected) { + if (!is_selectable() || selected == p_selected) { + return; + } + selected = p_selected; - update(); + emit_signal(p_selected ? SNAME("selected") : SNAME("deselected")); + queue_redraw(); } bool GraphNode::is_selected() { @@ -732,8 +762,12 @@ Vector2 GraphNode::get_drag_from() { } void GraphNode::set_show_close_button(bool p_enable) { + if (show_close == p_enable) { + return; + } + show_close = p_enable; - update(); + queue_redraw(); } bool GraphNode::is_close_button_visible() const { @@ -932,8 +966,12 @@ void GraphNode::gui_input(const Ref<InputEvent> &p_ev) { } void GraphNode::set_overlay(Overlay p_overlay) { + if (overlay == p_overlay) { + return; + } + overlay = p_overlay; - update(); + queue_redraw(); } GraphNode::Overlay GraphNode::get_overlay() const { @@ -941,8 +979,12 @@ GraphNode::Overlay GraphNode::get_overlay() const { } void GraphNode::set_comment(bool p_enable) { + if (comment == p_enable) { + return; + } + comment = p_enable; - update(); + queue_redraw(); } bool GraphNode::is_comment() const { @@ -950,14 +992,37 @@ bool GraphNode::is_comment() const { } void GraphNode::set_resizable(bool p_enable) { + if (resizable == p_enable) { + return; + } + resizable = p_enable; - update(); + queue_redraw(); } bool GraphNode::is_resizable() const { return resizable; } +void GraphNode::set_draggable(bool p_draggable) { + draggable = p_draggable; +} + +bool GraphNode::is_draggable() { + return draggable; +} + +void GraphNode::set_selectable(bool p_selectable) { + if (!p_selectable) { + set_selected(false); + } + selectable = p_selectable; +} + +bool GraphNode::is_selectable() { + return selectable; +} + Vector<int> GraphNode::get_allowed_size_flags_horizontal() const { Vector<int> flags; flags.append(SIZE_FILL); @@ -1019,6 +1084,12 @@ void GraphNode::_bind_methods() { ClassDB::bind_method(D_METHOD("set_resizable", "resizable"), &GraphNode::set_resizable); ClassDB::bind_method(D_METHOD("is_resizable"), &GraphNode::is_resizable); + ClassDB::bind_method(D_METHOD("set_draggable", "draggable"), &GraphNode::set_draggable); + ClassDB::bind_method(D_METHOD("is_draggable"), &GraphNode::is_draggable); + + ClassDB::bind_method(D_METHOD("set_selectable", "selectable"), &GraphNode::set_selectable); + ClassDB::bind_method(D_METHOD("is_selectable"), &GraphNode::is_selectable); + ClassDB::bind_method(D_METHOD("set_selected", "selected"), &GraphNode::set_selected); ClassDB::bind_method(D_METHOD("is_selected"), &GraphNode::is_selected); @@ -1044,6 +1115,8 @@ void GraphNode::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "position_offset", PROPERTY_HINT_NONE, "suffix:px"), "set_position_offset", "get_position_offset"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "show_close"), "set_show_close_button", "is_close_button_visible"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "resizable"), "set_resizable", "is_resizable"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "draggable"), "set_draggable", "is_draggable"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "selectable"), "set_selectable", "is_selectable"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "selected"), "set_selected", "is_selected"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "comment"), "set_comment", "is_comment"); ADD_PROPERTY(PropertyInfo(Variant::INT, "overlay", PROPERTY_HINT_ENUM, "Disabled,Breakpoint,Position"), "set_overlay", "get_overlay"); @@ -1054,6 +1127,8 @@ void GraphNode::_bind_methods() { ADD_GROUP("", ""); ADD_SIGNAL(MethodInfo("position_offset_changed")); + ADD_SIGNAL(MethodInfo("selected")); + ADD_SIGNAL(MethodInfo("deselected")); ADD_SIGNAL(MethodInfo("slot_updated", PropertyInfo(Variant::INT, "idx"))); ADD_SIGNAL(MethodInfo("dragged", PropertyInfo(Variant::VECTOR2, "from"), PropertyInfo(Variant::VECTOR2, "to"))); ADD_SIGNAL(MethodInfo("raise_request")); diff --git a/scene/gui/graph_node.h b/scene/gui/graph_node.h index 0651eb5cc9..9c8f926403 100644 --- a/scene/gui/graph_node.h +++ b/scene/gui/graph_node.h @@ -67,6 +67,8 @@ private: Vector2 position_offset; bool comment = false; bool resizable = false; + bool draggable = true; + bool selectable = true; bool resizing = false; Vector2 resizing_from; @@ -101,7 +103,6 @@ private: #ifdef TOOLS_ENABLED void _edit_set_position(const Point2 &p_position) override; - void _validate_property(PropertyInfo &property) const override; #endif protected: @@ -112,6 +113,7 @@ protected: bool _set(const StringName &p_name, const Variant &p_value); bool _get(const StringName &p_name, Variant &r_ret) const; void _get_property_list(List<PropertyInfo> *p_list) const; + void _validate_property(PropertyInfo &p_property) const; public: bool has_point(const Point2 &p_point) const override; @@ -183,6 +185,12 @@ public: void set_resizable(bool p_enable); bool is_resizable() const; + void set_draggable(bool p_draggable); + bool is_draggable(); + + void set_selectable(bool p_selectable); + bool is_selectable(); + virtual Size2 get_minimum_size() const override; virtual Vector<int> get_allowed_size_flags_horizontal() const override; diff --git a/scene/gui/grid_container.cpp b/scene/gui/grid_container.cpp index eaa6943ad2..8dc890eccb 100644 --- a/scene/gui/grid_container.cpp +++ b/scene/gui/grid_container.cpp @@ -31,6 +31,13 @@ #include "grid_container.h" #include "core/templates/rb_set.h" +void GridContainer::_update_theme_item_cache() { + Container::_update_theme_item_cache(); + + theme_cache.h_separation = get_theme_constant(SNAME("h_separation")); + theme_cache.v_separation = get_theme_constant(SNAME("v_separation")); +} + void GridContainer::_notification(int p_what) { switch (p_what) { case NOTIFICATION_SORT_CHILDREN: { @@ -39,9 +46,6 @@ void GridContainer::_notification(int p_what) { RBSet<int> col_expanded; // Columns which have the SIZE_EXPAND flag set. RBSet<int> row_expanded; // Rows which have the SIZE_EXPAND flag set. - int hsep = get_theme_constant(SNAME("h_separation")); - int vsep = get_theme_constant(SNAME("v_separation")); - // Compute the per-column/per-row data. int valid_controls_index = 0; for (int i = 0; i < get_child_count(); i++) { @@ -98,8 +102,8 @@ void GridContainer::_notification(int p_what) { remaining_space.height -= E.value; } } - remaining_space.height -= vsep * MAX(max_row - 1, 0); - remaining_space.width -= hsep * MAX(max_col - 1, 0); + remaining_space.height -= theme_cache.v_separation * MAX(max_row - 1, 0); + remaining_space.width -= theme_cache.h_separation * MAX(max_col - 1, 0); bool can_fit = false; while (!can_fit && col_expanded.size() > 0) { @@ -202,7 +206,7 @@ void GridContainer::_notification(int p_what) { col_ofs = 0; } if (row > 0) { - row_ofs += (row_expanded.has(row - 1) ? row_expand : row_minh[row - 1]) + vsep; + row_ofs += (row_expanded.has(row - 1) ? row_expand : row_minh[row - 1]) + theme_cache.v_separation; if (row_expanded.has(row - 1) && row - 1 < row_remaining_pixel_index) { // Apply the remaining pixel of the previous row. @@ -224,11 +228,11 @@ void GridContainer::_notification(int p_what) { if (rtl) { Point2 p(col_ofs - s.width, row_ofs); fit_child_in_rect(c, Rect2(p, s)); - col_ofs -= s.width + hsep; + col_ofs -= s.width + theme_cache.h_separation; } else { Point2 p(col_ofs, row_ofs); fit_child_in_rect(c, Rect2(p, s)); - col_ofs += s.width + hsep; + col_ofs += s.width + theme_cache.h_separation; } } } break; @@ -246,6 +250,11 @@ void GridContainer::_notification(int p_what) { void GridContainer::set_columns(int p_columns) { ERR_FAIL_COND(p_columns < 1); + + if (columns == p_columns) { + return; + } + columns = p_columns; queue_sort(); update_minimum_size(); @@ -266,9 +275,6 @@ Size2 GridContainer::get_minimum_size() const { RBMap<int, int> col_minw; RBMap<int, int> row_minh; - int hsep = get_theme_constant(SNAME("h_separation")); - int vsep = get_theme_constant(SNAME("v_separation")); - int max_row = 0; int max_col = 0; @@ -308,8 +314,8 @@ Size2 GridContainer::get_minimum_size() const { ms.height += E.value; } - ms.height += vsep * max_row; - ms.width += hsep * max_col; + ms.height += theme_cache.v_separation * max_row; + ms.width += theme_cache.h_separation * max_col; return ms; } diff --git a/scene/gui/grid_container.h b/scene/gui/grid_container.h index 9d77f90ab3..522046f694 100644 --- a/scene/gui/grid_container.h +++ b/scene/gui/grid_container.h @@ -38,7 +38,14 @@ class GridContainer : public Container { int columns = 1; + struct ThemeCache { + int h_separation = 0; + int v_separation = 0; + } theme_cache; + protected: + virtual void _update_theme_item_cache() override; + void _notification(int p_what); static void _bind_methods(); diff --git a/scene/gui/item_list.cpp b/scene/gui/item_list.cpp index d0a25972f8..5db4d8306f 100644 --- a/scene/gui/item_list.cpp +++ b/scene/gui/item_list.cpp @@ -43,9 +43,9 @@ void ItemList::_shape(int p_idx) { } else { item.text_buf->set_direction((TextServer::Direction)item.text_direction); } - item.text_buf->add_string(item.text, get_theme_font(SNAME("font")), get_theme_font_size(SNAME("font_size")), item.language); + item.text_buf->add_string(item.text, theme_cache.font, theme_cache.font_size, item.language); if (icon_mode == ICON_MODE_TOP && max_text_lines > 0) { - item.text_buf->set_break_flags(TextServer::BREAK_MANDATORY | TextServer::BREAK_WORD_BOUND | TextServer::BREAK_GRAPHEME_BOUND); + item.text_buf->set_break_flags(TextServer::BREAK_MANDATORY | TextServer::BREAK_WORD_BOUND | TextServer::BREAK_GRAPHEME_BOUND | TextServer::BREAK_TRIM_EDGE_SPACES); } else { item.text_buf->set_break_flags(TextServer::BREAK_NONE); } @@ -63,7 +63,7 @@ int ItemList::add_item(const String &p_item, const Ref<Texture2D> &p_texture, bo _shape(items.size() - 1); - update(); + queue_redraw(); shape_changed = true; notify_property_list_changed(); return item_id; @@ -76,7 +76,7 @@ int ItemList::add_icon_item(const Ref<Texture2D> &p_item, bool p_selectable) { items.push_back(item); int item_id = items.size() - 1; - update(); + queue_redraw(); shape_changed = true; notify_property_list_changed(); return item_id; @@ -88,9 +88,13 @@ void ItemList::set_item_text(int p_idx, const String &p_text) { } ERR_FAIL_INDEX(p_idx, items.size()); + if (items[p_idx].text == p_text) { + return; + } + items.write[p_idx].text = p_text; _shape(p_idx); - update(); + queue_redraw(); shape_changed = true; } @@ -108,7 +112,7 @@ void ItemList::set_item_text_direction(int p_idx, Control::TextDirection p_text_ if (items[p_idx].text_direction != p_text_direction) { items.write[p_idx].text_direction = p_text_direction; _shape(p_idx); - update(); + queue_redraw(); } } @@ -125,7 +129,7 @@ void ItemList::set_item_language(int p_idx, const String &p_language) { if (items[p_idx].language != p_language) { items.write[p_idx].language = p_language; _shape(p_idx); - update(); + queue_redraw(); } } @@ -153,8 +157,12 @@ void ItemList::set_item_tooltip(int p_idx, const String &p_tooltip) { } ERR_FAIL_INDEX(p_idx, items.size()); + if (items[p_idx].tooltip == p_tooltip) { + return; + } + items.write[p_idx].tooltip = p_tooltip; - update(); + queue_redraw(); shape_changed = true; } @@ -169,8 +177,12 @@ void ItemList::set_item_icon(int p_idx, const Ref<Texture2D> &p_icon) { } ERR_FAIL_INDEX(p_idx, items.size()); + if (items[p_idx].icon == p_icon) { + return; + } + items.write[p_idx].icon = p_icon; - update(); + queue_redraw(); shape_changed = true; } @@ -186,8 +198,12 @@ void ItemList::set_item_icon_transposed(int p_idx, const bool p_transposed) { } ERR_FAIL_INDEX(p_idx, items.size()); + if (items[p_idx].icon_transposed == p_transposed) { + return; + } + items.write[p_idx].icon_transposed = p_transposed; - update(); + queue_redraw(); shape_changed = true; } @@ -203,8 +219,12 @@ void ItemList::set_item_icon_region(int p_idx, const Rect2 &p_region) { } ERR_FAIL_INDEX(p_idx, items.size()); + if (items[p_idx].icon_region == p_region) { + return; + } + items.write[p_idx].icon_region = p_region; - update(); + queue_redraw(); shape_changed = true; } @@ -220,8 +240,12 @@ void ItemList::set_item_icon_modulate(int p_idx, const Color &p_modulate) { } ERR_FAIL_INDEX(p_idx, items.size()); + if (items[p_idx].icon_modulate == p_modulate) { + return; + } + items.write[p_idx].icon_modulate = p_modulate; - update(); + queue_redraw(); } Color ItemList::get_item_icon_modulate(int p_idx) const { @@ -236,8 +260,12 @@ void ItemList::set_item_custom_bg_color(int p_idx, const Color &p_custom_bg_colo } ERR_FAIL_INDEX(p_idx, items.size()); + if (items[p_idx].custom_bg == p_custom_bg_color) { + return; + } + items.write[p_idx].custom_bg = p_custom_bg_color; - update(); + queue_redraw(); } Color ItemList::get_item_custom_bg_color(int p_idx) const { @@ -252,8 +280,12 @@ void ItemList::set_item_custom_fg_color(int p_idx, const Color &p_custom_fg_colo } ERR_FAIL_INDEX(p_idx, items.size()); + if (items[p_idx].custom_fg == p_custom_fg_color) { + return; + } + items.write[p_idx].custom_fg = p_custom_fg_color; - update(); + queue_redraw(); } Color ItemList::get_item_custom_fg_color(int p_idx) const { @@ -268,8 +300,12 @@ void ItemList::set_item_tag_icon(int p_idx, const Ref<Texture2D> &p_tag_icon) { } ERR_FAIL_INDEX(p_idx, items.size()); + if (items[p_idx].tag_icon == p_tag_icon) { + return; + } + items.write[p_idx].tag_icon = p_tag_icon; - update(); + queue_redraw(); shape_changed = true; } @@ -299,8 +335,12 @@ void ItemList::set_item_disabled(int p_idx, bool p_disabled) { } ERR_FAIL_INDEX(p_idx, items.size()); + if (items[p_idx].disabled == p_disabled) { + return; + } + items.write[p_idx].disabled = p_disabled; - update(); + queue_redraw(); } bool ItemList::is_item_disabled(int p_idx) const { @@ -314,8 +354,12 @@ void ItemList::set_item_metadata(int p_idx, const Variant &p_metadata) { } ERR_FAIL_INDEX(p_idx, items.size()); + if (items[p_idx].metadata == p_metadata) { + return; + } + items.write[p_idx].metadata = p_metadata; - update(); + queue_redraw(); shape_changed = true; } @@ -343,7 +387,7 @@ void ItemList::select(int p_idx, bool p_single) { items.write[p_idx].selected = true; } } - update(); + queue_redraw(); } void ItemList::deselect(int p_idx) { @@ -355,7 +399,7 @@ void ItemList::deselect(int p_idx) { } else { items.write[p_idx].selected = false; } - update(); + queue_redraw(); } void ItemList::deselect_all() { @@ -367,7 +411,7 @@ void ItemList::deselect_all() { items.write[i].selected = false; } current = -1; - update(); + queue_redraw(); } bool ItemList::is_selected(int p_idx) const { @@ -379,11 +423,15 @@ bool ItemList::is_selected(int p_idx) const { void ItemList::set_current(int p_current) { ERR_FAIL_INDEX(p_current, items.size()); + if (current == p_current) { + return; + } + if (select_mode == SELECT_SINGLE) { select(p_current, true); } else { current = p_current; - update(); + queue_redraw(); } } @@ -403,15 +451,20 @@ void ItemList::move_item(int p_from_idx, int p_to_idx) { items.remove_at(p_from_idx); items.insert(p_to_idx, item); - update(); + queue_redraw(); shape_changed = true; notify_property_list_changed(); } void ItemList::set_item_count(int p_count) { ERR_FAIL_COND(p_count < 0); + + if (items.size() == p_count) { + return; + } + items.resize(p_count); - update(); + queue_redraw(); shape_changed = true; notify_property_list_changed(); } @@ -427,7 +480,7 @@ void ItemList::remove_item(int p_idx) { if (current == p_idx) { current = -1; } - update(); + queue_redraw(); shape_changed = true; defer_select_single = -1; notify_property_list_changed(); @@ -437,7 +490,7 @@ void ItemList::clear() { items.clear(); current = -1; ensure_selected_visible = false; - update(); + queue_redraw(); shape_changed = true; defer_select_single = -1; notify_property_list_changed(); @@ -445,8 +498,13 @@ void ItemList::clear() { void ItemList::set_fixed_column_width(int p_size) { ERR_FAIL_COND(p_size < 0); + + if (fixed_column_width == p_size) { + return; + } + fixed_column_width = p_size; - update(); + queue_redraw(); shape_changed = true; } @@ -455,8 +513,12 @@ int ItemList::get_fixed_column_width() const { } void ItemList::set_same_column_width(bool p_enable) { + if (same_column_width == p_enable) { + return; + } + same_column_width = p_enable; - update(); + queue_redraw(); shape_changed = true; } @@ -470,14 +532,14 @@ void ItemList::set_max_text_lines(int p_lines) { max_text_lines = p_lines; for (int i = 0; i < items.size(); i++) { if (icon_mode == ICON_MODE_TOP && max_text_lines > 0) { - items.write[i].text_buf->set_break_flags(TextServer::BREAK_MANDATORY | TextServer::BREAK_WORD_BOUND | TextServer::BREAK_GRAPHEME_BOUND); + items.write[i].text_buf->set_break_flags(TextServer::BREAK_MANDATORY | TextServer::BREAK_WORD_BOUND | TextServer::BREAK_GRAPHEME_BOUND | TextServer::BREAK_TRIM_EDGE_SPACES); items.write[i].text_buf->set_max_lines_visible(p_lines); } else { items.write[i].text_buf->set_break_flags(TextServer::BREAK_NONE); } } shape_changed = true; - update(); + queue_redraw(); } } @@ -487,8 +549,13 @@ int ItemList::get_max_text_lines() const { void ItemList::set_max_columns(int p_amount) { ERR_FAIL_COND(p_amount < 0); + + if (max_columns == p_amount) { + return; + } + max_columns = p_amount; - update(); + queue_redraw(); shape_changed = true; } @@ -497,8 +564,12 @@ int ItemList::get_max_columns() const { } void ItemList::set_select_mode(SelectMode p_mode) { + if (select_mode == p_mode) { + return; + } + select_mode = p_mode; - update(); + queue_redraw(); } ItemList::SelectMode ItemList::get_select_mode() const { @@ -511,13 +582,13 @@ void ItemList::set_icon_mode(IconMode p_mode) { icon_mode = p_mode; for (int i = 0; i < items.size(); i++) { if (icon_mode == ICON_MODE_TOP && max_text_lines > 0) { - items.write[i].text_buf->set_break_flags(TextServer::BREAK_MANDATORY | TextServer::BREAK_WORD_BOUND | TextServer::BREAK_GRAPHEME_BOUND); + items.write[i].text_buf->set_break_flags(TextServer::BREAK_MANDATORY | TextServer::BREAK_WORD_BOUND | TextServer::BREAK_GRAPHEME_BOUND | TextServer::BREAK_TRIM_EDGE_SPACES); } else { items.write[i].text_buf->set_break_flags(TextServer::BREAK_NONE); } } shape_changed = true; - update(); + queue_redraw(); } } @@ -526,8 +597,12 @@ ItemList::IconMode ItemList::get_icon_mode() const { } void ItemList::set_fixed_icon_size(const Size2 &p_size) { + if (fixed_icon_size == p_size) { + return; + } + fixed_icon_size = p_size; - update(); + queue_redraw(); } Size2 ItemList::get_fixed_icon_size() const { @@ -580,8 +655,7 @@ void ItemList::gui_input(const Ref<InputEvent> &p_event) { if (mb.is_valid() && mb->is_pressed()) { search_string = ""; //any mousepress cancels Vector2 pos = mb->get_position(); - Ref<StyleBox> bg = get_theme_stylebox(SNAME("bg")); - pos -= bg->get_offset(); + pos -= theme_cache.panel_style->get_offset(); pos.y += scroll_bar->get_value(); if (is_layout_rtl()) { @@ -605,7 +679,7 @@ void ItemList::gui_input(const Ref<InputEvent> &p_event) { if (closest != -1 && (mb->get_button_index() == MouseButton::LEFT || (allow_rmb_select && mb->get_button_index() == MouseButton::RIGHT))) { int i = closest; - if (select_mode == SELECT_MULTI && items[i].selected && mb->is_command_pressed()) { + if (select_mode == SELECT_MULTI && items[i].selected && mb->is_command_or_control_pressed()) { deselect(i); emit_signal(SNAME("multi_selected"), i, false); @@ -628,13 +702,13 @@ void ItemList::gui_input(const Ref<InputEvent> &p_event) { emit_signal(SNAME("item_clicked"), i, get_local_mouse_position(), mb->get_button_index()); } else { - if (!mb->is_double_click() && !mb->is_command_pressed() && select_mode == SELECT_MULTI && items[i].selectable && !items[i].disabled && items[i].selected && mb->get_button_index() == MouseButton::LEFT) { + if (!mb->is_double_click() && !mb->is_command_or_control_pressed() && select_mode == SELECT_MULTI && items[i].selectable && !items[i].disabled && items[i].selected && mb->get_button_index() == MouseButton::LEFT) { defer_select_single = i; return; } if (!items[i].selected || allow_reselect) { - select(i, select_mode == SELECT_SINGLE || !mb->is_command_pressed()); + select(i, select_mode == SELECT_SINGLE || !mb->is_command_or_control_pressed()); if (select_mode == SELECT_SINGLE) { emit_signal(SNAME("item_selected"), i); @@ -886,7 +960,7 @@ void ItemList::gui_input(const Ref<InputEvent> &p_event) { void ItemList::ensure_current_is_visible() { ensure_selected_visible = true; - update(); + queue_redraw(); } static Rect2 _adjust_to_max_size(Size2 p_size, Size2 p_max_size) { @@ -905,11 +979,36 @@ static Rect2 _adjust_to_max_size(Size2 p_size, Size2 p_max_size) { return Rect2(ofs_x, ofs_y, tex_width, tex_height); } +void ItemList::_update_theme_item_cache() { + Control::_update_theme_item_cache(); + + theme_cache.h_separation = get_theme_constant(SNAME("h_separation")); + theme_cache.v_separation = get_theme_constant(SNAME("v_separation")); + + theme_cache.panel_style = get_theme_stylebox(SNAME("panel")); + theme_cache.focus_style = get_theme_stylebox(SNAME("focus")); + + theme_cache.font = get_theme_font(SNAME("font")); + theme_cache.font_size = get_theme_font_size(SNAME("font_size")); + theme_cache.font_color = get_theme_color(SNAME("font_color")); + theme_cache.font_selected_color = get_theme_color(SNAME("font_selected_color")); + theme_cache.font_outline_size = get_theme_constant(SNAME("outline_size")); + theme_cache.font_outline_color = get_theme_color(SNAME("font_outline_color")); + + theme_cache.line_separation = get_theme_constant(SNAME("line_separation")); + theme_cache.icon_margin = get_theme_constant(SNAME("icon_margin")); + theme_cache.selected_style = get_theme_stylebox(SNAME("selected")); + theme_cache.selected_focus_style = get_theme_stylebox(SNAME("selected_focus")); + theme_cache.cursor_style = get_theme_stylebox(SNAME("cursor_unfocused")); + theme_cache.cursor_focus_style = get_theme_stylebox(SNAME("cursor")); + theme_cache.guide_color = get_theme_color(SNAME("guide_color")); +} + void ItemList::_notification(int p_what) { switch (p_what) { case NOTIFICATION_RESIZED: { shape_changed = true; - update(); + queue_redraw(); } break; case NOTIFICATION_LAYOUT_DIRECTION_CHANGED: @@ -919,45 +1018,36 @@ void ItemList::_notification(int p_what) { _shape(i); } shape_changed = true; - update(); + queue_redraw(); } break; case NOTIFICATION_DRAW: { - Ref<StyleBox> bg = get_theme_stylebox(SNAME("bg")); - int mw = scroll_bar->get_minimum_size().x; scroll_bar->set_anchor_and_offset(SIDE_LEFT, ANCHOR_END, -mw); scroll_bar->set_anchor_and_offset(SIDE_RIGHT, ANCHOR_END, 0); - scroll_bar->set_anchor_and_offset(SIDE_TOP, ANCHOR_BEGIN, bg->get_margin(SIDE_TOP)); - scroll_bar->set_anchor_and_offset(SIDE_BOTTOM, ANCHOR_END, -bg->get_margin(SIDE_BOTTOM)); + scroll_bar->set_anchor_and_offset(SIDE_TOP, ANCHOR_BEGIN, theme_cache.panel_style->get_margin(SIDE_TOP)); + scroll_bar->set_anchor_and_offset(SIDE_BOTTOM, ANCHOR_END, -theme_cache.panel_style->get_margin(SIDE_BOTTOM)); Size2 size = get_size(); + int width = size.width - theme_cache.panel_style->get_minimum_size().width; - int width = size.width - bg->get_minimum_size().width; - if (scroll_bar->is_visible()) { - width -= mw; - } - - draw_style_box(bg, Rect2(Point2(), size)); + draw_style_box(theme_cache.panel_style, Rect2(Point2(), size)); - int hseparation = get_theme_constant(SNAME("h_separation")); - int vseparation = get_theme_constant(SNAME("v_separation")); - int icon_margin = get_theme_constant(SNAME("icon_margin")); - int line_separation = get_theme_constant(SNAME("line_separation")); - Color font_outline_color = get_theme_color(SNAME("font_outline_color")); - int outline_size = get_theme_constant(SNAME("outline_size")); + Ref<StyleBox> sbsel; + Ref<StyleBox> cursor; - Ref<StyleBox> sbsel = has_focus() ? get_theme_stylebox(SNAME("selected_focus")) : get_theme_stylebox(SNAME("selected")); - Ref<StyleBox> cursor = has_focus() ? get_theme_stylebox(SNAME("cursor")) : get_theme_stylebox(SNAME("cursor_unfocused")); + if (has_focus()) { + sbsel = theme_cache.selected_focus_style; + cursor = theme_cache.cursor_focus_style; + } else { + sbsel = theme_cache.selected_style; + cursor = theme_cache.cursor_style; + } bool rtl = is_layout_rtl(); - Color guide_color = get_theme_color(SNAME("guide_color")); - Color font_color = get_theme_color(SNAME("font_color")); - Color font_selected_color = get_theme_color(SNAME("font_selected_color")); - if (has_focus()) { RenderingServer::get_singleton()->canvas_item_add_clip_ignore(get_canvas_item(), true); - draw_style_box(get_theme_stylebox(SNAME("bg_focus")), Rect2(Point2(), size)); + draw_style_box(theme_cache.focus_style, Rect2(Point2(), size)); RenderingServer::get_singleton()->canvas_item_add_clip_ignore(get_canvas_item(), false); } @@ -976,9 +1066,9 @@ void ItemList::_notification(int p_what) { if (!items[i].text.is_empty()) { if (icon_mode == ICON_MODE_TOP) { - minsize.y += icon_margin; + minsize.y += theme_cache.icon_margin; } else { - minsize.x += icon_margin; + minsize.x += theme_cache.icon_margin; } } } @@ -996,7 +1086,7 @@ void ItemList::_notification(int p_what) { if (icon_mode == ICON_MODE_TOP) { minsize.x = MAX(minsize.x, s.width); if (max_text_lines > 0) { - minsize.y += s.height + line_separation * max_text_lines; + minsize.y += s.height + theme_cache.line_separation * max_text_lines; } else { minsize.y += s.height; } @@ -1013,13 +1103,13 @@ void ItemList::_notification(int p_what) { max_column_width = MAX(max_column_width, minsize.x); // elements need to adapt to the selected size - minsize.y += vseparation; - minsize.x += hseparation; + minsize.y += theme_cache.v_separation; + minsize.x += theme_cache.h_separation; items.write[i].rect_cache.size = minsize; items.write[i].min_rect_cache.size = minsize; } - int fit_size = size.x - bg->get_minimum_size().width - mw; + int fit_size = size.x - theme_cache.panel_style->get_minimum_size().width - mw; //2-attempt best fit current_columns = 0x7FFFFFFF; @@ -1047,11 +1137,11 @@ void ItemList::_notification(int p_what) { } items.write[i].rect_cache.position = ofs; max_h = MAX(max_h, items[i].rect_cache.size.y); - ofs.x += items[i].rect_cache.size.x + hseparation; + ofs.x += items[i].rect_cache.size.x + theme_cache.h_separation; col++; if (col == current_columns) { if (i < items.size() - 1) { - separators.push_back(ofs.y + max_h + vseparation / 2); + separators.push_back(ofs.y + max_h + theme_cache.v_separation / 2); } for (int j = i; j >= 0 && col > 0; j--, col--) { @@ -1059,7 +1149,7 @@ void ItemList::_notification(int p_what) { } ofs.x = 0; - ofs.y += max_h + vseparation; + ofs.y += max_h + theme_cache.v_separation; col = 0; max_h = 0; } @@ -1070,10 +1160,10 @@ void ItemList::_notification(int p_what) { } if (all_fit) { - float page = MAX(0, size.height - bg->get_minimum_size().height); + float page = MAX(0, size.height - theme_cache.panel_style->get_minimum_size().height); float max = MAX(page, ofs.y + max_h); if (auto_height) { - auto_height_value = ofs.y + max_h + bg->get_minimum_size().height; + auto_height_value = ofs.y + max_h + theme_cache.panel_style->get_minimum_size().height; } scroll_bar->set_max(max); scroll_bar->set_page(page); @@ -1095,6 +1185,10 @@ void ItemList::_notification(int p_what) { shape_changed = false; } + if (scroll_bar->is_visible()) { + width -= mw; + } + //ensure_selected_visible needs to be checked before we draw the list. if (ensure_selected_visible && current >= 0 && current < items.size()) { Rect2 r = items[current].rect_cache; @@ -1110,7 +1204,7 @@ void ItemList::_notification(int p_what) { ensure_selected_visible = false; - Vector2 base_ofs = bg->get_offset(); + Vector2 base_ofs = theme_cache.panel_style->get_offset(); base_ofs.y -= int(scroll_bar->get_value()); const Rect2 clip(-base_ofs, size); // visible frame, don't need to draw outside of there @@ -1154,10 +1248,10 @@ void ItemList::_notification(int p_what) { if (items[i].selected) { Rect2 r = rcache; r.position += base_ofs; - r.position.y -= vseparation / 2; - r.size.y += vseparation; - r.position.x -= hseparation / 2; - r.size.x += hseparation; + r.position.y -= theme_cache.v_separation / 2; + r.size.y += theme_cache.v_separation; + r.position.x -= theme_cache.h_separation / 2; + r.size.x += theme_cache.h_separation; if (rtl) { r.position.x = size.width - r.position.x - r.size.x; @@ -1170,10 +1264,10 @@ void ItemList::_notification(int p_what) { r.position += base_ofs; // Size rect to make the align the temperature colors - r.position.y -= vseparation / 2; - r.size.y += vseparation; - r.position.x -= hseparation / 2; - r.size.x += hseparation; + r.position.y -= theme_cache.v_separation / 2; + r.size.y += theme_cache.v_separation; + r.position.x -= theme_cache.h_separation / 2; + r.size.x += theme_cache.h_separation; if (rtl) { r.position.x = size.width - r.position.x - r.size.x; @@ -1199,11 +1293,11 @@ void ItemList::_notification(int p_what) { if (icon_mode == ICON_MODE_TOP) { pos.x += Math::floor((items[i].rect_cache.size.width - icon_size.width) / 2); - pos.y += icon_margin; - text_ofs.y = icon_size.height + icon_margin * 2; + pos.y += theme_cache.icon_margin; + text_ofs.y = icon_size.height + theme_cache.icon_margin * 2; } else { pos.y += Math::floor((items[i].rect_cache.size.height - icon_size.height) / 2); - text_ofs.x = icon_size.width + icon_margin; + text_ofs.x = icon_size.width + theme_cache.icon_margin; } Rect2 draw_rect = Rect2(pos, icon_size); @@ -1254,7 +1348,7 @@ void ItemList::_notification(int p_what) { max_len = size2.x; } - Color modulate = items[i].selected ? font_selected_color : (items[i].custom_fg != Color() ? items[i].custom_fg : font_color); + Color modulate = items[i].selected ? theme_cache.font_selected_color : (items[i].custom_fg != Color() ? items[i].custom_fg : theme_cache.font_color); if (items[i].disabled) { modulate.a *= 0.5; } @@ -1269,8 +1363,8 @@ void ItemList::_notification(int p_what) { items.write[i].text_buf->set_alignment(HORIZONTAL_ALIGNMENT_CENTER); - if (outline_size > 0 && font_outline_color.a > 0) { - items[i].text_buf->draw_outline(get_canvas_item(), text_ofs, outline_size, font_outline_color); + if (theme_cache.font_outline_size > 0 && theme_cache.font_outline_color.a > 0) { + items[i].text_buf->draw_outline(get_canvas_item(), text_ofs, theme_cache.font_outline_size, theme_cache.font_outline_color); } items[i].text_buf->draw(get_canvas_item(), text_ofs, modulate); @@ -1300,8 +1394,8 @@ void ItemList::_notification(int p_what) { items.write[i].text_buf->set_alignment(HORIZONTAL_ALIGNMENT_LEFT); } - if (outline_size > 0 && font_outline_color.a > 0) { - items[i].text_buf->draw_outline(get_canvas_item(), text_ofs, outline_size, font_outline_color); + if (theme_cache.font_outline_size > 0 && theme_cache.font_outline_color.a > 0) { + items[i].text_buf->draw_outline(get_canvas_item(), text_ofs, theme_cache.font_outline_size, theme_cache.font_outline_color); } if (width - text_ofs.x > 0) { @@ -1313,10 +1407,10 @@ void ItemList::_notification(int p_what) { if (select_mode == SELECT_MULTI && i == current) { Rect2 r = rcache; r.position += base_ofs; - r.position.y -= vseparation / 2; - r.size.y += vseparation; - r.position.x -= hseparation / 2; - r.size.x += hseparation; + r.position.y -= theme_cache.v_separation / 2; + r.size.y += theme_cache.v_separation; + r.position.x -= theme_cache.h_separation / 2; + r.size.x += theme_cache.h_separation; if (rtl) { r.position.x = size.width - r.position.x - r.size.x; @@ -1348,20 +1442,19 @@ void ItemList::_notification(int p_what) { } const int y = base_ofs.y + separators[i]; - draw_line(Vector2(bg->get_margin(SIDE_LEFT), y), Vector2(width, y), guide_color); + draw_line(Vector2(theme_cache.panel_style->get_margin(SIDE_LEFT), y), Vector2(width, y), theme_cache.guide_color); } } break; } } void ItemList::_scroll_changed(double) { - update(); + queue_redraw(); } int ItemList::get_item_at_position(const Point2 &p_pos, bool p_exact) const { Vector2 pos = p_pos; - Ref<StyleBox> bg = get_theme_stylebox(SNAME("bg")); - pos -= bg->get_offset(); + pos -= theme_cache.panel_style->get_offset(); pos.y += scroll_bar->get_value(); if (is_layout_rtl()) { @@ -1398,8 +1491,7 @@ bool ItemList::is_pos_at_end_of_items(const Point2 &p_pos) const { } Vector2 pos = p_pos; - Ref<StyleBox> bg = get_theme_stylebox(SNAME("bg")); - pos -= bg->get_offset(); + pos -= theme_cache.panel_style->get_offset(); pos.y += scroll_bar->get_value(); if (is_layout_rtl()) { @@ -1430,7 +1522,7 @@ String ItemList::get_tooltip(const Point2 &p_pos) const { void ItemList::sort_items_by_text() { items.sort(); - update(); + queue_redraw(); shape_changed = true; if (select_mode == SELECT_SINGLE) { @@ -1512,9 +1604,13 @@ void ItemList::set_autoscroll_to_bottom(const bool p_enable) { } void ItemList::set_auto_height(bool p_enable) { + if (auto_height == p_enable) { + return; + } + auto_height = p_enable; shape_changed = true; - update(); + queue_redraw(); } bool ItemList::has_auto_height() const { @@ -1528,7 +1624,7 @@ void ItemList::set_text_overrun_behavior(TextServer::OverrunBehavior p_behavior) items.write[i].text_buf->set_text_overrun_behavior(p_behavior); } shape_changed = true; - update(); + queue_redraw(); } } diff --git a/scene/gui/item_list.h b/scene/gui/item_list.h index 21bd22759c..486c609034 100644 --- a/scene/gui/item_list.h +++ b/scene/gui/item_list.h @@ -109,23 +109,45 @@ private: int max_columns = 1; Size2 fixed_icon_size; - Size2 max_item_size_cache; int defer_select_single = -1; - bool allow_rmb_select = false; - bool allow_reselect = false; real_t icon_scale = 1.0; bool do_autoscroll_to_bottom = false; + struct ThemeCache { + int h_separation = 0; + int v_separation = 0; + + Ref<StyleBox> panel_style; + Ref<StyleBox> focus_style; + + Ref<Font> font; + int font_size = 0; + Color font_color; + Color font_selected_color; + int font_outline_size = 0; + Color font_outline_color; + + int line_separation = 0; + int icon_margin = 0; + Ref<StyleBox> selected_style; + Ref<StyleBox> selected_focus_style; + Ref<StyleBox> cursor_style; + Ref<StyleBox> cursor_focus_style; + Color guide_color; + } theme_cache; + void _scroll_changed(double); void _shape(int p_idx); protected: + virtual void _update_theme_item_cache() override; + void _notification(int p_what); bool _set(const StringName &p_name, const Variant &p_value); bool _get(const StringName &p_name, Variant &r_ret) const; diff --git a/scene/gui/label.cpp b/scene/gui/label.cpp index e7f48beb00..306ca3d340 100644 --- a/scene/gui/label.cpp +++ b/scene/gui/label.cpp @@ -38,11 +38,13 @@ #include "servers/text_server.h" void Label::set_autowrap_mode(TextServer::AutowrapMode p_mode) { - if (autowrap_mode != p_mode) { - autowrap_mode = p_mode; - lines_dirty = true; + if (autowrap_mode == p_mode) { + return; } - update(); + + autowrap_mode = p_mode; + lines_dirty = true; + queue_redraw(); if (clip || overrun_behavior != TextServer::OVERRUN_NO_TRIMMING) { update_minimum_size(); @@ -54,10 +56,14 @@ TextServer::AutowrapMode Label::get_autowrap_mode() const { } void Label::set_uppercase(bool p_uppercase) { + if (uppercase == p_uppercase) { + return; + } + uppercase = p_uppercase; dirty = true; - update(); + queue_redraw(); } bool Label::is_uppercase() const { @@ -65,7 +71,7 @@ bool Label::is_uppercase() const { } int Label::get_line_height(int p_line) const { - Ref<Font> font = (settings.is_valid() && settings->get_font().is_valid()) ? settings->get_font() : get_theme_font(SNAME("font")); + Ref<Font> font = (settings.is_valid() && settings->get_font().is_valid()) ? settings->get_font() : theme_cache.font; if (p_line >= 0 && p_line < lines_rid.size()) { return TS->shaped_text_get_size(lines_rid[p_line]).y; } else if (lines_rid.size() > 0) { @@ -75,13 +81,13 @@ int Label::get_line_height(int p_line) const { } return h; } else { - int font_size = settings.is_valid() ? settings->get_font_size() : get_theme_font_size(SNAME("font_size")); + int font_size = settings.is_valid() ? settings->get_font_size() : theme_cache.font_size; return font->get_height(font_size); } } void Label::_shape() { - Ref<StyleBox> style = get_theme_stylebox(SNAME("normal"), SNAME("Label")); + Ref<StyleBox> style = theme_cache.normal_style; int width = (get_size().width - style->get_minimum_size().width); if (dirty || font_dirty) { @@ -93,8 +99,8 @@ void Label::_shape() { } else { TS->shaped_text_set_direction(text_rid, (TextServer::Direction)text_direction); } - const Ref<Font> &font = (settings.is_valid() && settings->get_font().is_valid()) ? settings->get_font() : get_theme_font(SNAME("font")); - int font_size = settings.is_valid() ? settings->get_font_size() : get_theme_font_size(SNAME("font_size")); + const Ref<Font> &font = (settings.is_valid() && settings->get_font().is_valid()) ? settings->get_font() : theme_cache.font; + int font_size = settings.is_valid() ? settings->get_font_size() : theme_cache.font_size; ERR_FAIL_COND(font.is_null()); String text = (uppercase) ? TS->string_to_upper(xl_text, language) : xl_text; if (visible_chars >= 0 && visible_chars_behavior == TextServer::VC_CHARS_BEFORE_SHAPING) { @@ -137,8 +143,9 @@ void Label::_shape() { case TextServer::AUTOWRAP_OFF: break; } - PackedInt32Array line_breaks = TS->shaped_text_get_line_breaks(text_rid, width, 0, autowrap_flags); + autowrap_flags = autowrap_flags | TextServer::BREAK_TRIM_EDGE_SPACES; + PackedInt32Array line_breaks = TS->shaped_text_get_line_breaks(text_rid, width, 0, autowrap_flags); for (int i = 0; i < line_breaks.size(); i = i + 2) { RID line = TS->shaped_text_substr(text_rid, line_breaks[i], line_breaks[i + 1] - line_breaks[i]); lines_rid.push_back(line); @@ -225,8 +232,8 @@ void Label::_shape() { } void Label::_update_visible() { - int line_spacing = settings.is_valid() ? settings->get_line_spacing() : get_theme_constant(SNAME("line_spacing"), SNAME("Label")); - Ref<StyleBox> style = get_theme_stylebox(SNAME("normal"), SNAME("Label")); + int line_spacing = settings.is_valid() ? settings->get_line_spacing() : theme_cache.line_spacing; + Ref<StyleBox> style = theme_cache.normal_style; int lines_visible = lines_rid.size(); if (max_lines_visible >= 0 && lines_visible > max_lines_visible) { @@ -265,6 +272,22 @@ inline void draw_glyph_outline(const Glyph &p_gl, const RID &p_canvas, const Col } } +void Label::_update_theme_item_cache() { + Control::_update_theme_item_cache(); + + theme_cache.normal_style = get_theme_stylebox(SNAME("normal")); + theme_cache.font = get_theme_font(SNAME("font")); + + theme_cache.font_size = get_theme_font_size(SNAME("font_size")); + theme_cache.line_spacing = get_theme_constant(SNAME("line_spacing")); + theme_cache.font_color = get_theme_color(SNAME("font_color")); + theme_cache.font_shadow_color = get_theme_color(SNAME("font_shadow_color")); + theme_cache.font_shadow_offset = Point2(get_theme_constant(SNAME("shadow_offset_x")), get_theme_constant(SNAME("shadow_offset_y"))); + theme_cache.font_outline_color = get_theme_color(SNAME("font_outline_color")); + theme_cache.font_outline_size = get_theme_constant(SNAME("outline_size")); + theme_cache.font_shadow_outline_size = get_theme_constant(SNAME("shadow_outline_size")); +} + void Label::_notification(int p_what) { switch (p_what) { case NOTIFICATION_TRANSLATION_CHANGED: { @@ -273,16 +296,16 @@ void Label::_notification(int p_what) { return; // Nothing new. } xl_text = new_text; - if (percent_visible < 1) { - visible_chars = get_total_character_count() * percent_visible; + if (visible_ratio < 1) { + visible_chars = get_total_character_count() * visible_ratio; } dirty = true; - update(); + queue_redraw(); } break; case NOTIFICATION_LAYOUT_DIRECTION_CHANGED: { - update(); + queue_redraw(); } break; case NOTIFICATION_DRAW: { @@ -300,15 +323,15 @@ void Label::_notification(int p_what) { Size2 string_size; Size2 size = get_size(); - Ref<StyleBox> style = get_theme_stylebox(SNAME("normal")); - Ref<Font> font = (has_settings && settings->get_font().is_valid()) ? settings->get_font() : get_theme_font(SNAME("font")); - Color font_color = has_settings ? settings->get_font_color() : get_theme_color(SNAME("font_color")); - Color font_shadow_color = has_settings ? settings->get_shadow_color() : get_theme_color(SNAME("font_shadow_color")); - Point2 shadow_ofs = has_settings ? settings->get_shadow_offset() : Point2(get_theme_constant(SNAME("shadow_offset_x")), get_theme_constant(SNAME("shadow_offset_y"))); - int line_spacing = has_settings ? settings->get_line_spacing() : get_theme_constant(SNAME("line_spacing")); - Color font_outline_color = has_settings ? settings->get_outline_color() : get_theme_color(SNAME("font_outline_color")); - int outline_size = has_settings ? settings->get_outline_size() : get_theme_constant(SNAME("outline_size")); - int shadow_outline_size = has_settings ? settings->get_shadow_size() : get_theme_constant(SNAME("shadow_outline_size")); + Ref<StyleBox> style = theme_cache.normal_style; + Ref<Font> font = (has_settings && settings->get_font().is_valid()) ? settings->get_font() : theme_cache.font; + Color font_color = has_settings ? settings->get_font_color() : theme_cache.font_color; + Color font_shadow_color = has_settings ? settings->get_shadow_color() : theme_cache.font_shadow_color; + Point2 shadow_ofs = has_settings ? settings->get_shadow_offset() : theme_cache.font_shadow_offset; + int line_spacing = has_settings ? settings->get_line_spacing() : theme_cache.line_spacing; + Color font_outline_color = has_settings ? settings->get_outline_color() : theme_cache.font_outline_color; + int outline_size = has_settings ? settings->get_outline_size() : theme_cache.font_outline_size; + int shadow_outline_size = has_settings ? settings->get_shadow_size() : theme_cache.font_shadow_outline_size; bool rtl = (TS->shaped_text_get_inferred_direction(text_rid) == TextServer::DIRECTION_RTL); bool rtl_layout = is_layout_rtl(); @@ -342,7 +365,7 @@ void Label::_notification(int p_what) { total_h += TS->shaped_text_get_size(lines_rid[i]).y + line_spacing; total_glyphs += TS->shaped_text_get_glyph_count(lines_rid[i]) + TS->shaped_text_get_ellipsis_glyph_count(lines_rid[i]); } - int visible_glyphs = total_glyphs * percent_visible; + int visible_glyphs = total_glyphs * visible_ratio; int processed_glyphs = 0; total_h += style->get_margin(SIDE_TOP) + style->get_margin(SIDE_BOTTOM); @@ -538,7 +561,7 @@ void Label::_notification(int p_what) { case NOTIFICATION_THEME_CHANGED: { font_dirty = true; - update(); + queue_redraw(); } break; case NOTIFICATION_RESIZED: { @@ -555,12 +578,12 @@ Size2 Label::get_minimum_size() const { Size2 min_size = minsize; - const Ref<Font> &font = (settings.is_valid() && settings->get_font().is_valid()) ? settings->get_font() : get_theme_font(SNAME("font")); - int font_size = settings.is_valid() ? settings->get_font_size() : get_theme_font_size(SNAME("font_size")); + const Ref<Font> &font = (settings.is_valid() && settings->get_font().is_valid()) ? settings->get_font() : theme_cache.font; + int font_size = settings.is_valid() ? settings->get_font_size() : theme_cache.font_size; min_size.height = MAX(min_size.height, font->get_height(font_size) + font->get_spacing(TextServer::SPACING_TOP) + font->get_spacing(TextServer::SPACING_BOTTOM)); - Size2 min_style = get_theme_stylebox(SNAME("normal"))->get_minimum_size(); + Size2 min_style = theme_cache.normal_style->get_minimum_size(); if (autowrap_mode != TextServer::AUTOWRAP_OFF) { return Size2(1, (clip || overrun_behavior != TextServer::OVERRUN_NO_TRIMMING) ? 1 : min_size.height) + min_style; } else { @@ -583,8 +606,8 @@ int Label::get_line_count() const { } int Label::get_visible_line_count() const { - Ref<StyleBox> style = get_theme_stylebox(SNAME("normal")); - int line_spacing = settings.is_valid() ? settings->get_line_spacing() : get_theme_constant(SNAME("line_spacing")); + Ref<StyleBox> style = theme_cache.normal_style; + int line_spacing = settings.is_valid() ? settings->get_line_spacing() : theme_cache.line_spacing; int lines_visible = 0; float total_h = 0.0; for (int64_t i = lines_skipped; i < lines_rid.size(); i++) { @@ -608,13 +631,16 @@ int Label::get_visible_line_count() const { void Label::set_horizontal_alignment(HorizontalAlignment p_alignment) { ERR_FAIL_INDEX((int)p_alignment, 4); - if (horizontal_alignment != p_alignment) { - if (horizontal_alignment == HORIZONTAL_ALIGNMENT_FILL || p_alignment == HORIZONTAL_ALIGNMENT_FILL) { - lines_dirty = true; // Reshape lines. - } - horizontal_alignment = p_alignment; + if (horizontal_alignment == p_alignment) { + return; + } + + if (horizontal_alignment == HORIZONTAL_ALIGNMENT_FILL || p_alignment == HORIZONTAL_ALIGNMENT_FILL) { + lines_dirty = true; // Reshape lines. } - update(); + horizontal_alignment = p_alignment; + + queue_redraw(); } HorizontalAlignment Label::get_horizontal_alignment() const { @@ -623,8 +649,13 @@ HorizontalAlignment Label::get_horizontal_alignment() const { void Label::set_vertical_alignment(VerticalAlignment p_alignment) { ERR_FAIL_INDEX((int)p_alignment, 4); + + if (vertical_alignment == p_alignment) { + return; + } + vertical_alignment = p_alignment; - update(); + queue_redraw(); } VerticalAlignment Label::get_vertical_alignment() const { @@ -638,16 +669,16 @@ void Label::set_text(const String &p_string) { text = p_string; xl_text = atr(p_string); dirty = true; - if (percent_visible < 1) { - visible_chars = get_total_character_count() * percent_visible; + if (visible_ratio < 1) { + visible_chars = get_total_character_count() * visible_ratio; } - update(); + queue_redraw(); update_minimum_size(); } void Label::_invalidate() { font_dirty = true; - update(); + queue_redraw(); } void Label::set_label_settings(const Ref<LabelSettings> &p_settings) { @@ -672,7 +703,7 @@ void Label::set_text_direction(Control::TextDirection p_text_direction) { if (text_direction != p_text_direction) { text_direction = p_text_direction; font_dirty = true; - update(); + queue_redraw(); } } @@ -680,7 +711,7 @@ void Label::set_structured_text_bidi_override(TextServer::StructuredTextParser p if (st_parser != p_parser) { st_parser = p_parser; dirty = true; - update(); + queue_redraw(); } } @@ -689,9 +720,13 @@ TextServer::StructuredTextParser Label::get_structured_text_bidi_override() cons } void Label::set_structured_text_bidi_override_options(Array p_args) { + if (st_args == p_args) { + return; + } + st_args = p_args; dirty = true; - update(); + queue_redraw(); } Array Label::get_structured_text_bidi_override_options() const { @@ -706,7 +741,7 @@ void Label::set_language(const String &p_language) { if (language != p_language) { language = p_language; dirty = true; - update(); + queue_redraw(); } } @@ -715,8 +750,12 @@ String Label::get_language() const { } void Label::set_clip_text(bool p_clip) { + if (clip == p_clip) { + return; + } + clip = p_clip; - update(); + queue_redraw(); update_minimum_size(); } @@ -725,11 +764,13 @@ bool Label::is_clipping_text() const { } void Label::set_text_overrun_behavior(TextServer::OverrunBehavior p_behavior) { - if (overrun_behavior != p_behavior) { - overrun_behavior = p_behavior; - lines_dirty = true; + if (overrun_behavior == p_behavior) { + return; } - update(); + + overrun_behavior = p_behavior; + lines_dirty = true; + queue_redraw(); if (clip || overrun_behavior != TextServer::OVERRUN_NO_TRIMMING) { update_minimum_size(); } @@ -747,14 +788,14 @@ void Label::set_visible_characters(int p_amount) { if (visible_chars != p_amount) { visible_chars = p_amount; if (get_total_character_count() > 0) { - percent_visible = (float)p_amount / (float)get_total_character_count(); + visible_ratio = (float)p_amount / (float)get_total_character_count(); } else { - percent_visible = 1.0; + visible_ratio = 1.0; } if (visible_chars_behavior == TextServer::VC_CHARS_BEFORE_SHAPING) { dirty = true; } - update(); + queue_redraw(); } } @@ -762,24 +803,28 @@ int Label::get_visible_characters() const { return visible_chars; } -void Label::set_percent_visible(float p_percent) { - if (percent_visible != p_percent) { - if (p_percent < 0 || p_percent >= 1) { +void Label::set_visible_ratio(float p_ratio) { + if (visible_ratio != p_ratio) { + if (p_ratio >= 1.0) { visible_chars = -1; - percent_visible = 1; + visible_ratio = 1.0; + } else if (p_ratio < 0.0) { + visible_chars = 0; + visible_ratio = 0.0; } else { - visible_chars = get_total_character_count() * p_percent; - percent_visible = p_percent; + visible_chars = get_total_character_count() * p_ratio; + visible_ratio = p_ratio; } + if (visible_chars_behavior == TextServer::VC_CHARS_BEFORE_SHAPING) { dirty = true; } - update(); + queue_redraw(); } } -float Label::get_percent_visible() const { - return percent_visible; +float Label::get_visible_ratio() const { + return visible_ratio; } TextServer::VisibleCharactersBehavior Label::get_visible_characters_behavior() const { @@ -790,15 +835,20 @@ void Label::set_visible_characters_behavior(TextServer::VisibleCharactersBehavio if (visible_chars_behavior != p_behavior) { visible_chars_behavior = p_behavior; dirty = true; - update(); + queue_redraw(); } } void Label::set_lines_skipped(int p_lines) { ERR_FAIL_COND(p_lines < 0); + + if (lines_skipped == p_lines) { + return; + } + lines_skipped = p_lines; _update_visible(); - update(); + queue_redraw(); } int Label::get_lines_skipped() const { @@ -806,9 +856,13 @@ int Label::get_lines_skipped() const { } void Label::set_max_lines_visible(int p_lines) { + if (max_lines_visible == p_lines) { + return; + } + max_lines_visible = p_lines; _update_visible(); - update(); + queue_redraw(); } int Label::get_max_lines_visible() const { @@ -852,8 +906,8 @@ void Label::_bind_methods() { ClassDB::bind_method(D_METHOD("get_visible_characters"), &Label::get_visible_characters); ClassDB::bind_method(D_METHOD("get_visible_characters_behavior"), &Label::get_visible_characters_behavior); ClassDB::bind_method(D_METHOD("set_visible_characters_behavior", "behavior"), &Label::set_visible_characters_behavior); - ClassDB::bind_method(D_METHOD("set_percent_visible", "percent_visible"), &Label::set_percent_visible); - ClassDB::bind_method(D_METHOD("get_percent_visible"), &Label::get_percent_visible); + ClassDB::bind_method(D_METHOD("set_visible_ratio", "ratio"), &Label::set_visible_ratio); + ClassDB::bind_method(D_METHOD("get_visible_ratio"), &Label::get_visible_ratio); ClassDB::bind_method(D_METHOD("set_lines_skipped", "lines_skipped"), &Label::set_lines_skipped); ClassDB::bind_method(D_METHOD("get_lines_skipped"), &Label::get_lines_skipped); ClassDB::bind_method(D_METHOD("set_max_lines_visible", "lines_visible"), &Label::set_max_lines_visible); @@ -871,13 +925,14 @@ void Label::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::BOOL, "clip_text"), "set_clip_text", "is_clipping_text"); ADD_PROPERTY(PropertyInfo(Variant::INT, "text_overrun_behavior", PROPERTY_HINT_ENUM, "Trim Nothing,Trim Characters,Trim Words,Ellipsis,Word Ellipsis"), "set_text_overrun_behavior", "get_text_overrun_behavior"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "uppercase"), "set_uppercase", "is_uppercase"); + + ADD_GROUP("Displayed Text", ""); ADD_PROPERTY(PropertyInfo(Variant::INT, "lines_skipped", PROPERTY_HINT_RANGE, "0,999,1"), "set_lines_skipped", "get_lines_skipped"); ADD_PROPERTY(PropertyInfo(Variant::INT, "max_lines_visible", PROPERTY_HINT_RANGE, "-1,999,1"), "set_max_lines_visible", "get_max_lines_visible"); - - // Note: "visible_characters" and "percent_visible" should be set after "text" to be correctly applied. + // Note: "visible_characters" and "visible_ratio" should be set after "text" to be correctly applied. ADD_PROPERTY(PropertyInfo(Variant::INT, "visible_characters", PROPERTY_HINT_RANGE, "-1,128000,1"), "set_visible_characters", "get_visible_characters"); ADD_PROPERTY(PropertyInfo(Variant::INT, "visible_characters_behavior", PROPERTY_HINT_ENUM, "Characters Before Shaping,Characters After Shaping,Glyphs (Layout Direction),Glyphs (Left-to-Right),Glyphs (Right-to-Left)"), "set_visible_characters_behavior", "get_visible_characters_behavior"); - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "percent_visible", PROPERTY_HINT_RANGE, "0,1,0.001"), "set_percent_visible", "get_percent_visible"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "visible_ratio", PROPERTY_HINT_RANGE, "0,1,0.001"), "set_visible_ratio", "get_visible_ratio"); ADD_GROUP("BiDi", ""); ADD_PROPERTY(PropertyInfo(Variant::INT, "text_direction", PROPERTY_HINT_ENUM, "Auto,Left-to-Right,Right-to-Left,Inherited"), "set_text_direction", "get_text_direction"); diff --git a/scene/gui/label.h b/scene/gui/label.h index cab5b36d68..b79c94a5ba 100644 --- a/scene/gui/label.h +++ b/scene/gui/label.h @@ -59,22 +59,36 @@ private: TextServer::StructuredTextParser st_parser = TextServer::STRUCTURED_TEXT_DEFAULT; Array st_args; - float percent_visible = 1.0; - TextServer::VisibleCharactersBehavior visible_chars_behavior = TextServer::VC_CHARS_BEFORE_SHAPING; int visible_chars = -1; + float visible_ratio = 1.0; int lines_skipped = 0; int max_lines_visible = -1; Ref<LabelSettings> settings; + struct ThemeCache { + Ref<StyleBox> normal_style; + Ref<Font> font; + + int font_size = 0; + int line_spacing = 0; + Color font_color; + Color font_shadow_color; + Point2 font_shadow_offset; + Color font_outline_color; + int font_outline_size; + int font_shadow_outline_size; + } theme_cache; + void _update_visible(); void _shape(); void _invalidate(); protected: - void _notification(int p_what); + virtual void _update_theme_item_cache() override; + void _notification(int p_what); static void _bind_methods(); public: @@ -110,22 +124,22 @@ public: void set_uppercase(bool p_uppercase); bool is_uppercase() const; - TextServer::VisibleCharactersBehavior get_visible_characters_behavior() const; void set_visible_characters_behavior(TextServer::VisibleCharactersBehavior p_behavior); + TextServer::VisibleCharactersBehavior get_visible_characters_behavior() const; void set_visible_characters(int p_amount); int get_visible_characters() const; int get_total_character_count() const; + void set_visible_ratio(float p_ratio); + float get_visible_ratio() const; + void set_clip_text(bool p_clip); bool is_clipping_text() const; void set_text_overrun_behavior(TextServer::OverrunBehavior p_behavior); TextServer::OverrunBehavior get_text_overrun_behavior() const; - void set_percent_visible(float p_percent); - float get_percent_visible() const; - void set_lines_skipped(int p_lines); int get_lines_skipped() const; diff --git a/scene/gui/line_edit.cpp b/scene/gui/line_edit.cpp index f315b2bbf1..be94337c89 100644 --- a/scene/gui/line_edit.cpp +++ b/scene/gui/line_edit.cpp @@ -51,7 +51,7 @@ void LineEdit::_swap_current_input_direction() { input_direction = TEXT_DIRECTION_LTR; } set_caret_column(get_caret_column()); - update(); + queue_redraw(); } void LineEdit::_move_caret_left(bool p_select, bool p_move_by_word) { @@ -285,7 +285,7 @@ void LineEdit::gui_input(const Ref<InputEvent> &p_event) { if (!text.is_empty() && is_editable() && _is_over_clear_button(b->get_position())) { clear_button_status.press_attempt = true; clear_button_status.pressing_inside = true; - update(); + queue_redraw(); return; } @@ -348,7 +348,7 @@ void LineEdit::gui_input(const Ref<InputEvent> &p_event) { } } - update(); + queue_redraw(); } else { if (selection.enabled && !pass && b->get_button_index() == MouseButton::LEFT && DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_CLIPBOARD_PRIMARY)) { @@ -375,7 +375,7 @@ void LineEdit::gui_input(const Ref<InputEvent> &p_event) { show_virtual_keyboard(); } - update(); + queue_redraw(); } Ref<InputEventMouseMotion> m = p_event; @@ -385,7 +385,7 @@ void LineEdit::gui_input(const Ref<InputEvent> &p_event) { bool last_press_inside = clear_button_status.pressing_inside; clear_button_status.pressing_inside = clear_button_status.press_attempt && _is_over_clear_button(m->get_position()); if (last_press_inside != clear_button_status.pressing_inside) { - update(); + queue_redraw(); } } @@ -448,7 +448,7 @@ void LineEdit::gui_input(const Ref<InputEvent> &p_event) { if (context_menu_enabled) { if (k->is_action("ui_menu", true)) { _ensure_menu(); - Point2 pos = Point2(get_caret_pixel_pos().x, (get_size().y + get_theme_font(SNAME("font"))->get_height(get_theme_font_size(SNAME("font_size")))) / 2); + Point2 pos = Point2(get_caret_pixel_pos().x, (get_size().y + theme_cache.font->get_height(theme_cache.font_size)) / 2); menu->set_position(get_screen_position() + pos); menu->reset_size(); menu->popup(); @@ -589,7 +589,7 @@ void LineEdit::gui_input(const Ref<InputEvent> &p_event) { // Allow unicode handling if: // * No Modifiers are pressed (except shift) - bool allow_unicode_handling = !(k->is_command_pressed() || k->is_ctrl_pressed() || k->is_alt_pressed() || k->is_meta_pressed()); + bool allow_unicode_handling = !(k->is_command_or_control_pressed() || k->is_ctrl_pressed() || k->is_alt_pressed() || k->is_meta_pressed()); if (allow_unicode_handling && editable && k->get_unicode() >= 32) { // Handle Unicode (if no modifiers active) @@ -607,11 +607,13 @@ void LineEdit::gui_input(const Ref<InputEvent> &p_event) { void LineEdit::set_horizontal_alignment(HorizontalAlignment p_alignment) { ERR_FAIL_INDEX((int)p_alignment, 4); - if (alignment != p_alignment) { - alignment = p_alignment; - _shape(); + if (alignment == p_alignment) { + return; } - update(); + + alignment = p_alignment; + _shape(); + queue_redraw(); } HorizontalAlignment LineEdit::get_horizontal_alignment() const { @@ -679,7 +681,7 @@ void LineEdit::drop_data(const Point2 &p_point, const Variant &p_data) { } text_changed_dirty = true; } - update(); + queue_redraw(); } } @@ -694,18 +696,45 @@ bool LineEdit::_is_over_clear_button(const Point2 &p_pos) const { if (!clear_button_enabled || !has_point(p_pos)) { return false; } - Ref<Texture2D> icon = Control::get_theme_icon(SNAME("clear")); - int x_ofs = get_theme_stylebox(SNAME("normal"))->get_margin(SIDE_RIGHT); + Ref<Texture2D> icon = theme_cache.clear_icon; + int x_ofs = theme_cache.normal->get_margin(SIDE_RIGHT); return p_pos.x > get_size().width - icon->get_width() - x_ofs; } +void LineEdit::_update_theme_item_cache() { + Control::_update_theme_item_cache(); + + theme_cache.normal = get_theme_stylebox(SNAME("normal")); + theme_cache.read_only = get_theme_stylebox(SNAME("read_only")); + theme_cache.focus = get_theme_stylebox(SNAME("focus")); + + theme_cache.font = get_theme_font(SNAME("font")); + theme_cache.font_size = get_theme_font_size(SNAME("font_size")); + theme_cache.font_color = get_theme_color(SNAME("font_color")); + theme_cache.font_uneditable_color = get_theme_color(SNAME("font_uneditable_color")); + theme_cache.font_selected_color = get_theme_color(SNAME("font_selected_color")); + theme_cache.font_outline_size = get_theme_constant(SNAME("outline_size")); + theme_cache.font_outline_color = get_theme_color(SNAME("font_outline_color")); + theme_cache.font_placeholder_color = get_theme_color(SNAME("font_placeholder_color")); + theme_cache.caret_width = get_theme_constant(SNAME("caret_width")); + theme_cache.caret_color = get_theme_color(SNAME("caret_color")); + theme_cache.minimum_character_width = get_theme_constant(SNAME("minimum_character_width")); + theme_cache.selection_color = get_theme_color(SNAME("selection_color")); + + theme_cache.clear_icon = get_theme_icon(SNAME("clear")); + theme_cache.clear_button_color = get_theme_color(SNAME("clear_button_color")); + theme_cache.clear_button_color_pressed = get_theme_color(SNAME("clear_button_color_pressed")); + + theme_cache.base_scale = get_theme_default_base_scale(); +} + void LineEdit::_notification(int p_what) { switch (p_what) { #ifdef TOOLS_ENABLED case NOTIFICATION_ENTER_TREE: { if (Engine::get_singleton()->is_editor_hint() && !get_tree()->is_node_being_edited(this)) { set_caret_blink_enabled(EDITOR_GET("text_editor/appearance/caret/caret_blink")); - set_caret_blink_speed(EDITOR_GET("text_editor/appearance/caret/caret_blink_speed")); + set_caret_blink_interval(EDITOR_GET("text_editor/appearance/caret/caret_blink_interval")); if (!EditorSettings::get_singleton()->is_connected("settings_changed", callable_mp(this, &LineEdit::_editor_settings_changed))) { EditorSettings::get_singleton()->connect("settings_changed", callable_mp(this, &LineEdit::_editor_settings_changed)); @@ -716,39 +745,39 @@ void LineEdit::_notification(int p_what) { case NOTIFICATION_RESIZED: { _fit_to_width(); - scroll_offset = 0; + scroll_offset = 0.0; set_caret_column(get_caret_column()); } break; case NOTIFICATION_LAYOUT_DIRECTION_CHANGED: case NOTIFICATION_THEME_CHANGED: { _shape(); - update(); + queue_redraw(); } break; case NOTIFICATION_TRANSLATION_CHANGED: { placeholder_translated = atr(placeholder); _shape(); - update(); + queue_redraw(); } break; case NOTIFICATION_WM_WINDOW_FOCUS_IN: { window_has_focus = true; draw_caret = true; - update(); + queue_redraw(); } break; case NOTIFICATION_WM_WINDOW_FOCUS_OUT: { window_has_focus = false; draw_caret = false; - update(); + queue_redraw(); } break; case NOTIFICATION_INTERNAL_PROCESS: { if (caret_blinking) { caret_blink_timer += get_process_delta_time(); - if (caret_blink_timer >= caret_blink_speed) { + if (caret_blink_timer >= caret_blink_interval) { caret_blink_timer = 0.0; _toggle_draw_caret(); } @@ -769,19 +798,19 @@ void LineEdit::_notification(int p_what) { RID ci = get_canvas_item(); - Ref<StyleBox> style = get_theme_stylebox(SNAME("normal")); + Ref<StyleBox> style = theme_cache.normal; if (!is_editable()) { - style = get_theme_stylebox(SNAME("read_only")); + style = theme_cache.read_only; draw_caret = false; } - Ref<Font> font = get_theme_font(SNAME("font")); + Ref<Font> font = theme_cache.font; if (!flat) { style->draw(ci, Rect2(Point2(), size)); } if (has_focus()) { - get_theme_stylebox(SNAME("focus"))->draw(ci, Rect2(Point2(), size)); + theme_cache.focus->draw(ci, Rect2(Point2(), size)); } int x_ofs = 0; @@ -799,7 +828,7 @@ void LineEdit::_notification(int p_what) { } } break; case HORIZONTAL_ALIGNMENT_CENTER: { - if (scroll_offset != 0) { + if (!Math::is_zero_approx(scroll_offset)) { x_ofs = style->get_offset().x; } else { x_ofs = MAX(style->get_margin(SIDE_LEFT), int(size.width - (text_width)) / 2); @@ -819,32 +848,37 @@ void LineEdit::_notification(int p_what) { int y_area = height - style->get_minimum_size().height; int y_ofs = style->get_offset().y + (y_area - text_height) / 2; - Color selection_color = get_theme_color(SNAME("selection_color")); - Color font_color = get_theme_color(is_editable() ? SNAME("font_color") : SNAME("font_uneditable_color")); - Color font_selected_color = get_theme_color(SNAME("font_selected_color")); - Color caret_color = get_theme_color(SNAME("caret_color")); + Color selection_color = theme_cache.selection_color; + Color font_color; + if (is_editable()) { + font_color = theme_cache.font_color; + } else { + font_color = theme_cache.font_uneditable_color; + } + Color font_selected_color = theme_cache.font_selected_color; + Color caret_color = theme_cache.caret_color; // Draw placeholder color. if (using_placeholder) { - font_color = get_theme_color(SNAME("font_placeholder_color")); + font_color = theme_cache.font_placeholder_color; } 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; + Ref<Texture2D> r_icon = display_clear_icon ? theme_cache.clear_icon : right_icon; Color color_icon(1, 1, 1, !is_editable() ? .5 * .9 : .9); if (display_clear_icon) { if (clear_button_status.press_attempt && clear_button_status.pressing_inside) { - color_icon = get_theme_color(SNAME("clear_button_color_pressed")); + color_icon = theme_cache.clear_button_color_pressed; } else { - color_icon = get_theme_color(SNAME("clear_button_color")); + color_icon = theme_cache.clear_button_color; } } r_icon->draw(ci, Point2(width - r_icon->get_width() - style->get_margin(SIDE_RIGHT), height / 2 - r_icon->get_height() / 2), color_icon); if (alignment == HORIZONTAL_ALIGNMENT_CENTER) { - if (scroll_offset == 0) { + if (Math::is_zero_approx(scroll_offset)) { x_ofs = MAX(style->get_margin(SIDE_LEFT), int(size.width - text_width - r_icon->get_width() - style->get_margin(SIDE_RIGHT) * 2) / 2); } } else { @@ -877,8 +911,8 @@ void LineEdit::_notification(int p_what) { // Draw text. ofs.y += TS->shaped_text_get_ascent(text_rid); - Color font_outline_color = get_theme_color(SNAME("font_outline_color")); - int outline_size = get_theme_constant(SNAME("outline_size")); + Color font_outline_color = theme_cache.font_outline_color; + int outline_size = theme_cache.font_outline_size; if (outline_size > 0 && font_outline_color.a > 0) { Vector2 oofs = ofs; for (int i = 0; i < gl_size; i++) { @@ -916,7 +950,7 @@ void LineEdit::_notification(int p_what) { ofs.x = x_ofs + scroll_offset; if (draw_caret || drag_caret_force_displayed) { // Prevent carets from disappearing at theme scales below 1.0 (if the caret width is 1). - const int caret_width = get_theme_constant(SNAME("caret_width")) * MAX(1, get_theme_default_base_scale()); + const int caret_width = theme_cache.caret_width * MAX(1, theme_cache.base_scale); if (ime_text.length() == 0) { // Normal caret. @@ -924,7 +958,7 @@ void LineEdit::_notification(int p_what) { if (caret.l_caret == Rect2() && caret.t_caret == Rect2()) { // No carets, add one at the start. - int h = get_theme_font(SNAME("font"))->get_height(get_theme_font_size(SNAME("font_size"))); + int h = theme_cache.font->get_height(theme_cache.font_size); int y = style->get_offset().y + (y_area - h) / 2; if (rtl) { caret.l_dir = TextServer::DIRECTION_RTL; @@ -1050,7 +1084,7 @@ void LineEdit::_notification(int p_what) { _shape(); set_caret_column(caret_column); // Update scroll_offset - update(); + queue_redraw(); } } break; @@ -1191,7 +1225,7 @@ void LineEdit::shift_selection_check_post(bool p_shift) { } void LineEdit::set_caret_at_pixel_pos(int p_x) { - Ref<StyleBox> style = get_theme_stylebox(SNAME("normal")); + Ref<StyleBox> style = theme_cache.normal; bool rtl = is_layout_rtl(); int x_ofs = 0; @@ -1206,7 +1240,7 @@ void LineEdit::set_caret_at_pixel_pos(int p_x) { } } break; case HORIZONTAL_ALIGNMENT_CENTER: { - if (scroll_offset != 0) { + if (!Math::is_zero_approx(scroll_offset)) { x_ofs = style->get_offset().x; } else { x_ofs = MAX(style->get_margin(SIDE_LEFT), int(get_size().width - (text_width)) / 2); @@ -1224,9 +1258,9 @@ void LineEdit::set_caret_at_pixel_pos(int p_x) { 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; + Ref<Texture2D> r_icon = display_clear_icon ? theme_cache.clear_icon : right_icon; if (alignment == HORIZONTAL_ALIGNMENT_CENTER) { - if (scroll_offset == 0) { + if (Math::is_zero_approx(scroll_offset)) { x_ofs = MAX(style->get_margin(SIDE_LEFT), int(get_size().width - text_width - r_icon->get_width() - style->get_margin(SIDE_RIGHT) * 2) / 2); } } else { @@ -1234,12 +1268,12 @@ void LineEdit::set_caret_at_pixel_pos(int p_x) { } } - int ofs = TS->shaped_text_hit_test_position(text_rid, p_x - x_ofs - scroll_offset); + int ofs = ceil(TS->shaped_text_hit_test_position(text_rid, p_x - x_ofs - scroll_offset)); set_caret_column(ofs); } -Vector2i LineEdit::get_caret_pixel_pos() { - Ref<StyleBox> style = get_theme_stylebox(SNAME("normal")); +Vector2 LineEdit::get_caret_pixel_pos() { + Ref<StyleBox> style = theme_cache.normal; bool rtl = is_layout_rtl(); int x_ofs = 0; @@ -1254,7 +1288,7 @@ Vector2i LineEdit::get_caret_pixel_pos() { } } break; case HORIZONTAL_ALIGNMENT_CENTER: { - if (scroll_offset != 0) { + if (!Math::is_zero_approx(scroll_offset)) { x_ofs = style->get_offset().x; } else { x_ofs = MAX(style->get_margin(SIDE_LEFT), int(get_size().width - (text_width)) / 2); @@ -1272,9 +1306,9 @@ Vector2i LineEdit::get_caret_pixel_pos() { 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; + Ref<Texture2D> r_icon = display_clear_icon ? theme_cache.clear_icon : right_icon; if (alignment == HORIZONTAL_ALIGNMENT_CENTER) { - if (scroll_offset == 0) { + if (Math::is_zero_approx(scroll_offset)) { x_ofs = MAX(style->get_margin(SIDE_LEFT), int(get_size().width - text_width - r_icon->get_width() - style->get_margin(SIDE_RIGHT) * 2) / 2); } } else { @@ -1282,7 +1316,7 @@ Vector2i LineEdit::get_caret_pixel_pos() { } } - Vector2i ret; + Vector2 ret; CaretInfo caret; // Get position of the start of caret. if (ime_text.length() != 0 && ime_selection.x != 0) { @@ -1355,16 +1389,16 @@ bool LineEdit::is_caret_force_displayed() const { void LineEdit::set_caret_force_displayed(const bool p_enabled) { caret_force_displayed = p_enabled; set_caret_blink_enabled(caret_blink_enabled); - update(); + queue_redraw(); } -float LineEdit::get_caret_blink_speed() const { - return caret_blink_speed; +float LineEdit::get_caret_blink_interval() const { + return caret_blink_interval; } -void LineEdit::set_caret_blink_speed(const float p_speed) { - ERR_FAIL_COND(p_speed <= 0); - caret_blink_speed = p_speed; +void LineEdit::set_caret_blink_interval(const float p_interval) { + ERR_FAIL_COND(p_interval <= 0); + caret_blink_interval = p_interval; } void LineEdit::_reset_caret_blink_timer() { @@ -1372,7 +1406,7 @@ void LineEdit::_reset_caret_blink_timer() { draw_caret = true; if (has_focus()) { caret_blink_timer = 0.0; - update(); + queue_redraw(); } } } @@ -1380,7 +1414,7 @@ void LineEdit::_reset_caret_blink_timer() { void LineEdit::_toggle_draw_caret() { draw_caret = !draw_caret; if (is_visible_in_tree() && ((has_focus() && window_has_focus) || caret_force_displayed)) { - update(); + queue_redraw(); } } @@ -1423,9 +1457,9 @@ void LineEdit::set_text(String p_text) { insert_text_at_caret(p_text); _create_undo_state(); - update(); + queue_redraw(); caret_column = 0; - scroll_offset = 0; + scroll_offset = 0.0; } void LineEdit::set_text_direction(Control::TextDirection p_text_direction) { @@ -1443,7 +1477,7 @@ void LineEdit::set_text_direction(Control::TextDirection p_text_direction) { menu_dir->set_item_checked(menu_dir->get_item_index(MENU_DIR_LTR), text_direction == TEXT_DIRECTION_LTR); menu_dir->set_item_checked(menu_dir->get_item_index(MENU_DIR_RTL), text_direction == TEXT_DIRECTION_RTL); } - update(); + queue_redraw(); } } @@ -1455,7 +1489,7 @@ void LineEdit::set_language(const String &p_language) { if (language != p_language) { language = p_language; _shape(); - update(); + queue_redraw(); } } @@ -1470,7 +1504,7 @@ void LineEdit::set_draw_control_chars(bool p_draw_control_chars) { menu->set_item_checked(menu->get_item_index(MENU_DISPLAY_UCC), draw_control_chars); } _shape(); - update(); + queue_redraw(); } } @@ -1482,7 +1516,7 @@ void LineEdit::set_structured_text_bidi_override(TextServer::StructuredTextParse if (st_parser != p_parser) { st_parser = p_parser; _shape(); - update(); + queue_redraw(); } } @@ -1493,7 +1527,7 @@ TextServer::StructuredTextParser LineEdit::get_structured_text_bidi_override() c void LineEdit::set_structured_text_bidi_override_options(Array p_args) { st_args = p_args; _shape(); - update(); + queue_redraw(); } Array LineEdit::get_structured_text_bidi_override_options() const { @@ -1525,10 +1559,14 @@ String LineEdit::get_text() const { } void LineEdit::set_placeholder(String p_text) { + if (placeholder == p_text) { + return; + } + placeholder = p_text; placeholder_translated = atr(placeholder); _shape(); - update(); + queue_redraw(); } String LineEdit::get_placeholder() const { @@ -1549,11 +1587,11 @@ void LineEdit::set_caret_column(int p_column) { // Fit to window. if (!is_inside_tree()) { - scroll_offset = 0; + scroll_offset = 0.0; return; } - Ref<StyleBox> style = get_theme_stylebox(SNAME("normal")); + Ref<StyleBox> style = theme_cache.normal; bool rtl = is_layout_rtl(); int x_ofs = 0; @@ -1568,7 +1606,7 @@ void LineEdit::set_caret_column(int p_column) { } } break; case HORIZONTAL_ALIGNMENT_CENTER: { - if (scroll_offset != 0) { + if (!Math::is_zero_approx(scroll_offset)) { x_ofs = style->get_offset().x; } else { x_ofs = MAX(style->get_margin(SIDE_LEFT), int(get_size().width - (text_width)) / 2); @@ -1587,9 +1625,9 @@ void LineEdit::set_caret_column(int p_column) { 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; + Ref<Texture2D> r_icon = display_clear_icon ? theme_cache.clear_icon : right_icon; if (alignment == HORIZONTAL_ALIGNMENT_CENTER) { - if (scroll_offset == 0) { + if (Math::is_zero_approx(scroll_offset)) { x_ofs = MAX(style->get_margin(SIDE_LEFT), int(get_size().width - text_width - r_icon->get_width() - style->get_margin(SIDE_RIGHT) * 2) / 2); } } else { @@ -1599,30 +1637,30 @@ void LineEdit::set_caret_column(int p_column) { } // Note: Use two coordinates to fit IME input range. - Vector2i primary_catret_offset = get_caret_pixel_pos(); + Vector2 primary_caret_offset = get_caret_pixel_pos(); - if (MIN(primary_catret_offset.x, primary_catret_offset.y) <= x_ofs) { - scroll_offset += (x_ofs - MIN(primary_catret_offset.x, primary_catret_offset.y)); - } else if (MAX(primary_catret_offset.x, primary_catret_offset.y) >= ofs_max) { - scroll_offset += (ofs_max - MAX(primary_catret_offset.x, primary_catret_offset.y)); + if (MIN(primary_caret_offset.x, primary_caret_offset.y) <= x_ofs) { + scroll_offset += x_ofs - MIN(primary_caret_offset.x, primary_caret_offset.y); + } else if (MAX(primary_caret_offset.x, primary_caret_offset.y) >= ofs_max) { + scroll_offset += ofs_max - MAX(primary_caret_offset.x, primary_caret_offset.y); } scroll_offset = MIN(0, scroll_offset); - update(); + queue_redraw(); } int LineEdit::get_caret_column() const { return caret_column; } -void LineEdit::set_scroll_offset(int p_pos) { +void LineEdit::set_scroll_offset(float p_pos) { scroll_offset = p_pos; - if (scroll_offset < 0) { - scroll_offset = 0; + if (scroll_offset < 0.0) { + scroll_offset = 0.0; } } -int LineEdit::get_scroll_offset() const { +float LineEdit::get_scroll_offset() const { return scroll_offset; } @@ -1650,23 +1688,23 @@ void LineEdit::clear_internal() { deselect(); _clear_undo_stack(); caret_column = 0; - scroll_offset = 0; + scroll_offset = 0.0; undo_text = ""; text = ""; _shape(); - update(); + queue_redraw(); } Size2 LineEdit::get_minimum_size() const { - Ref<StyleBox> style = get_theme_stylebox(SNAME("normal")); - Ref<Font> font = get_theme_font(SNAME("font")); - int font_size = get_theme_font_size(SNAME("font_size")); + Ref<StyleBox> style = theme_cache.normal; + Ref<Font> font = theme_cache.font; + int font_size = theme_cache.font_size; Size2 min_size; // Minimum size of text. float em_space_size = font->get_char_size('M', font_size).x; - min_size.width = get_theme_constant(SNAME("minimum_character_width")) * em_space_size; + min_size.width = theme_cache.minimum_character_width * em_space_size; if (expand_to_text_length) { // Add a space because some fonts are too exact, and because caret needs a bit more when at the end. @@ -1682,9 +1720,8 @@ Size2 LineEdit::get_minimum_size() const { 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.height = MAX(min_size.height, theme_cache.clear_icon->get_height()); + icon_max_width = MAX(icon_max_width, theme_cache.clear_icon->get_width()); } min_size.width += icon_max_width; @@ -1698,7 +1735,7 @@ void LineEdit::deselect() { selection.enabled = false; selection.creating = false; selection.double_click = false; - update(); + queue_redraw(); } bool LineEdit::has_selection() const { @@ -1762,7 +1799,7 @@ void LineEdit::select_all() { selection.begin = 0; selection.end = text.length(); selection.enabled = true; - update(); + queue_redraw(); } void LineEdit::set_editable(bool p_editable) { @@ -1773,7 +1810,7 @@ void LineEdit::set_editable(bool p_editable) { editable = p_editable; update_minimum_size(); - update(); + queue_redraw(); } bool LineEdit::is_editable() const { @@ -1781,11 +1818,13 @@ bool LineEdit::is_editable() const { } void LineEdit::set_secret(bool p_secret) { - if (pass != p_secret) { - pass = p_secret; - _shape(); + if (pass == p_secret) { + return; } - update(); + + pass = p_secret; + _shape(); + queue_redraw(); } bool LineEdit::is_secret() const { @@ -1797,11 +1836,13 @@ void LineEdit::set_secret_character(const String &p_string) { // It also wouldn't make sense to use multiple characters as the secret character. ERR_FAIL_COND_MSG(p_string.length() != 1, "Secret character must be exactly one character long (" + itos(p_string.length()) + " characters given)."); - if (secret_character != p_string) { - secret_character = p_string; - _shape(); + if (secret_character == p_string) { + return; } - update(); + + secret_character = p_string; + _shape(); + queue_redraw(); } String LineEdit::get_secret_character() const { @@ -1838,7 +1879,7 @@ void LineEdit::select(int p_from, int p_to) { selection.end = p_to; selection.creating = false; selection.double_click = false; - update(); + queue_redraw(); } bool LineEdit::is_text_field() const { @@ -1996,7 +2037,7 @@ PopupMenu *LineEdit::get_menu() const { void LineEdit::_editor_settings_changed() { #ifdef TOOLS_ENABLED set_caret_blink_enabled(EDITOR_GET("text_editor/appearance/caret/caret_blink")); - set_caret_blink_speed(EDITOR_GET("text_editor/appearance/caret/caret_blink_speed")); + set_caret_blink_interval(EDITOR_GET("text_editor/appearance/caret/caret_blink_interval")); #endif } @@ -2017,7 +2058,7 @@ void LineEdit::set_clear_button_enabled(bool p_enabled) { clear_button_enabled = p_enabled; _fit_to_width(); update_minimum_size(); - update(); + queue_redraw(); } bool LineEdit::is_clear_button_enabled() const { @@ -2057,6 +2098,10 @@ bool LineEdit::is_middle_mouse_paste_enabled() const { } void LineEdit::set_selecting_enabled(bool p_enabled) { + if (selecting_enabled == p_enabled) { + return; + } + selecting_enabled = p_enabled; if (!selecting_enabled) { @@ -2069,6 +2114,10 @@ bool LineEdit::is_selecting_enabled() const { } void LineEdit::set_deselect_on_focus_loss_enabled(const bool p_enabled) { + if (deselect_on_focus_loss_enabled == p_enabled) { + return; + } + deselect_on_focus_loss_enabled = p_enabled; if (p_enabled && selection.enabled && !has_focus()) { deselect(); @@ -2086,7 +2135,7 @@ void LineEdit::set_right_icon(const Ref<Texture2D> &p_icon) { right_icon = p_icon; _fit_to_width(); update_minimum_size(); - update(); + queue_redraw(); } Ref<Texture2D> LineEdit::get_right_icon() { @@ -2096,7 +2145,7 @@ Ref<Texture2D> LineEdit::get_right_icon() { void LineEdit::set_flat(bool p_enabled) { if (flat != p_enabled) { flat = p_enabled; - update(); + queue_redraw(); } } @@ -2137,8 +2186,8 @@ void LineEdit::_shape() { } TS->shaped_text_set_preserve_control(text_rid, draw_control_chars); - const Ref<Font> &font = get_theme_font(SNAME("font")); - int font_size = get_theme_font_size(SNAME("font_size")); + const Ref<Font> &font = theme_cache.font; + int font_size = theme_cache.font_size; ERR_FAIL_COND(font.is_null()); TS->shaped_text_add_string(text_rid, t, font->get_rids(), font_size, font->get_opentype_features(), language); for (int i = 0; i < TextServer::SPACING_MAX; i++) { @@ -2158,12 +2207,12 @@ void LineEdit::_shape() { void LineEdit::_fit_to_width() { if (alignment == HORIZONTAL_ALIGNMENT_FILL) { - Ref<StyleBox> style = get_theme_stylebox(SNAME("normal")); + Ref<StyleBox> style = theme_cache.normal; int t_width = get_size().width - style->get_margin(SIDE_RIGHT) - style->get_margin(SIDE_LEFT); 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; + Ref<Texture2D> r_icon = display_clear_icon ? theme_cache.clear_icon : right_icon; t_width -= r_icon->get_width(); } TS->shaped_text_fit_to_width(text_rid, MAX(t_width, full_width)); @@ -2224,9 +2273,9 @@ Key LineEdit::_get_menu_action_accelerator(const String &p_action) { } } -void LineEdit::_validate_property(PropertyInfo &property) const { - if (!caret_blink_enabled && property.name == "caret_blink_speed") { - property.usage = PROPERTY_USAGE_NO_EDITOR; +void LineEdit::_validate_property(PropertyInfo &p_property) const { + if (!caret_blink_enabled && p_property.name == "caret_blink_interval") { + p_property.usage = PROPERTY_USAGE_NO_EDITOR; } } @@ -2268,8 +2317,8 @@ void LineEdit::_bind_methods() { ClassDB::bind_method(D_METHOD("is_caret_mid_grapheme_enabled"), &LineEdit::is_caret_mid_grapheme_enabled); ClassDB::bind_method(D_METHOD("set_caret_force_displayed", "enabled"), &LineEdit::set_caret_force_displayed); ClassDB::bind_method(D_METHOD("is_caret_force_displayed"), &LineEdit::is_caret_force_displayed); - ClassDB::bind_method(D_METHOD("set_caret_blink_speed", "blink_speed"), &LineEdit::set_caret_blink_speed); - ClassDB::bind_method(D_METHOD("get_caret_blink_speed"), &LineEdit::get_caret_blink_speed); + ClassDB::bind_method(D_METHOD("set_caret_blink_interval", "interval"), &LineEdit::set_caret_blink_interval); + ClassDB::bind_method(D_METHOD("get_caret_blink_interval"), &LineEdit::get_caret_blink_interval); ClassDB::bind_method(D_METHOD("set_max_length", "chars"), &LineEdit::set_max_length); ClassDB::bind_method(D_METHOD("get_max_length"), &LineEdit::get_max_length); ClassDB::bind_method(D_METHOD("insert_text_at_caret", "text"), &LineEdit::insert_text_at_caret); @@ -2370,7 +2419,7 @@ void LineEdit::_bind_methods() { 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"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "caret_blink_interval", PROPERTY_HINT_RANGE, "0.1,10,0.01"), "set_caret_blink_interval", "get_caret_blink_interval"); ADD_PROPERTY(PropertyInfo(Variant::INT, "caret_column", PROPERTY_HINT_RANGE, "0,1000,1,or_greater"), "set_caret_column", "get_caret_column"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "caret_force_displayed"), "set_caret_force_displayed", "is_caret_force_displayed"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "caret_mid_grapheme"), "set_caret_mid_grapheme_enabled", "is_caret_mid_grapheme_enabled"); diff --git a/scene/gui/line_edit.h b/scene/gui/line_edit.h index a828479b0c..a4d5205f81 100644 --- a/scene/gui/line_edit.h +++ b/scene/gui/line_edit.h @@ -95,7 +95,7 @@ private: String text; String placeholder; String placeholder_translated; - String secret_character = "*"; + String secret_character = U"•"; String ime_text; Point2 ime_selection; @@ -113,7 +113,7 @@ private: bool caret_mid_grapheme_enabled = true; int caret_column = 0; - int scroll_offset = 0; + float scroll_offset = 0.0; int max_length = 0; // 0 for no maximum. String language; @@ -153,8 +153,7 @@ private: struct TextOperation { int caret_column = 0; - int scroll_offset = 0; - int cached_width = 0; + float scroll_offset = 0.0; String text; }; List<TextOperation> undo_stack; @@ -171,10 +170,35 @@ private: bool caret_blink_enabled = false; bool caret_force_displayed = false; bool draw_caret = true; - float caret_blink_speed = 0.65; + float caret_blink_interval = 0.65; double caret_blink_timer = 0.0; bool caret_blinking = false; + struct ThemeCache { + Ref<StyleBox> normal; + Ref<StyleBox> read_only; + Ref<StyleBox> focus; + + Ref<Font> font; + int font_size = 0; + Color font_color; + Color font_uneditable_color; + Color font_selected_color; + int font_outline_size; + Color font_outline_color; + Color font_placeholder_color; + int caret_width = 0; + Color caret_color; + int minimum_character_width = 0; + Color selection_color; + + Ref<Texture2D> clear_icon; + Color clear_button_color; + Color clear_button_color_pressed; + + float base_scale = 1.0; + } theme_cache; + bool _is_over_clear_button(const Point2 &p_pos) const; void _clear_undo_stack(); @@ -192,11 +216,11 @@ private: void shift_selection_check_post(bool); void selection_fill_at_caret(); - void set_scroll_offset(int p_pos); - int get_scroll_offset() const; + void set_scroll_offset(float p_pos); + float get_scroll_offset() const; void set_caret_at_pixel_pos(int p_x); - Vector2i get_caret_pixel_pos(); + Vector2 get_caret_pixel_pos(); void _reset_caret_blink_timer(); void _toggle_draw_caret(); @@ -216,12 +240,13 @@ private: void _ensure_menu(); protected: + virtual void _update_theme_item_cache() override; void _notification(int p_what); static void _bind_methods(); virtual void unhandled_key_input(const Ref<InputEvent> &p_event) override; virtual void gui_input(const Ref<InputEvent> &p_event) override; - void _validate_property(PropertyInfo &property) const override; + void _validate_property(PropertyInfo &p_property) const; public: void set_horizontal_alignment(HorizontalAlignment p_alignment); @@ -286,8 +311,8 @@ public: bool is_caret_blink_enabled() const; void set_caret_blink_enabled(const bool p_enabled); - float get_caret_blink_speed() const; - void set_caret_blink_speed(const float p_speed); + float get_caret_blink_interval() const; + void set_caret_blink_interval(const float p_interval); void set_caret_force_displayed(const bool p_enabled); bool is_caret_force_displayed() const; diff --git a/scene/gui/link_button.cpp b/scene/gui/link_button.cpp index 30c0bb3321..7219e86f52 100644 --- a/scene/gui/link_button.cpp +++ b/scene/gui/link_button.cpp @@ -33,8 +33,8 @@ #include "core/string/translation.h" void LinkButton::_shape() { - Ref<Font> font = get_theme_font(SNAME("font")); - int font_size = get_theme_font_size(SNAME("font_size")); + Ref<Font> font = theme_cache.font; + int font_size = theme_cache.font_size; text_buf->clear(); if (text_direction == Control::TEXT_DIRECTION_INHERITED) { @@ -54,7 +54,7 @@ void LinkButton::set_text(const String &p_text) { xl_text = atr(text); _shape(); update_minimum_size(); - update(); + queue_redraw(); } String LinkButton::get_text() const { @@ -65,7 +65,7 @@ void LinkButton::set_structured_text_bidi_override(TextServer::StructuredTextPar if (st_parser != p_parser) { st_parser = p_parser; _shape(); - update(); + queue_redraw(); } } @@ -76,7 +76,7 @@ TextServer::StructuredTextParser LinkButton::get_structured_text_bidi_override() void LinkButton::set_structured_text_bidi_override_options(Array p_args) { st_args = p_args; _shape(); - update(); + queue_redraw(); } Array LinkButton::get_structured_text_bidi_override_options() const { @@ -88,7 +88,7 @@ void LinkButton::set_text_direction(Control::TextDirection p_text_direction) { if (text_direction != p_text_direction) { text_direction = p_text_direction; _shape(); - update(); + queue_redraw(); } } @@ -100,7 +100,7 @@ void LinkButton::set_language(const String &p_language) { if (language != p_language) { language = p_language; _shape(); - update(); + queue_redraw(); } } @@ -109,8 +109,12 @@ String LinkButton::get_language() const { } void LinkButton::set_underline_mode(UnderlineMode p_underline_mode) { + if (underline_mode == p_underline_mode) { + return; + } + underline_mode = p_underline_mode; - update(); + queue_redraw(); } LinkButton::UnderlineMode LinkButton::get_underline_mode() const { @@ -121,23 +125,43 @@ Size2 LinkButton::get_minimum_size() const { return text_buf->get_size(); } +void LinkButton::_update_theme_item_cache() { + BaseButton::_update_theme_item_cache(); + + theme_cache.focus = get_theme_stylebox(SNAME("focus")); + + theme_cache.font_color = get_theme_color(SNAME("font_color")); + theme_cache.font_focus_color = get_theme_color(SNAME("font_focus_color")); + theme_cache.font_pressed_color = get_theme_color(SNAME("font_pressed_color")); + theme_cache.font_hover_color = get_theme_color(SNAME("font_hover_color")); + theme_cache.font_hover_pressed_color = get_theme_color(SNAME("font_hover_pressed_color")); + theme_cache.font_disabled_color = get_theme_color(SNAME("font_disabled_color")); + + theme_cache.font = get_theme_font(SNAME("font")); + theme_cache.font_size = get_theme_font_size(SNAME("font_size")); + theme_cache.outline_size = get_theme_constant(SNAME("outline_size")); + theme_cache.font_outline_color = get_theme_color(SNAME("font_outline_color")); + + theme_cache.underline_spacing = get_theme_constant(SNAME("underline_spacing")); +} + void LinkButton::_notification(int p_what) { switch (p_what) { case NOTIFICATION_TRANSLATION_CHANGED: { xl_text = atr(text); _shape(); update_minimum_size(); - update(); + queue_redraw(); } break; case NOTIFICATION_LAYOUT_DIRECTION_CHANGED: { - update(); + queue_redraw(); } break; case NOTIFICATION_THEME_CHANGED: { _shape(); update_minimum_size(); - update(); + queue_redraw(); } break; case NOTIFICATION_DRAW: { @@ -149,9 +173,9 @@ void LinkButton::_notification(int p_what) { switch (get_draw_mode()) { case DRAW_NORMAL: { if (has_focus()) { - color = get_theme_color(SNAME("font_focus_color")); + color = theme_cache.font_focus_color; } else { - color = get_theme_color(SNAME("font_color")); + color = theme_cache.font_color; } do_underline = underline_mode == UNDERLINE_MODE_ALWAYS; @@ -159,35 +183,35 @@ void LinkButton::_notification(int p_what) { case DRAW_HOVER_PRESSED: case DRAW_PRESSED: { if (has_theme_color(SNAME("font_pressed_color"))) { - color = get_theme_color(SNAME("font_pressed_color")); + color = theme_cache.font_pressed_color; } else { - color = get_theme_color(SNAME("font_color")); + color = theme_cache.font_color; } do_underline = underline_mode != UNDERLINE_MODE_NEVER; } break; case DRAW_HOVER: { - color = get_theme_color(SNAME("font_hover_color")); + color = theme_cache.font_hover_color; do_underline = underline_mode != UNDERLINE_MODE_NEVER; } break; case DRAW_DISABLED: { - color = get_theme_color(SNAME("font_disabled_color")); + color = theme_cache.font_disabled_color; do_underline = underline_mode == UNDERLINE_MODE_ALWAYS; } break; } if (has_focus()) { - Ref<StyleBox> style = get_theme_stylebox(SNAME("focus")); + Ref<StyleBox> style = theme_cache.focus; style->draw(ci, Rect2(Point2(), size)); } int width = text_buf->get_line_width(); - Color font_outline_color = get_theme_color(SNAME("font_outline_color")); - int outline_size = get_theme_constant(SNAME("outline_size")); + Color font_outline_color = theme_cache.font_outline_color; + int outline_size = theme_cache.outline_size; if (is_layout_rtl()) { if (outline_size > 0 && font_outline_color.a > 0) { text_buf->draw_outline(get_canvas_item(), Vector2(size.width - width, 0), outline_size, font_outline_color); @@ -201,7 +225,7 @@ void LinkButton::_notification(int p_what) { } if (do_underline) { - int underline_spacing = get_theme_constant(SNAME("underline_spacing")) + text_buf->get_line_underline_position(); + int underline_spacing = theme_cache.underline_spacing + text_buf->get_line_underline_position(); int y = text_buf->get_line_ascent() + underline_spacing; if (is_layout_rtl()) { diff --git a/scene/gui/link_button.h b/scene/gui/link_button.h index 12a6a7618f..accd848163 100644 --- a/scene/gui/link_button.h +++ b/scene/gui/link_button.h @@ -55,10 +55,29 @@ private: TextServer::StructuredTextParser st_parser = TextServer::STRUCTURED_TEXT_DEFAULT; Array st_args; + struct ThemeCache { + Ref<StyleBox> focus; + + Color font_color; + Color font_focus_color; + Color font_pressed_color; + Color font_hover_color; + Color font_hover_pressed_color; + Color font_disabled_color; + + Ref<Font> font; + int font_size = 0; + int outline_size = 0; + Color font_outline_color; + + int underline_spacing = 0; + } theme_cache; + void _shape(); protected: virtual Size2 get_minimum_size() const override; + virtual void _update_theme_item_cache() override; void _notification(int p_what); static void _bind_methods(); diff --git a/scene/gui/margin_container.cpp b/scene/gui/margin_container.cpp index fac37a8634..60fe681824 100644 --- a/scene/gui/margin_container.cpp +++ b/scene/gui/margin_container.cpp @@ -30,12 +30,16 @@ #include "margin_container.h" -Size2 MarginContainer::get_minimum_size() const { - int margin_left = get_theme_constant(SNAME("margin_left")); - int margin_top = get_theme_constant(SNAME("margin_top")); - int margin_right = get_theme_constant(SNAME("margin_right")); - int margin_bottom = get_theme_constant(SNAME("margin_bottom")); +void MarginContainer::_update_theme_item_cache() { + Container::_update_theme_item_cache(); + + theme_cache.margin_left = get_theme_constant(SNAME("margin_left")); + theme_cache.margin_top = get_theme_constant(SNAME("margin_top")); + theme_cache.margin_right = get_theme_constant(SNAME("margin_right")); + theme_cache.margin_bottom = get_theme_constant(SNAME("margin_bottom")); +} +Size2 MarginContainer::get_minimum_size() const { Size2 max; for (int i = 0; i < get_child_count(); i++) { @@ -59,8 +63,8 @@ Size2 MarginContainer::get_minimum_size() const { } } - max.width += (margin_left + margin_right); - max.height += (margin_top + margin_bottom); + max.width += (theme_cache.margin_left + theme_cache.margin_right); + max.height += (theme_cache.margin_top + theme_cache.margin_bottom); return max; } @@ -86,11 +90,6 @@ Vector<int> MarginContainer::get_allowed_size_flags_vertical() const { void MarginContainer::_notification(int p_what) { switch (p_what) { case NOTIFICATION_SORT_CHILDREN: { - int margin_left = get_theme_constant(SNAME("margin_left")); - int margin_top = get_theme_constant(SNAME("margin_top")); - int margin_right = get_theme_constant(SNAME("margin_right")); - int margin_bottom = get_theme_constant(SNAME("margin_bottom")); - Size2 s = get_size(); for (int i = 0; i < get_child_count(); i++) { @@ -102,9 +101,9 @@ void MarginContainer::_notification(int p_what) { continue; } - int w = s.width - margin_left - margin_right; - int h = s.height - margin_top - margin_bottom; - fit_child_in_rect(c, Rect2(margin_left, margin_top, w, h)); + int w = s.width - theme_cache.margin_left - theme_cache.margin_right; + int h = s.height - theme_cache.margin_top - theme_cache.margin_bottom; + fit_child_in_rect(c, Rect2(theme_cache.margin_left, theme_cache.margin_top, w, h)); } } break; diff --git a/scene/gui/margin_container.h b/scene/gui/margin_container.h index f8a3c5bb11..5c33785170 100644 --- a/scene/gui/margin_container.h +++ b/scene/gui/margin_container.h @@ -36,7 +36,16 @@ class MarginContainer : public Container { GDCLASS(MarginContainer, Container); + struct ThemeCache { + int margin_left = 0; + int margin_top = 0; + int margin_right = 0; + int margin_bottom = 0; + } theme_cache; + protected: + virtual void _update_theme_item_cache() override; + void _notification(int p_what); public: diff --git a/scene/gui/menu_bar.cpp b/scene/gui/menu_bar.cpp new file mode 100644 index 0000000000..d6bf84ea5a --- /dev/null +++ b/scene/gui/menu_bar.cpp @@ -0,0 +1,906 @@ +/*************************************************************************/ +/* menu_bar.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#include "menu_bar.h" + +#include "core/os/keyboard.h" +#include "scene/main/window.h" + +void MenuBar::gui_input(const Ref<InputEvent> &p_event) { + ERR_FAIL_COND(p_event.is_null()); + if (is_native_menu()) { + // Handled by OS. + return; + } + + MutexLock lock(mutex); + if (p_event->is_action("ui_left") && p_event->is_pressed()) { + int new_sel = selected_menu; + int old_sel = (selected_menu < 0) ? 0 : selected_menu; + do { + new_sel--; + if (new_sel < 0) { + new_sel = menu_cache.size() - 1; + } + if (old_sel == new_sel) { + return; + } + } while (menu_cache[new_sel].hidden || menu_cache[new_sel].disabled); + + if (selected_menu != new_sel) { + selected_menu = new_sel; + focused_menu = selected_menu; + if (active_menu >= 0) { + get_menu_popup(active_menu)->hide(); + } + _open_popup(selected_menu, true); + } + return; + } else if (p_event->is_action("ui_right") && p_event->is_pressed()) { + int new_sel = selected_menu; + int old_sel = (selected_menu < 0) ? menu_cache.size() - 1 : selected_menu; + do { + new_sel++; + if (new_sel >= menu_cache.size()) { + new_sel = 0; + } + if (old_sel == new_sel) { + return; + } + } while (menu_cache[new_sel].hidden || menu_cache[new_sel].disabled); + + if (selected_menu != new_sel) { + selected_menu = new_sel; + focused_menu = selected_menu; + if (active_menu >= 0) { + get_menu_popup(active_menu)->hide(); + } + _open_popup(selected_menu, true); + } + return; + } + + Ref<InputEventMouseMotion> mm = p_event; + if (mm.is_valid()) { + int old_sel = selected_menu; + focused_menu = _get_index_at_point(mm->get_position()); + if (focused_menu >= 0) { + selected_menu = focused_menu; + } + if (selected_menu != old_sel) { + queue_redraw(); + } + } + + Ref<InputEventMouseButton> mb = p_event; + if (mb.is_valid()) { + if (mb->is_pressed() && (mb->get_button_index() == MouseButton::LEFT || mb->get_button_index() == MouseButton::RIGHT)) { + int index = _get_index_at_point(mb->get_position()); + if (index >= 0) { + _open_popup(index); + } + } + } +} + +void MenuBar::_open_popup(int p_index, bool p_focus_item) { + ERR_FAIL_INDEX(p_index, menu_cache.size()); + + PopupMenu *pm = get_menu_popup(p_index); + if (pm->is_visible()) { + pm->hide(); + return; + } + + Rect2 item_rect = _get_menu_item_rect(p_index); + Point2 screen_pos = get_screen_position() + item_rect.position * get_viewport()->get_canvas_transform().get_scale(); + Size2 screen_size = item_rect.size * get_viewport()->get_canvas_transform().get_scale(); + + active_menu = p_index; + + pm->set_size(Size2(screen_size.x, 0)); + screen_pos.y += screen_size.y; + if (is_layout_rtl()) { + screen_pos.x += screen_size.x - pm->get_size().width; + } + pm->set_position(screen_pos); + pm->set_parent_rect(Rect2(Point2(screen_pos - pm->get_position()), Size2(screen_size.x, screen_pos.y))); + pm->popup(); + + if (p_focus_item) { + for (int i = 0; i < pm->get_item_count(); i++) { + if (!pm->is_item_disabled(i)) { + pm->set_focused_item(i); + break; + } + } + } + + queue_redraw(); +} + +void MenuBar::shortcut_input(const Ref<InputEvent> &p_event) { + ERR_FAIL_COND(p_event.is_null()); + + if (!_is_focus_owner_in_shortcut_context()) { + return; + } + + if (disable_shortcuts) { + return; + } + + if (p_event->is_pressed() && !p_event->is_echo() && (Object::cast_to<InputEventKey>(p_event.ptr()) || Object::cast_to<InputEventJoypadButton>(p_event.ptr()) || Object::cast_to<InputEventAction>(*p_event) || Object::cast_to<InputEventShortcut>(*p_event))) { + if (!get_parent() || !is_visible_in_tree()) { + return; + } + + Vector<PopupMenu *> popups = _get_popups(); + for (int i = 0; i < popups.size(); i++) { + if (menu_cache[i].hidden || menu_cache[i].disabled) { + continue; + } + if (popups[i]->activate_item_by_event(p_event, false)) { + accept_event(); + return; + } + } + } +} + +void MenuBar::set_shortcut_context(Node *p_node) { + if (p_node != nullptr) { + shortcut_context = p_node->get_instance_id(); + } else { + shortcut_context = ObjectID(); + } +} + +Node *MenuBar::get_shortcut_context() const { + Object *ctx_obj = ObjectDB::get_instance(shortcut_context); + Node *ctx_node = Object::cast_to<Node>(ctx_obj); + + return ctx_node; +} + +bool MenuBar::_is_focus_owner_in_shortcut_context() const { + if (shortcut_context == ObjectID()) { + // No context, therefore global - always "in" context. + return true; + } + + Node *ctx_node = get_shortcut_context(); + Control *vp_focus = get_viewport() ? get_viewport()->gui_get_focus_owner() : nullptr; + + // If the context is valid and the viewport focus is valid, check if the context is the focus or is a parent of it. + return ctx_node && vp_focus && (ctx_node == vp_focus || ctx_node->is_ancestor_of(vp_focus)); +} + +void MenuBar::_popup_visibility_changed(bool p_visible) { + if (!p_visible) { + active_menu = -1; + focused_menu = -1; + set_process_internal(false); + queue_redraw(); + return; + } + + if (switch_on_hover) { + Window *window = Object::cast_to<Window>(get_viewport()); + if (window) { + mouse_pos_adjusted = window->get_position(); + + if (window->is_embedded()) { + Window *window_parent = Object::cast_to<Window>(window->get_parent()->get_viewport()); + while (window_parent) { + if (!window_parent->is_embedded()) { + mouse_pos_adjusted += window_parent->get_position(); + break; + } + + window_parent = Object::cast_to<Window>(window_parent->get_parent()->get_viewport()); + } + } + + set_process_internal(true); + } + } +} + +void MenuBar::_update_submenu(const String &p_menu_name, PopupMenu *p_child) { + int count = p_child->get_item_count(); + global_menus.insert(p_menu_name); + for (int i = 0; i < count; i++) { + if (p_child->is_item_separator(i)) { + DisplayServer::get_singleton()->global_menu_add_separator(p_menu_name); + } else if (!p_child->get_item_submenu(i).is_empty()) { + Node *n = p_child->get_node(p_child->get_item_submenu(i)); + ERR_FAIL_COND_MSG(!n, "Item subnode does not exist: " + p_child->get_item_submenu(i) + "."); + PopupMenu *pm = Object::cast_to<PopupMenu>(n); + ERR_FAIL_COND_MSG(!pm, "Item subnode is not a PopupMenu: " + p_child->get_item_submenu(i) + "."); + + DisplayServer::get_singleton()->global_menu_add_submenu_item(p_menu_name, p_child->get_item_text(i), p_menu_name + "/" + itos(i)); + _update_submenu(p_menu_name + "/" + itos(i), pm); + } else { + int index = DisplayServer::get_singleton()->global_menu_add_item(p_menu_name, p_child->get_item_text(i), callable_mp(p_child, &PopupMenu::activate_item), Callable(), i); + + if (p_child->is_item_checkable(i)) { + DisplayServer::get_singleton()->global_menu_set_item_checkable(p_menu_name, index, true); + } + if (p_child->is_item_radio_checkable(i)) { + DisplayServer::get_singleton()->global_menu_set_item_radio_checkable(p_menu_name, index, true); + } + DisplayServer::get_singleton()->global_menu_set_item_checked(p_menu_name, index, p_child->is_item_checked(i)); + DisplayServer::get_singleton()->global_menu_set_item_disabled(p_menu_name, index, p_child->is_item_disabled(i)); + DisplayServer::get_singleton()->global_menu_set_item_max_states(p_menu_name, index, p_child->get_item_max_states(i)); + DisplayServer::get_singleton()->global_menu_set_item_icon(p_menu_name, index, p_child->get_item_icon(i)); + DisplayServer::get_singleton()->global_menu_set_item_state(p_menu_name, index, p_child->get_item_state(i)); + DisplayServer::get_singleton()->global_menu_set_item_indentation_level(p_menu_name, index, p_child->get_item_indent(i)); + DisplayServer::get_singleton()->global_menu_set_item_tooltip(p_menu_name, index, p_child->get_item_tooltip(i)); + if (!p_child->is_item_shortcut_disabled(i) && p_child->get_item_shortcut(i).is_valid() && p_child->get_item_shortcut(i)->has_valid_event()) { + Array events = p_child->get_item_shortcut(i)->get_events(); + for (int j = 0; j < events.size(); j++) { + Ref<InputEventKey> ie = events[j]; + if (ie.is_valid()) { + DisplayServer::get_singleton()->global_menu_set_item_accelerator(p_menu_name, index, ie->get_keycode_with_modifiers()); + break; + } + } + } else if (p_child->get_item_accelerator(i) != Key::NONE) { + DisplayServer::get_singleton()->global_menu_set_item_accelerator(p_menu_name, i, p_child->get_item_accelerator(i)); + } + } + } +} + +bool MenuBar::is_native_menu() const { + if (Engine::get_singleton()->is_editor_hint() && is_inside_tree() && get_tree()->get_edited_scene_root() && (get_tree()->get_edited_scene_root()->is_ancestor_of(this) || get_tree()->get_edited_scene_root() == this)) { + return false; + } + + return (DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_GLOBAL_MENU) && is_native); +} + +void MenuBar::_clear_menu() { + if (!DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_GLOBAL_MENU)) { + return; + } + + // Remove root menu items. + int count = DisplayServer::get_singleton()->global_menu_get_item_count("_main"); + for (int i = count - 1; i >= 0; i--) { + if (global_menus.has(DisplayServer::get_singleton()->global_menu_get_item_submenu("_main", i))) { + DisplayServer::get_singleton()->global_menu_remove_item("_main", i); + } + } + // Erase submenu contents. + for (const String &E : global_menus) { + DisplayServer::get_singleton()->global_menu_clear(E); + } + global_menus.clear(); +} + +void MenuBar::_update_menu() { + _clear_menu(); + + if (!is_visible_in_tree()) { + return; + } + + int index = start_index; + if (is_native_menu()) { + Vector<PopupMenu *> popups = _get_popups(); + String root_name = "MenuBar<" + String::num_int64((uint64_t)this, 16) + ">"; + for (int i = 0; i < popups.size(); i++) { + if (menu_cache[i].hidden) { + continue; + } + String menu_name = String(popups[i]->get_meta("_menu_name", popups[i]->get_name())); + + index = DisplayServer::get_singleton()->global_menu_add_submenu_item("_main", menu_name, root_name + "/" + itos(i), index); + if (menu_cache[i].disabled) { + DisplayServer::get_singleton()->global_menu_set_item_disabled("_main", index, true); + } + _update_submenu(root_name + "/" + itos(i), popups[i]); + index++; + } + } + update_minimum_size(); + queue_redraw(); +} + +void MenuBar::_update_theme_item_cache() { + Control::_update_theme_item_cache(); + + theme_cache.normal = get_theme_stylebox(SNAME("normal")); + theme_cache.normal_mirrored = get_theme_stylebox(SNAME("normal_mirrored")); + theme_cache.disabled = get_theme_stylebox(SNAME("disabled")); + theme_cache.disabled_mirrored = get_theme_stylebox(SNAME("disabled_mirrored")); + theme_cache.pressed = get_theme_stylebox(SNAME("pressed")); + theme_cache.pressed_mirrored = get_theme_stylebox(SNAME("pressed_mirrored")); + theme_cache.hover = get_theme_stylebox(SNAME("hover")); + theme_cache.hover_mirrored = get_theme_stylebox(SNAME("hover_mirrored")); + theme_cache.hover_pressed = get_theme_stylebox(SNAME("hover_pressed")); + theme_cache.hover_pressed_mirrored = get_theme_stylebox(SNAME("hover_pressed_mirrored")); + + theme_cache.font = get_theme_font(SNAME("font")); + theme_cache.font_size = get_theme_font_size(SNAME("font_size")); + theme_cache.outline_size = get_theme_constant(SNAME("outline_size")); + theme_cache.font_outline_color = get_theme_color(SNAME("font_outline_color")); + + theme_cache.font_color = get_theme_color(SNAME("font_color")); + theme_cache.font_disabled_color = get_theme_color(SNAME("font_disabled_color")); + theme_cache.font_pressed_color = get_theme_color(SNAME("font_pressed_color")); + theme_cache.font_hover_color = get_theme_color(SNAME("font_hover_color")); + theme_cache.font_hover_pressed_color = get_theme_color(SNAME("font_hover_pressed_color")); + theme_cache.font_focus_color = get_theme_color(SNAME("font_focus_color")); + + theme_cache.h_separation = get_theme_constant(SNAME("h_separation")); +} + +void MenuBar::_notification(int p_what) { + switch (p_what) { + case NOTIFICATION_ENTER_TREE: { + if (get_menu_count() > 0) { + _refresh_menu_names(); + } + } break; + case NOTIFICATION_EXIT_TREE: { + _clear_menu(); + } break; + case NOTIFICATION_MOUSE_EXIT: { + focused_menu = -1; + queue_redraw(); + } break; + case NOTIFICATION_TRANSLATION_CHANGED: + case NOTIFICATION_LAYOUT_DIRECTION_CHANGED: + case NOTIFICATION_THEME_CHANGED: { + for (int i = 0; i < menu_cache.size(); i++) { + shape(menu_cache.write[i]); + } + _update_menu(); + } break; + case NOTIFICATION_VISIBILITY_CHANGED: { + _update_menu(); + } break; + case NOTIFICATION_DRAW: { + if (is_native_menu()) { + return; + } + for (int i = 0; i < menu_cache.size(); i++) { + _draw_menu_item(i); + } + } break; + case NOTIFICATION_INTERNAL_PROCESS: { + MutexLock lock(mutex); + + if (is_native_menu()) { + // Handled by OS. + return; + } + + Vector2 pos = DisplayServer::get_singleton()->mouse_get_position() - mouse_pos_adjusted - get_global_position(); + if (pos == old_mouse_pos) { + return; + } + old_mouse_pos = pos; + + int index = _get_index_at_point(pos); + if (index >= 0 && index != active_menu) { + selected_menu = index; + focused_menu = selected_menu; + if (active_menu >= 0) { + get_menu_popup(active_menu)->hide(); + } + _open_popup(index); + } + } break; + } +} + +int MenuBar::_get_index_at_point(const Point2 &p_point) const { + Ref<StyleBox> style = theme_cache.normal; + int offset = 0; + for (int i = 0; i < menu_cache.size(); i++) { + if (menu_cache[i].hidden) { + continue; + } + Size2 size = menu_cache[i].text_buf->get_size() + style->get_minimum_size(); + if (p_point.x > offset && p_point.x < offset + size.x) { + if (p_point.y > 0 && p_point.y < size.y) { + return i; + } + } + offset += size.x + theme_cache.h_separation; + } + return -1; +} + +Rect2 MenuBar::_get_menu_item_rect(int p_index) const { + ERR_FAIL_INDEX_V(p_index, menu_cache.size(), Rect2()); + + Ref<StyleBox> style = theme_cache.normal; + + int offset = 0; + for (int i = 0; i < p_index; i++) { + if (menu_cache[i].hidden) { + continue; + } + Size2 size = menu_cache[i].text_buf->get_size() + style->get_minimum_size(); + offset += size.x + theme_cache.h_separation; + } + + return Rect2(Point2(offset, 0), menu_cache[p_index].text_buf->get_size() + style->get_minimum_size()); +} + +void MenuBar::_draw_menu_item(int p_index) { + ERR_FAIL_INDEX(p_index, menu_cache.size()); + + RID ci = get_canvas_item(); + bool hovered = (focused_menu == p_index); + bool pressed = (active_menu == p_index); + bool rtl = is_layout_rtl(); + + if (menu_cache[p_index].hidden) { + return; + } + + Color color; + Ref<StyleBox> style = theme_cache.normal; + Rect2 item_rect = _get_menu_item_rect(p_index); + + if (menu_cache[p_index].disabled) { + if (rtl && has_theme_stylebox(SNAME("disabled_mirrored"))) { + style = theme_cache.disabled_mirrored; + } else { + style = theme_cache.disabled; + } + if (!flat) { + style->draw(ci, item_rect); + } + color = theme_cache.font_disabled_color; + } else if (hovered && pressed && has_theme_stylebox("hover_pressed")) { + if (rtl && has_theme_stylebox(SNAME("hover_pressed_mirrored"))) { + style = theme_cache.hover_pressed_mirrored; + } else { + style = theme_cache.hover_pressed; + } + if (!flat) { + style->draw(ci, item_rect); + } + if (has_theme_color(SNAME("font_hover_pressed_color"))) { + color = theme_cache.font_hover_pressed_color; + } + } else if (pressed) { + if (rtl && has_theme_stylebox(SNAME("pressed_mirrored"))) { + style = theme_cache.pressed_mirrored; + } else { + style = theme_cache.pressed; + } + if (!flat) { + style->draw(ci, item_rect); + } + if (has_theme_color(SNAME("font_pressed_color"))) { + color = theme_cache.font_pressed_color; + } else { + color = theme_cache.font_color; + } + } else if (hovered) { + if (rtl && has_theme_stylebox(SNAME("hover_mirrored"))) { + style = theme_cache.hover_mirrored; + } else { + style = theme_cache.hover; + } + if (!flat) { + style->draw(ci, item_rect); + } + color = theme_cache.font_hover_color; + } else { + if (rtl && has_theme_stylebox(SNAME("normal_mirrored"))) { + style = theme_cache.normal_mirrored; + } else { + style = theme_cache.normal; + } + if (!flat) { + style->draw(ci, item_rect); + } + // Focus colors only take precedence over normal state. + if (has_focus()) { + color = theme_cache.font_focus_color; + } else { + color = theme_cache.font_color; + } + } + + Point2 text_ofs = item_rect.position + Point2(style->get_margin(SIDE_LEFT), style->get_margin(SIDE_TOP)); + + Color font_outline_color = theme_cache.font_outline_color; + int outline_size = theme_cache.outline_size; + if (outline_size > 0 && font_outline_color.a > 0) { + menu_cache[p_index].text_buf->draw_outline(ci, text_ofs, outline_size, font_outline_color); + } + menu_cache[p_index].text_buf->draw(ci, text_ofs, color); +} + +void MenuBar::shape(Menu &p_menu) { + p_menu.text_buf->clear(); + if (text_direction == Control::TEXT_DIRECTION_INHERITED) { + p_menu.text_buf->set_direction(is_layout_rtl() ? TextServer::DIRECTION_RTL : TextServer::DIRECTION_LTR); + } else { + p_menu.text_buf->set_direction((TextServer::Direction)text_direction); + } + p_menu.text_buf->add_string(p_menu.name, theme_cache.font, theme_cache.font_size, language); +} + +void MenuBar::_refresh_menu_names() { + Vector<PopupMenu *> popups = _get_popups(); + for (int i = 0; i < popups.size(); i++) { + if (!popups[i]->has_meta("_menu_name") && String(popups[i]->get_name()) != get_menu_title(i)) { + menu_cache.write[i].name = popups[i]->get_name(); + shape(menu_cache.write[i]); + } + } + _update_menu(); +} + +Vector<PopupMenu *> MenuBar::_get_popups() const { + Vector<PopupMenu *> popups; + for (int i = 0; i < get_child_count(); i++) { + PopupMenu *pm = Object::cast_to<PopupMenu>(get_child(i)); + if (!pm) { + continue; + } + popups.push_back(pm); + } + return popups; +} + +int MenuBar::get_menu_idx_from_control(PopupMenu *p_child) const { + ERR_FAIL_NULL_V(p_child, -1); + ERR_FAIL_COND_V(p_child->get_parent() != this, -1); + + Vector<PopupMenu *> popups = _get_popups(); + for (int i = 0; i < popups.size(); i++) { + if (popups[i] == p_child) { + return i; + } + } + + return -1; +} + +void MenuBar::add_child_notify(Node *p_child) { + Control::add_child_notify(p_child); + + PopupMenu *pm = Object::cast_to<PopupMenu>(p_child); + if (!pm) { + return; + } + Menu menu = Menu(p_child->get_name()); + shape(menu); + + menu_cache.push_back(menu); + p_child->connect("renamed", callable_mp(this, &MenuBar::_refresh_menu_names)); + p_child->connect("menu_changed", callable_mp(this, &MenuBar::_update_menu)); + p_child->connect("about_to_popup", callable_mp(this, &MenuBar::_popup_visibility_changed).bind(true)); + p_child->connect("popup_hide", callable_mp(this, &MenuBar::_popup_visibility_changed).bind(false)); + + _update_menu(); +} + +void MenuBar::move_child_notify(Node *p_child) { + Control::move_child_notify(p_child); + + PopupMenu *pm = Object::cast_to<PopupMenu>(p_child); + if (!pm) { + return; + } + + int old_idx = -1; + String menu_name = String(pm->get_meta("_menu_name", pm->get_name())); + // Find the previous menu index of the control. + for (int i = 0; i < get_menu_count(); i++) { + if (get_menu_title(i) == menu_name) { + old_idx = i; + break; + } + } + Menu menu = menu_cache[old_idx]; + menu_cache.remove_at(old_idx); + menu_cache.insert(get_menu_idx_from_control(pm), menu); + + _update_menu(); +} + +void MenuBar::remove_child_notify(Node *p_child) { + Control::remove_child_notify(p_child); + + PopupMenu *pm = Object::cast_to<PopupMenu>(p_child); + if (!pm) { + return; + } + + int idx = get_menu_idx_from_control(pm); + + menu_cache.remove_at(idx); + + p_child->remove_meta("_menu_name"); + p_child->remove_meta("_menu_tooltip"); + + p_child->disconnect("renamed", callable_mp(this, &MenuBar::_refresh_menu_names)); + p_child->disconnect("menu_changed", callable_mp(this, &MenuBar::_update_menu)); + p_child->disconnect("about_to_popup", callable_mp(this, &MenuBar::_popup_visibility_changed)); + p_child->disconnect("popup_hide", callable_mp(this, &MenuBar::_popup_visibility_changed)); + + _update_menu(); +} + +void MenuBar::_bind_methods() { + ClassDB::bind_method(D_METHOD("set_switch_on_hover", "enable"), &MenuBar::set_switch_on_hover); + ClassDB::bind_method(D_METHOD("is_switch_on_hover"), &MenuBar::is_switch_on_hover); + ClassDB::bind_method(D_METHOD("set_disable_shortcuts", "disabled"), &MenuBar::set_disable_shortcuts); + + ClassDB::bind_method(D_METHOD("set_prefer_global_menu", "enabled"), &MenuBar::set_prefer_global_menu); + ClassDB::bind_method(D_METHOD("is_prefer_global_menu"), &MenuBar::is_prefer_global_menu); + ClassDB::bind_method(D_METHOD("is_native_menu"), &MenuBar::is_native_menu); + + ClassDB::bind_method(D_METHOD("get_menu_count"), &MenuBar::get_menu_count); + + ClassDB::bind_method(D_METHOD("set_text_direction", "direction"), &MenuBar::set_text_direction); + ClassDB::bind_method(D_METHOD("get_text_direction"), &MenuBar::get_text_direction); + ClassDB::bind_method(D_METHOD("set_language", "language"), &MenuBar::set_language); + ClassDB::bind_method(D_METHOD("get_language"), &MenuBar::get_language); + ClassDB::bind_method(D_METHOD("set_flat", "enabled"), &MenuBar::set_flat); + ClassDB::bind_method(D_METHOD("is_flat"), &MenuBar::is_flat); + ClassDB::bind_method(D_METHOD("set_start_index", "enabled"), &MenuBar::set_start_index); + ClassDB::bind_method(D_METHOD("get_start_index"), &MenuBar::get_start_index); + + ClassDB::bind_method(D_METHOD("set_menu_title", "menu", "title"), &MenuBar::set_menu_title); + ClassDB::bind_method(D_METHOD("get_menu_title", "menu"), &MenuBar::get_menu_title); + + ClassDB::bind_method(D_METHOD("set_menu_tooltip", "menu", "tooltip"), &MenuBar::set_menu_tooltip); + ClassDB::bind_method(D_METHOD("get_menu_tooltip", "menu"), &MenuBar::get_menu_tooltip); + + ClassDB::bind_method(D_METHOD("set_menu_disabled", "menu", "disabled"), &MenuBar::set_menu_disabled); + ClassDB::bind_method(D_METHOD("is_menu_disabled", "menu"), &MenuBar::is_menu_disabled); + + ClassDB::bind_method(D_METHOD("set_menu_hidden", "menu", "hidden"), &MenuBar::set_menu_hidden); + ClassDB::bind_method(D_METHOD("is_menu_hidden", "menu"), &MenuBar::is_menu_hidden); + + ClassDB::bind_method(D_METHOD("set_shortcut_context", "node"), &MenuBar::set_shortcut_context); + ClassDB::bind_method(D_METHOD("get_shortcut_context"), &MenuBar::get_shortcut_context); + + ClassDB::bind_method(D_METHOD("get_menu_popup", "menu"), &MenuBar::get_menu_popup); + + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "flat"), "set_flat", "is_flat"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "start_index"), "set_start_index", "get_start_index"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "switch_on_hover"), "set_switch_on_hover", "is_switch_on_hover"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "prefer_global_menu"), "set_prefer_global_menu", "is_prefer_global_menu"); + ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "shortcut_context", PROPERTY_HINT_RESOURCE_TYPE, "Node"), "set_shortcut_context", "get_shortcut_context"); + + ADD_GROUP("BiDi", ""); + 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"); +} + +void MenuBar::set_switch_on_hover(bool p_enabled) { + switch_on_hover = p_enabled; +} + +bool MenuBar::is_switch_on_hover() { + return switch_on_hover; +} + +void MenuBar::set_disable_shortcuts(bool p_disabled) { + disable_shortcuts = p_disabled; +} + +void MenuBar::set_text_direction(Control::TextDirection p_text_direction) { + ERR_FAIL_COND((int)p_text_direction < -1 || (int)p_text_direction > 3); + if (text_direction != p_text_direction) { + text_direction = p_text_direction; + _update_menu(); + } +} + +Control::TextDirection MenuBar::get_text_direction() const { + return text_direction; +} + +void MenuBar::set_language(const String &p_language) { + if (language != p_language) { + language = p_language; + _update_menu(); + } +} + +String MenuBar::get_language() const { + return language; +} + +void MenuBar::set_flat(bool p_enabled) { + if (flat != p_enabled) { + flat = p_enabled; + queue_redraw(); + } +} + +bool MenuBar::is_flat() const { + return flat; +} + +void MenuBar::set_start_index(int p_index) { + if (start_index != p_index) { + start_index = p_index; + _update_menu(); + } +} + +int MenuBar::get_start_index() const { + return start_index; +} + +void MenuBar::set_prefer_global_menu(bool p_enabled) { + if (is_native != p_enabled) { + if (is_native) { + _clear_menu(); + } + is_native = p_enabled; + _update_menu(); + } +} + +bool MenuBar::is_prefer_global_menu() const { + return is_native; +} + +Size2 MenuBar::get_minimum_size() const { + if (is_native_menu()) { + return Size2(); + } + + Ref<StyleBox> style = theme_cache.normal; + + Vector2 size; + for (int i = 0; i < menu_cache.size(); i++) { + if (menu_cache[i].hidden) { + continue; + } + Size2 sz = menu_cache[i].text_buf->get_size() + style->get_minimum_size(); + size.y = MAX(size.y, sz.y); + size.x += sz.x; + } + if (menu_cache.size() > 1) { + size.x += theme_cache.h_separation * (menu_cache.size() - 1); + } + return size; +} + +int MenuBar::get_menu_count() const { + return menu_cache.size(); +} + +void MenuBar::set_menu_title(int p_menu, const String &p_title) { + ERR_FAIL_INDEX(p_menu, menu_cache.size()); + PopupMenu *pm = get_menu_popup(p_menu); + if (p_title == pm->get_name()) { + pm->remove_meta("_menu_name"); + } else { + pm->set_meta("_menu_name", p_title); + } + menu_cache.write[p_menu].name = p_title; + shape(menu_cache.write[p_menu]); + _update_menu(); +} + +String MenuBar::get_menu_title(int p_menu) const { + ERR_FAIL_INDEX_V(p_menu, menu_cache.size(), String()); + return menu_cache[p_menu].name; +} + +void MenuBar::set_menu_tooltip(int p_menu, const String &p_tooltip) { + ERR_FAIL_INDEX(p_menu, menu_cache.size()); + PopupMenu *pm = get_menu_popup(p_menu); + pm->set_meta("_menu_tooltip", p_tooltip); + menu_cache.write[p_menu].name = p_tooltip; +} + +String MenuBar::get_menu_tooltip(int p_menu) const { + ERR_FAIL_INDEX_V(p_menu, menu_cache.size(), String()); + return menu_cache[p_menu].tooltip; +} + +void MenuBar::set_menu_disabled(int p_menu, bool p_disabled) { + ERR_FAIL_INDEX(p_menu, menu_cache.size()); + menu_cache.write[p_menu].disabled = p_disabled; + _update_menu(); +} + +bool MenuBar::is_menu_disabled(int p_menu) const { + ERR_FAIL_INDEX_V(p_menu, menu_cache.size(), false); + return menu_cache[p_menu].disabled; +} + +void MenuBar::set_menu_hidden(int p_menu, bool p_hidden) { + ERR_FAIL_INDEX(p_menu, menu_cache.size()); + menu_cache.write[p_menu].hidden = p_hidden; + _update_menu(); +} + +bool MenuBar::is_menu_hidden(int p_menu) const { + ERR_FAIL_INDEX_V(p_menu, menu_cache.size(), false); + return menu_cache[p_menu].hidden; +} + +PopupMenu *MenuBar::get_menu_popup(int p_idx) const { + Vector<PopupMenu *> controls = _get_popups(); + if (p_idx >= 0 && p_idx < controls.size()) { + return controls[p_idx]; + } else { + return nullptr; + } +} + +String MenuBar::get_tooltip(const Point2 &p_pos) const { + int index = _get_index_at_point(p_pos); + if (index >= 0 && index < menu_cache.size()) { + return menu_cache[index].tooltip; + } else { + return String(); + } +} + +void MenuBar::get_translatable_strings(List<String> *p_strings) const { + Vector<PopupMenu *> popups = _get_popups(); + for (int i = 0; i < popups.size(); i++) { + PopupMenu *pm = popups[i]; + + if (!pm->has_meta("_menu_name") && !pm->has_meta("_menu_tooltip")) { + continue; + } + + String name = pm->get_meta("_menu_name"); + if (!name.is_empty()) { + p_strings->push_back(name); + } + + String tooltip = pm->get_meta("_menu_tooltip"); + if (!tooltip.is_empty()) { + p_strings->push_back(tooltip); + } + } +} + +MenuBar::MenuBar() { + set_process_shortcut_input(true); +} + +MenuBar::~MenuBar() { +} diff --git a/scene/gui/menu_bar.h b/scene/gui/menu_bar.h new file mode 100644 index 0000000000..f7ef19e98b --- /dev/null +++ b/scene/gui/menu_bar.h @@ -0,0 +1,185 @@ +/*************************************************************************/ +/* menu_bar.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#ifndef MENU_BAR_H +#define MENU_BAR_H + +#include "scene/gui/button.h" +#include "scene/gui/popup_menu.h" + +class MenuBar : public Control { + GDCLASS(MenuBar, Control); + + Mutex mutex; + + bool switch_on_hover = true; + bool disable_shortcuts = false; + bool is_native = true; + bool flat = false; + int start_index = -1; + + String language; + TextDirection text_direction = TEXT_DIRECTION_AUTO; + + struct Menu { + String name; + String tooltip; + + Ref<TextLine> text_buf; + bool hidden = false; + bool disabled = false; + + Menu(const String &p_name) { + name = p_name; + text_buf.instantiate(); + } + + Menu() { + text_buf.instantiate(); + } + }; + Vector<Menu> menu_cache; + HashSet<String> global_menus; + + int focused_menu = -1; + int selected_menu = -1; + int active_menu = -1; + + Vector2i mouse_pos_adjusted; + Vector2i old_mouse_pos; + ObjectID shortcut_context; + + struct ThemeCache { + Ref<StyleBox> normal; + Ref<StyleBox> normal_mirrored; + Ref<StyleBox> disabled; + Ref<StyleBox> disabled_mirrored; + Ref<StyleBox> pressed; + Ref<StyleBox> pressed_mirrored; + Ref<StyleBox> hover; + Ref<StyleBox> hover_mirrored; + Ref<StyleBox> hover_pressed; + Ref<StyleBox> hover_pressed_mirrored; + + Ref<Font> font; + int font_size = 0; + int outline_size = 0; + Color font_outline_color; + + Color font_color; + Color font_disabled_color; + Color font_pressed_color; + Color font_hover_color; + Color font_hover_pressed_color; + Color font_focus_color; + + int h_separation = 0; + } theme_cache; + + int _get_index_at_point(const Point2 &p_point) const; + Rect2 _get_menu_item_rect(int p_index) const; + void _draw_menu_item(int p_index); + + void shape(Menu &p_menu); + void _refresh_menu_names(); + Vector<PopupMenu *> _get_popups() const; + int get_menu_idx_from_control(PopupMenu *p_child) const; + + void _open_popup(int p_index, bool p_focus_item = false); + void _popup_visibility_changed(bool p_visible); + void _update_submenu(const String &p_menu_name, PopupMenu *p_child); + void _clear_menu(); + void _update_menu(); + + bool _is_focus_owner_in_shortcut_context() const; + +protected: + virtual void shortcut_input(const Ref<InputEvent> &p_event) override; + + virtual void _update_theme_item_cache() override; + void _notification(int p_what); + virtual void add_child_notify(Node *p_child) override; + virtual void move_child_notify(Node *p_child) override; + virtual void remove_child_notify(Node *p_child) override; + static void _bind_methods(); + +public: + virtual void gui_input(const Ref<InputEvent> &p_event) override; + + void set_switch_on_hover(bool p_enabled); + bool is_switch_on_hover(); + void set_disable_shortcuts(bool p_disabled); + + void set_prefer_global_menu(bool p_enabled); + bool is_prefer_global_menu() const; + + bool is_native_menu() const; + + virtual Size2 get_minimum_size() const override; + + int get_menu_count() const; + + void set_text_direction(TextDirection p_text_direction); + TextDirection get_text_direction() const; + + void set_language(const String &p_language); + String get_language() const; + + void set_start_index(int p_index); + int get_start_index() const; + + void set_flat(bool p_enabled); + bool is_flat() const; + + void set_menu_title(int p_menu, const String &p_title); + String get_menu_title(int p_menu) const; + + void set_menu_tooltip(int p_menu, const String &p_tooltip); + String get_menu_tooltip(int p_menu) const; + + void set_menu_disabled(int p_menu, bool p_disabled); + bool is_menu_disabled(int p_menu) const; + + void set_menu_hidden(int p_menu, bool p_hidden); + bool is_menu_hidden(int p_menu) const; + + void set_shortcut_context(Node *p_node); + Node *get_shortcut_context() const; + + PopupMenu *get_menu_popup(int p_menu) const; + + virtual void get_translatable_strings(List<String> *p_strings) const override; + virtual String get_tooltip(const Point2 &p_pos) const override; + + MenuBar(); + ~MenuBar(); +}; + +#endif // MENU_BAR_H diff --git a/scene/gui/menu_button.cpp b/scene/gui/menu_button.cpp index 069a31d9d2..67a36240a2 100644 --- a/scene/gui/menu_button.cpp +++ b/scene/gui/menu_button.cpp @@ -86,6 +86,11 @@ void MenuButton::_popup_visibility_changed(bool p_visible) { } void MenuButton::pressed() { + if (popup->is_visible()) { + popup->hide(); + return; + } + emit_signal(SNAME("about_to_popup")); Size2 size = get_size() * get_viewport()->get_canvas_transform().get_scale(); @@ -98,11 +103,14 @@ void MenuButton::pressed() { popup->set_position(gp); popup->set_parent_rect(Rect2(Point2(gp - popup->get_position()), size)); - // If not triggered by the mouse, start the popup with its first item selected. - if (popup->get_item_count() > 0 && - ((get_action_mode() == ActionMode::ACTION_MODE_BUTTON_PRESS && Input::get_singleton()->is_action_just_pressed("ui_accept")) || - (get_action_mode() == ActionMode::ACTION_MODE_BUTTON_RELEASE && Input::get_singleton()->is_action_just_released("ui_accept")))) { - popup->set_current_index(0); + // If not triggered by the mouse, start the popup with its first enabled item focused. + if (!_was_pressed_by_mouse()) { + for (int i = 0; i < popup->get_item_count(); i++) { + if (!popup->is_item_disabled(i)) { + popup->set_focused_item(i); + break; + } + } } popup->popup(); @@ -126,6 +134,11 @@ bool MenuButton::is_switch_on_hover() { void MenuButton::set_item_count(int p_count) { ERR_FAIL_COND(p_count < 0); + + if (popup->get_item_count() == p_count) { + return; + } + popup->set_item_count(p_count); notify_property_list_changed(); } @@ -153,7 +166,10 @@ void MenuButton::_notification(int p_what) { if (menu_btn_other && menu_btn_other != this && menu_btn_other->is_switch_on_hover() && !menu_btn_other->is_disabled() && (get_parent()->is_ancestor_of(menu_btn_other) || menu_btn_other->get_parent()->is_ancestor_of(popup))) { popup->hide(); + menu_btn_other->pressed(); + // As the popup wasn't triggered by a mouse click, the item focus needs to be removed manually. + menu_btn_other->get_popup()->set_focused_item(-1); } } break; } diff --git a/scene/gui/nine_patch_rect.cpp b/scene/gui/nine_patch_rect.cpp index 8fee10b19a..6048916d7d 100644 --- a/scene/gui/nine_patch_rect.cpp +++ b/scene/gui/nine_patch_rect.cpp @@ -94,7 +94,7 @@ void NinePatchRect::set_texture(const Ref<Texture2D> &p_tex) { return; } texture = p_tex; - update(); + queue_redraw(); update_minimum_size(); emit_signal(SceneStringNames::get_singleton()->texture_changed); } @@ -105,8 +105,13 @@ Ref<Texture2D> NinePatchRect::get_texture() const { void NinePatchRect::set_patch_margin(Side p_side, int p_size) { ERR_FAIL_INDEX((int)p_side, 4); + + if (margin[p_side] == p_size) { + return; + } + margin[p_side] = p_size; - update(); + queue_redraw(); update_minimum_size(); } @@ -130,8 +135,12 @@ Rect2 NinePatchRect::get_region_rect() const { } void NinePatchRect::set_draw_center(bool p_enabled) { + if (draw_center == p_enabled) { + return; + } + draw_center = p_enabled; - update(); + queue_redraw(); } bool NinePatchRect::is_draw_center_enabled() const { @@ -139,8 +148,12 @@ bool NinePatchRect::is_draw_center_enabled() const { } void NinePatchRect::set_h_axis_stretch_mode(AxisStretchMode p_mode) { + if (axis_h == p_mode) { + return; + } + axis_h = p_mode; - update(); + queue_redraw(); } NinePatchRect::AxisStretchMode NinePatchRect::get_h_axis_stretch_mode() const { @@ -148,8 +161,12 @@ NinePatchRect::AxisStretchMode NinePatchRect::get_h_axis_stretch_mode() const { } void NinePatchRect::set_v_axis_stretch_mode(AxisStretchMode p_mode) { + if (axis_v == p_mode) { + return; + } + axis_v = p_mode; - update(); + queue_redraw(); } NinePatchRect::AxisStretchMode NinePatchRect::get_v_axis_stretch_mode() const { diff --git a/scene/gui/option_button.cpp b/scene/gui/option_button.cpp index c6d011d4ef..08f5e0bbfb 100644 --- a/scene/gui/option_button.cpp +++ b/scene/gui/option_button.cpp @@ -43,11 +43,11 @@ Size2 OptionButton::get_minimum_size() const { } if (has_theme_icon(SNAME("arrow"))) { - const Size2 padding = get_theme_stylebox(SNAME("normal"))->get_minimum_size(); - const Size2 arrow_size = Control::get_theme_icon(SNAME("arrow"))->get_size(); + const Size2 padding = theme_cache.normal->get_minimum_size(); + const Size2 arrow_size = theme_cache.arrow_icon->get_size(); Size2 content_size = minsize - padding; - content_size.width += arrow_size.width + get_theme_constant(SNAME("h_separation")); + content_size.width += arrow_size.width + MAX(0, theme_cache.h_separation); content_size.height = MAX(content_size.height, arrow_size.height); minsize = content_size + padding; @@ -56,32 +56,63 @@ Size2 OptionButton::get_minimum_size() const { return minsize; } +void OptionButton::_update_theme_item_cache() { + Button::_update_theme_item_cache(); + + theme_cache.normal = get_theme_stylebox(SNAME("normal")); + + theme_cache.font_color = get_theme_color(SNAME("font_color")); + theme_cache.font_focus_color = get_theme_color(SNAME("font_focus_color")); + theme_cache.font_pressed_color = get_theme_color(SNAME("font_pressed_color")); + theme_cache.font_hover_color = get_theme_color(SNAME("font_hover_color")); + theme_cache.font_hover_pressed_color = get_theme_color(SNAME("font_hover_pressed_color")); + theme_cache.font_disabled_color = get_theme_color(SNAME("font_disabled_color")); + + theme_cache.h_separation = get_theme_constant(SNAME("h_separation")); + + theme_cache.arrow_icon = get_theme_icon(SNAME("arrow")); + theme_cache.arrow_margin = get_theme_constant(SNAME("arrow_margin")); + theme_cache.modulate_arrow = get_theme_constant(SNAME("modulate_arrow")); +} + void OptionButton::_notification(int p_what) { switch (p_what) { + case NOTIFICATION_POSTINITIALIZE: { + if (has_theme_icon(SNAME("arrow"))) { + if (is_layout_rtl()) { + _set_internal_margin(SIDE_LEFT, theme_cache.arrow_icon->get_width()); + } else { + _set_internal_margin(SIDE_RIGHT, theme_cache.arrow_icon->get_width()); + } + } + } break; + case NOTIFICATION_DRAW: { if (!has_theme_icon(SNAME("arrow"))) { return; } RID ci = get_canvas_item(); - Ref<Texture2D> arrow = Control::get_theme_icon(SNAME("arrow")); Color clr = Color(1, 1, 1); - if (get_theme_constant(SNAME("modulate_arrow"))) { + if (theme_cache.modulate_arrow) { switch (get_draw_mode()) { case DRAW_PRESSED: - clr = get_theme_color(SNAME("font_pressed_color")); + clr = theme_cache.font_pressed_color; break; case DRAW_HOVER: - clr = get_theme_color(SNAME("font_hover_color")); + clr = theme_cache.font_hover_color; + break; + case DRAW_HOVER_PRESSED: + clr = theme_cache.font_hover_pressed_color; break; case DRAW_DISABLED: - clr = get_theme_color(SNAME("font_disabled_color")); + clr = theme_cache.font_disabled_color; break; default: if (has_focus()) { - clr = get_theme_color(SNAME("font_focus_color")); + clr = theme_cache.font_focus_color; } else { - clr = get_theme_color(SNAME("font_color")); + clr = theme_cache.font_color; } } } @@ -90,11 +121,11 @@ void OptionButton::_notification(int p_what) { Point2 ofs; if (is_layout_rtl()) { - ofs = Point2(get_theme_constant(SNAME("arrow_margin")), int(Math::abs((size.height - arrow->get_height()) / 2))); + ofs = Point2(theme_cache.arrow_margin, int(Math::abs((size.height - theme_cache.arrow_icon->get_height()) / 2))); } else { - ofs = Point2(size.width - arrow->get_width() - get_theme_constant(SNAME("arrow_margin")), int(Math::abs((size.height - arrow->get_height()) / 2))); + ofs = Point2(size.width - theme_cache.arrow_icon->get_width() - theme_cache.arrow_margin, int(Math::abs((size.height - theme_cache.arrow_icon->get_height()) / 2))); } - arrow->draw(ci, ofs, clr); + theme_cache.arrow_icon->draw(ci, ofs, clr); } break; case NOTIFICATION_TRANSLATION_CHANGED: @@ -105,11 +136,11 @@ void OptionButton::_notification(int p_what) { case NOTIFICATION_THEME_CHANGED: { if (has_theme_icon(SNAME("arrow"))) { if (is_layout_rtl()) { - _set_internal_margin(SIDE_LEFT, Control::get_theme_icon(SNAME("arrow"))->get_width()); + _set_internal_margin(SIDE_LEFT, theme_cache.arrow_icon->get_width()); _set_internal_margin(SIDE_RIGHT, 0.f); } else { _set_internal_margin(SIDE_LEFT, 0.f); - _set_internal_margin(SIDE_RIGHT, Control::get_theme_icon(SNAME("arrow"))->get_width()); + _set_internal_margin(SIDE_RIGHT, theme_cache.arrow_icon->get_width()); } } _refresh_size_cache(); @@ -195,17 +226,33 @@ void OptionButton::_selected(int p_which) { } void OptionButton::pressed() { + if (popup->is_visible()) { + popup->hide(); + return; + } + Size2 size = get_size() * get_viewport()->get_canvas_transform().get_scale(); popup->set_position(get_screen_position() + Size2(0, size.height * get_global_transform().get_scale().y)); popup->set_size(Size2(size.width, 0)); - // If not triggered by the mouse, start the popup with the checked item selected. - if (popup->get_item_count() > 0) { - if ((get_action_mode() == ActionMode::ACTION_MODE_BUTTON_PRESS && Input::get_singleton()->is_action_just_pressed("ui_accept")) || - (get_action_mode() == ActionMode::ACTION_MODE_BUTTON_RELEASE && Input::get_singleton()->is_action_just_released("ui_accept"))) { - popup->set_current_index(current > -1 ? current : 0); + // If not triggered by the mouse, start the popup with the checked item (or the first enabled one) focused. + if (current != NONE_SELECTED && !popup->is_item_disabled(current)) { + if (!_was_pressed_by_mouse()) { + popup->set_focused_item(current); } else { - popup->scroll_to_item(current > -1 ? current : 0); + popup->scroll_to_item(current); + } + } else { + for (int i = 0; i < popup->get_item_count(); i++) { + if (!popup->is_item_disabled(i)) { + if (!_was_pressed_by_mouse()) { + popup->set_focused_item(i); + } else { + popup->scroll_to_item(i); + } + + break; + } } } @@ -468,9 +515,9 @@ void OptionButton::get_translatable_strings(List<String> *p_strings) const { popup->get_translatable_strings(p_strings); } -void OptionButton::_validate_property(PropertyInfo &property) const { - if (property.name == "text" || property.name == "icon") { - property.usage = PROPERTY_USAGE_NONE; +void OptionButton::_validate_property(PropertyInfo &p_property) const { + if (p_property.name == "text" || p_property.name == "icon") { + p_property.usage = PROPERTY_USAGE_NONE; } } @@ -521,15 +568,6 @@ OptionButton::OptionButton(const String &p_text) : Button(p_text) { set_toggle_mode(true); set_text_alignment(HORIZONTAL_ALIGNMENT_LEFT); - if (is_layout_rtl()) { - if (has_theme_icon(SNAME("arrow"))) { - _set_internal_margin(SIDE_LEFT, Control::get_theme_icon(SNAME("arrow"))->get_width()); - } - } else { - if (has_theme_icon(SNAME("arrow"))) { - _set_internal_margin(SIDE_RIGHT, Control::get_theme_icon(SNAME("arrow"))->get_width()); - } - } set_action_mode(ACTION_MODE_BUTTON_PRESS); popup = memnew(PopupMenu); diff --git a/scene/gui/option_button.h b/scene/gui/option_button.h index 49b5eee910..2c7e0510f5 100644 --- a/scene/gui/option_button.h +++ b/scene/gui/option_button.h @@ -43,6 +43,23 @@ class OptionButton : public Button { Vector2 _cached_size; bool cache_refresh_pending = false; + struct ThemeCache { + Ref<StyleBox> normal; + + Color font_color; + Color font_focus_color; + Color font_pressed_color; + Color font_hover_color; + Color font_hover_pressed_color; + Color font_disabled_color; + + int h_separation = 0; + + Ref<Texture2D> arrow_icon; + int arrow_margin = 0; + int modulate_arrow = 0; + } theme_cache; + void _focused(int p_which); void _selected(int p_which); void _select(int p_which, bool p_emit = false); @@ -54,11 +71,12 @@ class OptionButton : public Button { protected: Size2 get_minimum_size() const override; + virtual void _update_theme_item_cache() override; void _notification(int p_what); bool _set(const StringName &p_name, const Variant &p_value); bool _get(const StringName &p_name, Variant &r_ret) const; void _get_property_list(List<PropertyInfo> *p_list) const; - virtual void _validate_property(PropertyInfo &property) const override; + void _validate_property(PropertyInfo &p_property) const; static void _bind_methods(); public: diff --git a/scene/gui/panel.cpp b/scene/gui/panel.cpp index 1ac6cf57ab..09bc295513 100644 --- a/scene/gui/panel.cpp +++ b/scene/gui/panel.cpp @@ -30,12 +30,17 @@ #include "panel.h" +void Panel::_update_theme_item_cache() { + Control::_update_theme_item_cache(); + + theme_cache.panel_style = get_theme_stylebox(SNAME("panel")); +} + void Panel::_notification(int p_what) { switch (p_what) { case NOTIFICATION_DRAW: { RID ci = get_canvas_item(); - Ref<StyleBox> style = get_theme_stylebox(SNAME("panel")); - style->draw(ci, Rect2(Point2(), get_size())); + theme_cache.panel_style->draw(ci, Rect2(Point2(), get_size())); } break; } } diff --git a/scene/gui/panel.h b/scene/gui/panel.h index 5d2e912680..f9bd721681 100644 --- a/scene/gui/panel.h +++ b/scene/gui/panel.h @@ -36,7 +36,13 @@ class Panel : public Control { GDCLASS(Panel, Control); + struct ThemeCache { + Ref<StyleBox> panel_style; + } theme_cache; + protected: + virtual void _update_theme_item_cache() override; + void _notification(int p_what); public: diff --git a/scene/gui/panel_container.cpp b/scene/gui/panel_container.cpp index fe01712a89..eeaded1788 100644 --- a/scene/gui/panel_container.cpp +++ b/scene/gui/panel_container.cpp @@ -31,14 +31,6 @@ #include "panel_container.h" Size2 PanelContainer::get_minimum_size() const { - Ref<StyleBox> style; - - if (has_theme_stylebox(SNAME("panel"))) { - style = get_theme_stylebox(SNAME("panel")); - } else { - style = get_theme_stylebox(SNAME("panel"), SNAME("PanelContainer")); - } - Size2 ms; for (int i = 0; i < get_child_count(); i++) { Control *c = Object::cast_to<Control>(get_child(i)); @@ -54,8 +46,8 @@ Size2 PanelContainer::get_minimum_size() const { ms.height = MAX(ms.height, minsize.height); } - if (style.is_valid()) { - ms += style->get_minimum_size(); + if (theme_cache.panel_style.is_valid()) { + ms += theme_cache.panel_style->get_minimum_size(); } return ms; } @@ -78,35 +70,25 @@ Vector<int> PanelContainer::get_allowed_size_flags_vertical() const { return flags; } +void PanelContainer::_update_theme_item_cache() { + Container::_update_theme_item_cache(); + + theme_cache.panel_style = get_theme_stylebox(SNAME("panel")); +} + void PanelContainer::_notification(int p_what) { switch (p_what) { case NOTIFICATION_DRAW: { RID ci = get_canvas_item(); - Ref<StyleBox> style; - - if (has_theme_stylebox(SNAME("panel"))) { - style = get_theme_stylebox(SNAME("panel")); - } else { - style = get_theme_stylebox(SNAME("panel"), SNAME("PanelContainer")); - } - - style->draw(ci, Rect2(Point2(), get_size())); + theme_cache.panel_style->draw(ci, Rect2(Point2(), get_size())); } break; case NOTIFICATION_SORT_CHILDREN: { - Ref<StyleBox> style; - - if (has_theme_stylebox(SNAME("panel"))) { - style = get_theme_stylebox(SNAME("panel")); - } else { - style = get_theme_stylebox(SNAME("panel"), SNAME("PanelContainer")); - } - Size2 size = get_size(); Point2 ofs; - if (style.is_valid()) { - size -= style->get_minimum_size(); - ofs += style->get_offset(); + if (theme_cache.panel_style.is_valid()) { + size -= theme_cache.panel_style->get_minimum_size(); + ofs += theme_cache.panel_style->get_offset(); } for (int i = 0; i < get_child_count(); i++) { diff --git a/scene/gui/panel_container.h b/scene/gui/panel_container.h index 8f07ce38eb..97d0cdc872 100644 --- a/scene/gui/panel_container.h +++ b/scene/gui/panel_container.h @@ -36,7 +36,12 @@ class PanelContainer : public Container { GDCLASS(PanelContainer, Container); + struct ThemeCache { + Ref<StyleBox> panel_style; + } theme_cache; + protected: + virtual void _update_theme_item_cache() override; void _notification(int p_what); public: diff --git a/scene/gui/popup.cpp b/scene/gui/popup.cpp index c4396f636a..ceae3791f3 100644 --- a/scene/gui/popup.cpp +++ b/scene/gui/popup.cpp @@ -68,6 +68,12 @@ void Popup::_deinitialize_visible_parents() { } } +void Popup::_update_theme_item_cache() { + Window::_update_theme_item_cache(); + + theme_cache.panel_style = get_theme_stylebox(SNAME("panel")); +} + void Popup::_notification(int p_what) { switch (p_what) { case NOTIFICATION_VISIBILITY_CHANGED: { @@ -186,8 +192,6 @@ Popup::~Popup() { } Size2 PopupPanel::_get_contents_minimum_size() const { - Ref<StyleBox> p = get_theme_stylebox(SNAME("panel"), get_class_name()); - Size2 ms; for (int i = 0; i < get_child_count(); i++) { @@ -205,14 +209,12 @@ Size2 PopupPanel::_get_contents_minimum_size() const { ms.y = MAX(cms.y, ms.y); } - return ms + p->get_minimum_size(); + return ms + theme_cache.panel_style->get_minimum_size(); } void PopupPanel::_update_child_rects() { - Ref<StyleBox> p = get_theme_stylebox(SNAME("panel"), get_class_name()); - - Vector2 cpos(p->get_offset()); - Vector2 csize(get_size() - p->get_minimum_size()); + Vector2 cpos(theme_cache.panel_style->get_offset()); + Vector2 csize(get_size() - theme_cache.panel_style->get_minimum_size()); for (int i = 0; i < get_child_count(); i++) { Control *c = Object::cast_to<Control>(get_child(i)); @@ -234,15 +236,17 @@ void PopupPanel::_update_child_rects() { } } +void PopupPanel::_update_theme_item_cache() { + Popup::_update_theme_item_cache(); + + theme_cache.panel_style = get_theme_stylebox(SNAME("panel")); +} + void PopupPanel::_notification(int p_what) { switch (p_what) { + case NOTIFICATION_READY: case NOTIFICATION_THEME_CHANGED: { - panel->add_theme_style_override("panel", get_theme_stylebox(SNAME("panel"), get_class_name())); - } break; - - case NOTIFICATION_ENTER_TREE: - case NOTIFICATION_READY: { - panel->add_theme_style_override("panel", get_theme_stylebox(SNAME("panel"), get_class_name())); + panel->add_theme_style_override("panel", theme_cache.panel_style); _update_child_rects(); } break; diff --git a/scene/gui/popup.h b/scene/gui/popup.h index 70eb8722d0..0d6ca25c18 100644 --- a/scene/gui/popup.h +++ b/scene/gui/popup.h @@ -43,6 +43,10 @@ class Popup : public Window { LocalVector<Window *> visible_parents; bool popped_up = false; + struct ThemeCache { + Ref<StyleBox> panel_style; + } theme_cache; + void _input_from_window(const Ref<InputEvent> &p_event); void _initialize_visible_parents(); @@ -52,6 +56,7 @@ protected: void _close_pressed(); virtual Rect2i _popup_adjust_rect() const override; + virtual void _update_theme_item_cache() override; void _notification(int p_what); static void _bind_methods(); @@ -69,8 +74,14 @@ class PopupPanel : public Popup { Panel *panel = nullptr; + struct ThemeCache { + Ref<StyleBox> panel_style; + } theme_cache; + protected: void _update_child_rects(); + + virtual void _update_theme_item_cache() override; void _notification(int p_what); virtual Size2 _get_contents_minimum_size() const override; diff --git a/scene/gui/popup_menu.cpp b/scene/gui/popup_menu.cpp index 928bab8842..d4a4efd578 100644 --- a/scene/gui/popup_menu.cpp +++ b/scene/gui/popup_menu.cpp @@ -36,6 +36,7 @@ #include "core/os/os.h" #include "core/string/print_string.h" #include "core/string/translation.h" +#include "scene/gui/menu_bar.h" String PopupMenu::_get_accel_text(const Item &p_item) const { if (p_item.shortcut.is_valid()) { @@ -47,15 +48,12 @@ String PopupMenu::_get_accel_text(const Item &p_item) const { } Size2 PopupMenu::_get_contents_minimum_size() const { - int vseparation = get_theme_constant(SNAME("v_separation")); - int hseparation = get_theme_constant(SNAME("h_separation")); - - Size2 minsize = get_theme_stylebox(SNAME("panel"))->get_minimum_size(); // Accounts for margin in the margin container + Size2 minsize = theme_cache.panel_style->get_minimum_size(); // Accounts for margin in the margin container minsize.x += scroll_container->get_v_scroll_bar()->get_size().width * 2; // Adds a buffer so that the scrollbar does not render over the top of content float max_w = 0.0; float icon_w = 0.0; - int check_w = MAX(get_theme_icon(SNAME("checked"))->get_width(), get_theme_icon(SNAME("radio_checked"))->get_width()) + hseparation; + int check_w = MAX(theme_cache.checked->get_width(), theme_cache.radio_checked->get_width()) + theme_cache.h_separation; int accel_max_w = 0; bool has_check = false; @@ -66,23 +64,23 @@ Size2 PopupMenu::_get_contents_minimum_size() const { size.height = _get_item_height(i); icon_w = MAX(icon_size.width, icon_w); - size.width += items[i].h_ofs; + size.width += items[i].indent * theme_cache.indent; if (items[i].checkable_type && !items[i].separator) { has_check = true; } size.width += items[i].text_buf->get_size().x; - size.height += vseparation; + size.height += theme_cache.v_separation; if (items[i].accel != Key::NONE || (items[i].shortcut.is_valid() && items[i].shortcut->has_valid_event())) { - int accel_w = hseparation * 2; + int accel_w = theme_cache.h_separation * 2; accel_w += items[i].accel_text_buf->get_size().x; accel_max_w = MAX(accel_w, accel_max_w); } if (!items[i].submenu.is_empty()) { - size.width += get_theme_icon(SNAME("submenu"))->get_width(); + size.width += theme_cache.submenu->get_width(); } max_w = MAX(max_w, size.width); @@ -90,7 +88,7 @@ Size2 PopupMenu::_get_contents_minimum_size() const { minsize.height += size.height; } - int item_side_padding = get_theme_constant(SNAME("item_start_padding")) + get_theme_constant(SNAME("item_end_padding")); + int item_side_padding = theme_cache.item_start_padding + theme_cache.item_end_padding; minsize.width += max_w + icon_w + accel_max_w + item_side_padding; if (has_check) { @@ -112,33 +110,31 @@ int PopupMenu::_get_item_height(int p_item) const { int icon_height = items[p_item].get_icon_size().height; if (items[p_item].checkable_type && !items[p_item].separator) { - icon_height = MAX(icon_height, MAX(get_theme_icon(SNAME("checked"))->get_height(), get_theme_icon(SNAME("radio_checked"))->get_height())); + icon_height = MAX(icon_height, MAX(theme_cache.checked->get_height(), theme_cache.radio_checked->get_height())); } int text_height = items[p_item].text_buf->get_size().height; if (text_height == 0 && !items[p_item].separator) { - text_height = get_theme_font(SNAME("font"))->get_height(get_theme_font_size(SNAME("font_size"))); + text_height = theme_cache.font->get_height(theme_cache.font_size); } int separator_height = 0; if (items[p_item].separator) { - separator_height = MAX(get_theme_stylebox(SNAME("separator"))->get_minimum_size().height, MAX(get_theme_stylebox(SNAME("labeled_separator_left"))->get_minimum_size().height, get_theme_stylebox(SNAME("labeled_separator_right"))->get_minimum_size().height)); + separator_height = MAX(theme_cache.separator_style->get_minimum_size().height, MAX(theme_cache.labeled_separator_left->get_minimum_size().height, theme_cache.labeled_separator_right->get_minimum_size().height)); } return MAX(separator_height, MAX(text_height, icon_height)); } int PopupMenu::_get_items_total_height() const { - int vsep = get_theme_constant(SNAME("v_separation")); - // Get total height of all items by taking max of icon height and font height int items_total_height = 0; for (int i = 0; i < items.size(); i++) { - items_total_height += _get_item_height(i) + vsep; + items_total_height += _get_item_height(i) + theme_cache.v_separation; } // Subtract a separator which is not needed for the last item. - return items_total_height - vsep; + return items_total_height - theme_cache.v_separation; } int PopupMenu::_get_mouse_over(const Point2 &p_over) const { @@ -146,18 +142,15 @@ int PopupMenu::_get_mouse_over(const Point2 &p_over) const { return -1; } - Ref<StyleBox> style = get_theme_stylebox(SNAME("panel")); // Accounts for margin in the margin container - - int vseparation = get_theme_constant(SNAME("v_separation")); - - Point2 ofs = style->get_offset() + Point2(0, vseparation / 2); + // Accounts for margin in the margin container + Point2 ofs = theme_cache.panel_style->get_offset() + Point2(0, theme_cache.v_separation / 2); if (ofs.y > p_over.y) { return -1; } for (int i = 0; i < items.size(); i++) { - ofs.y += i > 0 ? vseparation : (float)vseparation / 2; + ofs.y += i > 0 ? theme_cache.v_separation : (float)theme_cache.v_separation / 2; ofs.y += _get_item_height(i); @@ -178,9 +171,6 @@ void PopupMenu::_activate_submenu(int p_over, bool p_by_keyboard) { return; // Already visible. } - Ref<StyleBox> style = get_theme_stylebox(SNAME("panel")); - int vsep = get_theme_constant(SNAME("v_separation")); - Point2 this_pos = get_position(); Rect2 this_rect(this_pos, get_size()); @@ -215,9 +205,14 @@ void PopupMenu::_activate_submenu(int p_over, bool p_by_keyboard) { submenu_pum->activated_by_keyboard = p_by_keyboard; - // If not triggered by the mouse, start the popup with its first item selected. - if (submenu_pum->get_item_count() > 0 && p_by_keyboard) { - submenu_pum->set_current_index(0); + // If not triggered by the mouse, start the popup with its first enabled item focused. + if (p_by_keyboard) { + for (int i = 0; i < submenu_pum->get_item_count(); i++) { + if (!submenu_pum->is_item_disabled(i)) { + submenu_pum->set_focused_item(i); + break; + } + } } submenu_pum->popup(); @@ -225,7 +220,7 @@ void PopupMenu::_activate_submenu(int p_over, bool p_by_keyboard) { // Set autohide areas. Rect2 safe_area = this_rect; - safe_area.position.y += items[p_over]._ofs_cache + scroll_offset + style->get_offset().height - vsep / 2; + safe_area.position.y += items[p_over]._ofs_cache + scroll_offset + theme_cache.panel_style->get_offset().height - theme_cache.v_separation / 2; safe_area.size.y = items[p_over]._height_cache; DisplayServer::get_singleton()->window_set_popup_safe_rect(submenu_popup->get_window_id(), safe_area); @@ -234,11 +229,11 @@ void PopupMenu::_activate_submenu(int p_over, bool p_by_keyboard) { // Autohide area above the submenu item. submenu_pum->clear_autohide_areas(); - submenu_pum->add_autohide_area(Rect2(this_rect.position.x, this_rect.position.y, this_rect.size.x, items[p_over]._ofs_cache + scroll_offset + style->get_offset().height - vsep / 2)); + submenu_pum->add_autohide_area(Rect2(this_rect.position.x, this_rect.position.y, this_rect.size.x, items[p_over]._ofs_cache + scroll_offset + theme_cache.panel_style->get_offset().height - theme_cache.v_separation / 2)); // If there is an area below the submenu item, add an autohide area there. if (items[p_over]._ofs_cache + items[p_over]._height_cache + scroll_offset <= control->get_size().height) { - int from = items[p_over]._ofs_cache + items[p_over]._height_cache + scroll_offset + vsep / 2 + style->get_offset().height; + int from = items[p_over]._ofs_cache + items[p_over]._height_cache + scroll_offset + theme_cache.v_separation / 2 + theme_cache.panel_style->get_offset().height; submenu_pum->add_autohide_area(Rect2(this_rect.position.x, this_rect.position.y + from, this_rect.size.x, this_rect.size.y - from)); } } @@ -277,89 +272,104 @@ void PopupMenu::_submenu_timeout() { void PopupMenu::gui_input(const Ref<InputEvent> &p_event) { ERR_FAIL_COND(p_event.is_null()); - if (p_event->is_action("ui_down") && p_event->is_pressed()) { - int search_from = mouse_over + 1; - if (search_from >= items.size()) { - search_from = 0; - } - - bool match_found = false; - for (int i = search_from; i < items.size(); i++) { - if (!items[i].separator && !items[i].disabled) { - mouse_over = i; - emit_signal(SNAME("id_focused"), i); - scroll_to_item(i); - control->update(); - set_input_as_handled(); - match_found = true; - break; + if (!items.is_empty()) { + if (p_event->is_action("ui_down") && p_event->is_pressed()) { + int search_from = mouse_over + 1; + if (search_from >= items.size()) { + search_from = 0; } - } - if (!match_found) { - // If the last item is not selectable, try re-searching from the start. - for (int i = 0; i < search_from; i++) { + bool match_found = false; + for (int i = search_from; i < items.size(); i++) { if (!items[i].separator && !items[i].disabled) { mouse_over = i; emit_signal(SNAME("id_focused"), i); scroll_to_item(i); - control->update(); + control->queue_redraw(); set_input_as_handled(); + match_found = true; break; } } - } - } else if (p_event->is_action("ui_up") && p_event->is_pressed()) { - int search_from = mouse_over - 1; - if (search_from < 0) { - search_from = items.size() - 1; - } - bool match_found = false; - for (int i = search_from; i >= 0; i--) { - if (!items[i].separator && !items[i].disabled) { - mouse_over = i; - emit_signal(SNAME("id_focused"), i); - scroll_to_item(i); - control->update(); - set_input_as_handled(); - match_found = true; - break; + if (!match_found) { + // If the last item is not selectable, try re-searching from the start. + for (int i = 0; i < search_from; i++) { + if (!items[i].separator && !items[i].disabled) { + mouse_over = i; + emit_signal(SNAME("id_focused"), i); + scroll_to_item(i); + control->queue_redraw(); + set_input_as_handled(); + break; + } + } + } + } else if (p_event->is_action("ui_up") && p_event->is_pressed()) { + int search_from = mouse_over - 1; + if (search_from < 0) { + search_from = items.size() - 1; } - } - if (!match_found) { - // If the first item is not selectable, try re-searching from the end. - for (int i = items.size() - 1; i >= search_from; i--) { + bool match_found = false; + for (int i = search_from; i >= 0; i--) { if (!items[i].separator && !items[i].disabled) { mouse_over = i; emit_signal(SNAME("id_focused"), i); scroll_to_item(i); - control->update(); + control->queue_redraw(); set_input_as_handled(); + match_found = true; break; } } - } - } else if (p_event->is_action("ui_left") && p_event->is_pressed()) { - Node *n = get_parent(); - if (n && Object::cast_to<PopupMenu>(n)) { - hide(); - set_input_as_handled(); - } - } else if (p_event->is_action("ui_right") && p_event->is_pressed()) { - if (mouse_over >= 0 && mouse_over < items.size() && !items[mouse_over].separator && !items[mouse_over].submenu.is_empty() && submenu_over != mouse_over) { - _activate_submenu(mouse_over, true); - set_input_as_handled(); - } - } else if (p_event->is_action("ui_accept") && p_event->is_pressed()) { - if (mouse_over >= 0 && mouse_over < items.size() && !items[mouse_over].separator) { - if (!items[mouse_over].submenu.is_empty() && submenu_over != mouse_over) { + + if (!match_found) { + // If the first item is not selectable, try re-searching from the end. + for (int i = items.size() - 1; i >= search_from; i--) { + if (!items[i].separator && !items[i].disabled) { + mouse_over = i; + emit_signal(SNAME("id_focused"), i); + scroll_to_item(i); + control->queue_redraw(); + set_input_as_handled(); + break; + } + } + } + } else if (p_event->is_action("ui_left") && p_event->is_pressed()) { + Node *n = get_parent(); + if (n) { + if (Object::cast_to<PopupMenu>(n)) { + hide(); + set_input_as_handled(); + } else if (Object::cast_to<MenuBar>(n)) { + Object::cast_to<MenuBar>(n)->gui_input(p_event); + set_input_as_handled(); + return; + } + } + } else if (p_event->is_action("ui_right") && p_event->is_pressed()) { + if (mouse_over >= 0 && mouse_over < items.size() && !items[mouse_over].separator && !items[mouse_over].submenu.is_empty() && submenu_over != mouse_over) { _activate_submenu(mouse_over, true); + set_input_as_handled(); } else { - activate_item(mouse_over); + Node *n = get_parent(); + if (n && Object::cast_to<MenuBar>(n)) { + Object::cast_to<MenuBar>(n)->gui_input(p_event); + set_input_as_handled(); + return; + } + } + } else if (p_event->is_action("ui_accept") && p_event->is_pressed()) { + if (mouse_over >= 0 && mouse_over < items.size() && !items[mouse_over].separator) { + if (!items[mouse_over].submenu.is_empty() && submenu_over != mouse_over) { + _activate_submenu(mouse_over, true); + } else { + activate_item(mouse_over); + } + set_input_as_handled(); } - set_input_as_handled(); } } @@ -421,7 +431,7 @@ void PopupMenu::gui_input(const Ref<InputEvent> &p_event) { Ref<InputEventMouseMotion> m = p_event; if (m.is_valid()) { - if (m->get_velocity().is_equal_approx(Vector2())) { + if (m->get_velocity().is_zero_approx()) { return; } activated_by_keyboard = false; @@ -442,7 +452,7 @@ void PopupMenu::gui_input(const Ref<InputEvent> &p_event) { if (id < 0) { mouse_over = -1; - control->update(); + control->queue_redraw(); return; } @@ -453,7 +463,7 @@ void PopupMenu::gui_input(const Ref<InputEvent> &p_event) { if (over != mouse_over) { mouse_over = over; - control->update(); + control->queue_redraw(); } } @@ -490,7 +500,7 @@ void PopupMenu::gui_input(const Ref<InputEvent> &p_event) { mouse_over = i; emit_signal(SNAME("id_focused"), i); scroll_to_item(i); - control->update(); + control->queue_redraw(); set_input_as_handled(); break; } @@ -507,34 +517,17 @@ void PopupMenu::_draw_items() { margin_size.height = margin_container->get_theme_constant(SNAME("margin_top")) + margin_container->get_theme_constant(SNAME("margin_bottom")); // Space between the item content and the sides of popup menu. - int item_start_padding = get_theme_constant(SNAME("item_start_padding")); - int item_end_padding = get_theme_constant(SNAME("item_end_padding")); - bool rtl = control->is_layout_rtl(); - Ref<StyleBox> style = get_theme_stylebox(SNAME("panel")); - Ref<StyleBox> hover = get_theme_stylebox(SNAME("hover")); - // In Item::checkable_type enum order (less the non-checkable member). - Ref<Texture2D> check[] = { get_theme_icon(SNAME("checked")), get_theme_icon(SNAME("radio_checked")) }; - Ref<Texture2D> uncheck[] = { get_theme_icon(SNAME("unchecked")), get_theme_icon(SNAME("radio_unchecked")) }; + // In Item::checkable_type enum order (less the non-checkable member), with disabled repeated at the end. + Ref<Texture2D> check[] = { theme_cache.checked, theme_cache.radio_checked, theme_cache.checked_disabled, theme_cache.radio_checked_disabled }; + Ref<Texture2D> uncheck[] = { theme_cache.unchecked, theme_cache.radio_unchecked, theme_cache.unchecked_disabled, theme_cache.radio_unchecked_disabled }; Ref<Texture2D> submenu; if (rtl) { - submenu = get_theme_icon(SNAME("submenu_mirrored")); + submenu = theme_cache.submenu_mirrored; } else { - submenu = get_theme_icon(SNAME("submenu")); + submenu = theme_cache.submenu; } - Ref<StyleBox> separator = get_theme_stylebox(SNAME("separator")); - Ref<StyleBox> labeled_separator_left = get_theme_stylebox(SNAME("labeled_separator_left")); - Ref<StyleBox> labeled_separator_right = get_theme_stylebox(SNAME("labeled_separator_right")); - - int vseparation = get_theme_constant(SNAME("v_separation")); - int hseparation = get_theme_constant(SNAME("h_separation")); - Color font_color = get_theme_color(SNAME("font_color")); - Color font_disabled_color = get_theme_color(SNAME("font_disabled_color")); - Color font_accelerator_color = get_theme_color(SNAME("font_accelerator_color")); - Color font_hover_color = get_theme_color(SNAME("font_hover_color")); - Color font_separator_color = get_theme_color(SNAME("font_separator_color")); - float scroll_width = scroll_container->get_v_scroll_bar()->is_visible_in_tree() ? scroll_container->get_v_scroll_bar()->get_size().width : 0; float display_width = control->get_size().width - scroll_width; @@ -553,12 +546,16 @@ void PopupMenu::_draw_items() { } } if (icon_ofs > 0.0) { - icon_ofs += hseparation; + icon_ofs += theme_cache.h_separation; } float check_ofs = 0.0; if (has_check) { - check_ofs = MAX(get_theme_icon(SNAME("checked"))->get_width(), get_theme_icon(SNAME("radio_checked"))->get_width()) + hseparation; + for (int i = 0; i < 4; i++) { + check_ofs = MAX(check_ofs, check[i]->get_width()); + check_ofs = MAX(check_ofs, uncheck[i]->get_width()); + } + check_ofs += theme_cache.h_separation; } Point2 ofs = Point2(); @@ -566,7 +563,7 @@ void PopupMenu::_draw_items() { // Loop through all items and draw each. for (int i = 0; i < items.size(); i++) { // For the first item only add half a separation. For all other items, add a whole separation to the offset. - ofs.y += i > 0 ? vseparation : (float)vseparation / 2; + ofs.y += i > 0 ? theme_cache.v_separation : (float)theme_cache.v_separation / 2; _shape_item(i); @@ -576,51 +573,52 @@ void PopupMenu::_draw_items() { if (i == mouse_over) { if (rtl) { - hover->draw(ci, Rect2(item_ofs + Point2(scroll_width, -vseparation / 2), Size2(display_width, h + vseparation))); + theme_cache.hover_style->draw(ci, Rect2(item_ofs + Point2(scroll_width, -theme_cache.v_separation / 2), Size2(display_width, h + theme_cache.v_separation))); } else { - hover->draw(ci, Rect2(item_ofs + Point2(0, -vseparation / 2), Size2(display_width, h + vseparation))); + theme_cache.hover_style->draw(ci, Rect2(item_ofs + Point2(0, -theme_cache.v_separation / 2), Size2(display_width, h + theme_cache.v_separation))); } } String text = items[i].xl_text; // Separator - item_ofs.x += items[i].h_ofs; + item_ofs.x += items[i].indent * theme_cache.indent; if (items[i].separator) { if (!text.is_empty() || !items[i].icon.is_null()) { - int content_size = items[i].text_buf->get_size().width + hseparation * 2; + int content_size = items[i].text_buf->get_size().width + theme_cache.h_separation * 2; if (!items[i].icon.is_null()) { - content_size += icon_size.width + hseparation; + content_size += icon_size.width + theme_cache.h_separation; } int content_center = display_width / 2; int content_left = content_center - content_size / 2; int content_right = content_center + content_size / 2; if (content_left > item_ofs.x) { - int sep_h = labeled_separator_left->get_center_size().height + labeled_separator_left->get_minimum_size().height; + int sep_h = theme_cache.labeled_separator_left->get_center_size().height + theme_cache.labeled_separator_left->get_minimum_size().height; int sep_ofs = Math::floor((h - sep_h) / 2.0); - labeled_separator_left->draw(ci, Rect2(item_ofs + Point2(0, sep_ofs), Size2(MAX(0, content_left - item_ofs.x), sep_h))); + theme_cache.labeled_separator_left->draw(ci, Rect2(item_ofs + Point2(0, sep_ofs), Size2(MAX(0, content_left - item_ofs.x), sep_h))); } if (content_right < display_width) { - int sep_h = labeled_separator_right->get_center_size().height + labeled_separator_right->get_minimum_size().height; + int sep_h = theme_cache.labeled_separator_right->get_center_size().height + theme_cache.labeled_separator_right->get_minimum_size().height; int sep_ofs = Math::floor((h - sep_h) / 2.0); - labeled_separator_right->draw(ci, Rect2(Point2(content_right, item_ofs.y + sep_ofs), Size2(MAX(0, display_width - content_right), sep_h))); + theme_cache.labeled_separator_right->draw(ci, Rect2(Point2(content_right, item_ofs.y + sep_ofs), Size2(MAX(0, display_width - content_right), sep_h))); } } else { - int sep_h = separator->get_center_size().height + separator->get_minimum_size().height; + int sep_h = theme_cache.separator_style->get_center_size().height + theme_cache.separator_style->get_minimum_size().height; int sep_ofs = Math::floor((h - sep_h) / 2.0); - separator->draw(ci, Rect2(item_ofs + Point2(0, sep_ofs), Size2(display_width, sep_h))); + theme_cache.separator_style->draw(ci, Rect2(item_ofs + Point2(0, sep_ofs), Size2(display_width, sep_h))); } } Color icon_color(1, 1, 1, items[i].disabled && !items[i].separator ? 0.5 : 1); // For non-separator items, add some padding for the content. - item_ofs.x += item_start_padding; + item_ofs.x += theme_cache.item_start_padding; // Checkboxes if (items[i].checkable_type && !items[i].separator) { - Texture2D *icon = (items[i].checked ? check[items[i].checkable_type - 1] : uncheck[items[i].checkable_type - 1]).ptr(); + int disabled = int(items[i].disabled) * 2; + Texture2D *icon = (items[i].checked ? check[items[i].checkable_type - 1 + disabled] : uncheck[items[i].checkable_type - 1 + disabled]).ptr(); if (rtl) { icon->draw(ci, Size2(control->get_size().width - item_ofs.x - icon->get_width(), item_ofs.y) + Point2(0, Math::floor((h - icon->get_height()) / 2.0)), icon_color); } else { @@ -633,7 +631,7 @@ void PopupMenu::_draw_items() { // Icon if (!items[i].icon.is_null()) { if (items[i].separator) { - separator_ofs -= (icon_size.width + hseparation) / 2; + separator_ofs -= (icon_size.width + theme_cache.h_separation) / 2; if (rtl) { items[i].icon->draw(ci, Size2(control->get_size().width - item_ofs.x - separator_ofs - icon_size.width, item_ofs.y) + Point2(0, Math::floor((h - icon_size.height) / 2.0)), icon_color); @@ -652,61 +650,55 @@ void PopupMenu::_draw_items() { // Submenu arrow on right hand side. if (!items[i].submenu.is_empty()) { if (rtl) { - submenu->draw(ci, Point2(scroll_width + style->get_margin(SIDE_LEFT) + item_end_padding, item_ofs.y + Math::floor(h - submenu->get_height()) / 2), icon_color); + submenu->draw(ci, Point2(scroll_width + theme_cache.panel_style->get_margin(SIDE_LEFT) + theme_cache.item_end_padding, item_ofs.y + Math::floor(h - submenu->get_height()) / 2), icon_color); } else { - submenu->draw(ci, Point2(display_width - style->get_margin(SIDE_RIGHT) - submenu->get_width() - item_end_padding, item_ofs.y + Math::floor(h - submenu->get_height()) / 2), icon_color); + submenu->draw(ci, Point2(display_width - theme_cache.panel_style->get_margin(SIDE_RIGHT) - submenu->get_width() - theme_cache.item_end_padding, item_ofs.y + Math::floor(h - submenu->get_height()) / 2), icon_color); } } - Color font_outline_color = get_theme_color(SNAME("font_outline_color")); - int outline_size = get_theme_constant(SNAME("outline_size")); - // Text if (items[i].separator) { - Color font_separator_outline_color = get_theme_color(SNAME("font_separator_outline_color")); - int separator_outline_size = get_theme_constant(SNAME("separator_outline_size")); - if (!text.is_empty()) { Vector2 text_pos = Point2(separator_ofs, item_ofs.y + Math::floor((h - items[i].text_buf->get_size().y) / 2.0)); if (!rtl && !items[i].icon.is_null()) { - text_pos.x += icon_size.width + hseparation; + text_pos.x += icon_size.width + theme_cache.h_separation; } - if (separator_outline_size > 0 && font_separator_outline_color.a > 0) { - items[i].text_buf->draw_outline(ci, text_pos, separator_outline_size, font_separator_outline_color); + if (theme_cache.font_separator_outline_size > 0 && theme_cache.font_separator_outline_color.a > 0) { + items[i].text_buf->draw_outline(ci, text_pos, theme_cache.font_separator_outline_size, theme_cache.font_separator_outline_color); } - items[i].text_buf->draw(ci, text_pos, font_separator_color); + items[i].text_buf->draw(ci, text_pos, theme_cache.font_separator_color); } } else { item_ofs.x += icon_ofs + check_ofs; if (rtl) { Vector2 text_pos = Size2(control->get_size().width - items[i].text_buf->get_size().width - item_ofs.x, item_ofs.y) + Point2(0, Math::floor((h - items[i].text_buf->get_size().y) / 2.0)); - if (outline_size > 0 && font_outline_color.a > 0) { - items[i].text_buf->draw_outline(ci, text_pos, outline_size, font_outline_color); + if (theme_cache.font_outline_size > 0 && theme_cache.font_outline_color.a > 0) { + items[i].text_buf->draw_outline(ci, text_pos, theme_cache.font_outline_size, theme_cache.font_outline_color); } - items[i].text_buf->draw(ci, text_pos, items[i].disabled ? font_disabled_color : (i == mouse_over ? font_hover_color : font_color)); + items[i].text_buf->draw(ci, text_pos, items[i].disabled ? theme_cache.font_disabled_color : (i == mouse_over ? theme_cache.font_hover_color : theme_cache.font_color)); } else { Vector2 text_pos = item_ofs + Point2(0, Math::floor((h - items[i].text_buf->get_size().y) / 2.0)); - if (outline_size > 0 && font_outline_color.a > 0) { - items[i].text_buf->draw_outline(ci, text_pos, outline_size, font_outline_color); + if (theme_cache.font_outline_size > 0 && theme_cache.font_outline_color.a > 0) { + items[i].text_buf->draw_outline(ci, text_pos, theme_cache.font_outline_size, theme_cache.font_outline_color); } - items[i].text_buf->draw(ci, text_pos, items[i].disabled ? font_disabled_color : (i == mouse_over ? font_hover_color : font_color)); + items[i].text_buf->draw(ci, text_pos, items[i].disabled ? theme_cache.font_disabled_color : (i == mouse_over ? theme_cache.font_hover_color : theme_cache.font_color)); } } // Accelerator / Shortcut if (items[i].accel != Key::NONE || (items[i].shortcut.is_valid() && items[i].shortcut->has_valid_event())) { if (rtl) { - item_ofs.x = scroll_width + style->get_margin(SIDE_LEFT) + item_end_padding; + item_ofs.x = scroll_width + theme_cache.panel_style->get_margin(SIDE_LEFT) + theme_cache.item_end_padding; } else { - item_ofs.x = display_width - style->get_margin(SIDE_RIGHT) - items[i].accel_text_buf->get_size().x - item_end_padding; + item_ofs.x = display_width - theme_cache.panel_style->get_margin(SIDE_RIGHT) - items[i].accel_text_buf->get_size().x - theme_cache.item_end_padding; } Vector2 text_pos = item_ofs + Point2(0, Math::floor((h - items[i].text_buf->get_size().y) / 2.0)); - if (outline_size > 0 && font_outline_color.a > 0) { - items[i].accel_text_buf->draw_outline(ci, text_pos, outline_size, font_outline_color); + if (theme_cache.font_outline_size > 0 && theme_cache.font_outline_color.a > 0) { + items[i].accel_text_buf->draw_outline(ci, text_pos, theme_cache.font_outline_size, theme_cache.font_outline_color); } - items[i].accel_text_buf->draw(ci, text_pos, i == mouse_over ? font_hover_color : font_accelerator_color); + items[i].accel_text_buf->draw(ci, text_pos, i == mouse_over ? theme_cache.font_hover_color : theme_cache.font_accelerator_color); } // Cache the item vertical offset from the first item and the height. @@ -718,9 +710,8 @@ void PopupMenu::_draw_items() { } void PopupMenu::_draw_background() { - Ref<StyleBox> style = get_theme_stylebox(SNAME("panel")); RID ci2 = margin_container->get_canvas_item(); - style->draw(ci2, Rect2(Point2(), margin_container->get_size())); + theme_cache.panel_style->draw(ci2, Rect2(Point2(), margin_container->get_size())); } void PopupMenu::_minimum_lifetime_timeout() { @@ -752,8 +743,8 @@ void PopupMenu::_shape_item(int p_item) { if (items.write[p_item].dirty) { items.write[p_item].text_buf->clear(); - Ref<Font> font = get_theme_font(items[p_item].separator ? SNAME("font_separator") : SNAME("font")); - int font_size = get_theme_font_size(items[p_item].separator ? SNAME("font_separator_size") : SNAME("font_size")); + Ref<Font> font = items[p_item].separator ? theme_cache.font_separator : theme_cache.font; + int font_size = items[p_item].separator ? theme_cache.font_separator_size : theme_cache.font_size; if (items[p_item].text_direction == Control::TEXT_DIRECTION_INHERITED) { items.write[p_item].text_buf->set_direction(is_layout_rtl() ? TextServer::DIRECTION_RTL : TextServer::DIRECTION_LTR); @@ -769,6 +760,77 @@ void PopupMenu::_shape_item(int p_item) { } } +void PopupMenu::_menu_changed() { + emit_signal(SNAME("menu_changed")); +} + +void PopupMenu::add_child_notify(Node *p_child) { + Window::add_child_notify(p_child); + + PopupMenu *pm = Object::cast_to<PopupMenu>(p_child); + if (!pm) { + return; + } + p_child->connect("menu_changed", callable_mp(this, &PopupMenu::_menu_changed)); + _menu_changed(); +} + +void PopupMenu::remove_child_notify(Node *p_child) { + Window::remove_child_notify(p_child); + + PopupMenu *pm = Object::cast_to<PopupMenu>(p_child); + if (!pm) { + return; + } + p_child->disconnect("menu_changed", callable_mp(this, &PopupMenu::_menu_changed)); + _menu_changed(); +} + +void PopupMenu::_update_theme_item_cache() { + Popup::_update_theme_item_cache(); + + theme_cache.panel_style = get_theme_stylebox(SNAME("panel")); + theme_cache.hover_style = get_theme_stylebox(SNAME("hover")); + + theme_cache.separator_style = get_theme_stylebox(SNAME("separator")); + theme_cache.labeled_separator_left = get_theme_stylebox(SNAME("labeled_separator_left")); + theme_cache.labeled_separator_right = get_theme_stylebox(SNAME("labeled_separator_right")); + + theme_cache.v_separation = get_theme_constant(SNAME("v_separation")); + theme_cache.h_separation = get_theme_constant(SNAME("h_separation")); + theme_cache.indent = get_theme_constant(SNAME("indent")); + theme_cache.item_start_padding = get_theme_constant(SNAME("item_start_padding")); + theme_cache.item_end_padding = get_theme_constant(SNAME("item_end_padding")); + + theme_cache.checked = get_theme_icon(SNAME("checked")); + theme_cache.checked_disabled = get_theme_icon(SNAME("checked_disabled")); + theme_cache.unchecked = get_theme_icon(SNAME("unchecked")); + theme_cache.unchecked_disabled = get_theme_icon(SNAME("unchecked_disabled")); + theme_cache.radio_checked = get_theme_icon(SNAME("radio_checked")); + theme_cache.radio_checked_disabled = get_theme_icon(SNAME("radio_checked_disabled")); + theme_cache.radio_unchecked = get_theme_icon(SNAME("radio_unchecked")); + theme_cache.radio_unchecked_disabled = get_theme_icon(SNAME("radio_unchecked_disabled")); + + theme_cache.submenu = get_theme_icon(SNAME("submenu")); + theme_cache.submenu_mirrored = get_theme_icon(SNAME("submenu_mirrored")); + + theme_cache.font = get_theme_font(SNAME("font")); + theme_cache.font_size = get_theme_font_size(SNAME("font_size")); + theme_cache.font_separator = get_theme_font(SNAME("font_separator")); + theme_cache.font_separator_size = get_theme_font_size(SNAME("font_separator_size")); + + theme_cache.font_color = get_theme_color(SNAME("font_color")); + theme_cache.font_hover_color = get_theme_color(SNAME("font_hover_color")); + theme_cache.font_disabled_color = get_theme_color(SNAME("font_disabled_color")); + theme_cache.font_accelerator_color = get_theme_color(SNAME("font_accelerator_color")); + theme_cache.font_outline_size = get_theme_constant(SNAME("outline_size")); + theme_cache.font_outline_color = get_theme_color(SNAME("font_outline_color")); + + theme_cache.font_separator_color = get_theme_color(SNAME("font_separator_color")); + theme_cache.font_separator_outline_size = get_theme_constant(SNAME("separator_outline_size")); + theme_cache.font_separator_outline_color = get_theme_color(SNAME("font_separator_outline_color")); +} + void PopupMenu::_notification(int p_what) { switch (p_what) { case NOTIFICATION_ENTER_TREE: { @@ -790,7 +852,8 @@ void PopupMenu::_notification(int p_what) { } child_controls_changed(); - control->update(); + _menu_changed(); + control->queue_redraw(); } break; case NOTIFICATION_WM_MOUSE_ENTER: { @@ -800,7 +863,7 @@ void PopupMenu::_notification(int p_what) { case NOTIFICATION_WM_MOUSE_EXIT: { if (mouse_over >= 0 && (items[mouse_over].submenu.is_empty() || submenu_over != -1)) { mouse_over = -1; - control->update(); + control->queue_redraw(); } } break; @@ -828,7 +891,7 @@ void PopupMenu::_notification(int p_what) { if (!is_visible()) { if (mouse_over >= 0) { mouse_over = -1; - control->update(); + control->queue_redraw(); } for (int i = 0; i < items.size(); i++) { @@ -856,11 +919,10 @@ void PopupMenu::_notification(int p_what) { } // Set margin on the margin container - Ref<StyleBox> panel_style = get_theme_stylebox(SNAME("panel")); - margin_container->add_theme_constant_override("margin_left", panel_style->get_margin(Side::SIDE_LEFT)); - margin_container->add_theme_constant_override("margin_top", panel_style->get_margin(Side::SIDE_TOP)); - margin_container->add_theme_constant_override("margin_right", panel_style->get_margin(Side::SIDE_RIGHT)); - margin_container->add_theme_constant_override("margin_bottom", panel_style->get_margin(Side::SIDE_BOTTOM)); + margin_container->add_theme_constant_override("margin_left", theme_cache.panel_style->get_margin(Side::SIDE_LEFT)); + margin_container->add_theme_constant_override("margin_top", theme_cache.panel_style->get_margin(Side::SIDE_TOP)); + margin_container->add_theme_constant_override("margin_right", theme_cache.panel_style->get_margin(Side::SIDE_RIGHT)); + margin_container->add_theme_constant_override("margin_bottom", theme_cache.panel_style->get_margin(Side::SIDE_BOTTOM)); } } break; } @@ -881,9 +943,10 @@ void PopupMenu::add_item(const String &p_label, int p_id, Key p_accel) { ITEM_SETUP_WITH_ACCEL(p_label, p_id, p_accel); items.push_back(item); _shape_item(items.size() - 1); - control->update(); + control->queue_redraw(); child_controls_changed(); notify_property_list_changed(); + _menu_changed(); } void PopupMenu::add_icon_item(const Ref<Texture2D> &p_icon, const String &p_label, int p_id, Key p_accel) { @@ -892,9 +955,10 @@ void PopupMenu::add_icon_item(const Ref<Texture2D> &p_icon, const String &p_labe item.icon = p_icon; items.push_back(item); _shape_item(items.size() - 1); - control->update(); + control->queue_redraw(); child_controls_changed(); notify_property_list_changed(); + _menu_changed(); } void PopupMenu::add_check_item(const String &p_label, int p_id, Key p_accel) { @@ -903,8 +967,9 @@ void PopupMenu::add_check_item(const String &p_label, int p_id, Key p_accel) { item.checkable_type = Item::CHECKABLE_TYPE_CHECK_BOX; items.push_back(item); _shape_item(items.size() - 1); - control->update(); + control->queue_redraw(); child_controls_changed(); + _menu_changed(); } void PopupMenu::add_icon_check_item(const Ref<Texture2D> &p_icon, const String &p_label, int p_id, Key p_accel) { @@ -914,7 +979,7 @@ void PopupMenu::add_icon_check_item(const Ref<Texture2D> &p_icon, const String & item.checkable_type = Item::CHECKABLE_TYPE_CHECK_BOX; items.push_back(item); _shape_item(items.size() - 1); - control->update(); + control->queue_redraw(); child_controls_changed(); } @@ -924,8 +989,9 @@ void PopupMenu::add_radio_check_item(const String &p_label, int p_id, Key p_acce item.checkable_type = Item::CHECKABLE_TYPE_RADIO_BUTTON; items.push_back(item); _shape_item(items.size() - 1); - control->update(); + control->queue_redraw(); child_controls_changed(); + _menu_changed(); } void PopupMenu::add_icon_radio_check_item(const Ref<Texture2D> &p_icon, const String &p_label, int p_id, Key p_accel) { @@ -935,8 +1001,9 @@ void PopupMenu::add_icon_radio_check_item(const Ref<Texture2D> &p_icon, const St item.checkable_type = Item::CHECKABLE_TYPE_RADIO_BUTTON; items.push_back(item); _shape_item(items.size() - 1); - control->update(); + control->queue_redraw(); child_controls_changed(); + _menu_changed(); } void PopupMenu::add_multistate_item(const String &p_label, int p_max_states, int p_default_state, int p_id, Key p_accel) { @@ -946,8 +1013,9 @@ void PopupMenu::add_multistate_item(const String &p_label, int p_max_states, int item.state = p_default_state; items.push_back(item); _shape_item(items.size() - 1); - control->update(); + control->queue_redraw(); child_controls_changed(); + _menu_changed(); } #define ITEM_SETUP_WITH_SHORTCUT(p_shortcut, p_id, p_global) \ @@ -964,8 +1032,9 @@ void PopupMenu::add_shortcut(const Ref<Shortcut> &p_shortcut, int p_id, bool p_g ITEM_SETUP_WITH_SHORTCUT(p_shortcut, p_id, p_global); items.push_back(item); _shape_item(items.size() - 1); - control->update(); + control->queue_redraw(); child_controls_changed(); + _menu_changed(); } void PopupMenu::add_icon_shortcut(const Ref<Texture2D> &p_icon, const Ref<Shortcut> &p_shortcut, int p_id, bool p_global) { @@ -974,8 +1043,9 @@ void PopupMenu::add_icon_shortcut(const Ref<Texture2D> &p_icon, const Ref<Shortc item.icon = p_icon; items.push_back(item); _shape_item(items.size() - 1); - control->update(); + control->queue_redraw(); child_controls_changed(); + _menu_changed(); } void PopupMenu::add_check_shortcut(const Ref<Shortcut> &p_shortcut, int p_id, bool p_global) { @@ -984,8 +1054,9 @@ void PopupMenu::add_check_shortcut(const Ref<Shortcut> &p_shortcut, int p_id, bo item.checkable_type = Item::CHECKABLE_TYPE_CHECK_BOX; items.push_back(item); _shape_item(items.size() - 1); - control->update(); + control->queue_redraw(); child_controls_changed(); + _menu_changed(); } void PopupMenu::add_icon_check_shortcut(const Ref<Texture2D> &p_icon, const Ref<Shortcut> &p_shortcut, int p_id, bool p_global) { @@ -995,8 +1066,9 @@ void PopupMenu::add_icon_check_shortcut(const Ref<Texture2D> &p_icon, const Ref< item.checkable_type = Item::CHECKABLE_TYPE_CHECK_BOX; items.push_back(item); _shape_item(items.size() - 1); - control->update(); + control->queue_redraw(); child_controls_changed(); + _menu_changed(); } void PopupMenu::add_radio_check_shortcut(const Ref<Shortcut> &p_shortcut, int p_id, bool p_global) { @@ -1005,8 +1077,9 @@ void PopupMenu::add_radio_check_shortcut(const Ref<Shortcut> &p_shortcut, int p_ item.checkable_type = Item::CHECKABLE_TYPE_RADIO_BUTTON; items.push_back(item); _shape_item(items.size() - 1); - control->update(); + control->queue_redraw(); child_controls_changed(); + _menu_changed(); } void PopupMenu::add_icon_radio_check_shortcut(const Ref<Texture2D> &p_icon, const Ref<Shortcut> &p_shortcut, int p_id, bool p_global) { @@ -1016,8 +1089,9 @@ void PopupMenu::add_icon_radio_check_shortcut(const Ref<Texture2D> &p_icon, cons item.checkable_type = Item::CHECKABLE_TYPE_RADIO_BUTTON; items.push_back(item); _shape_item(items.size() - 1); - control->update(); + control->queue_redraw(); child_controls_changed(); + _menu_changed(); } void PopupMenu::add_submenu_item(const String &p_label, const String &p_submenu, int p_id) { @@ -1028,8 +1102,9 @@ void PopupMenu::add_submenu_item(const String &p_label, const String &p_submenu, item.submenu = p_submenu; items.push_back(item); _shape_item(items.size() - 1); - control->update(); + control->queue_redraw(); child_controls_changed(); + _menu_changed(); } #undef ITEM_SETUP_WITH_ACCEL @@ -1050,8 +1125,9 @@ void PopupMenu::set_item_text(int p_idx, const String &p_text) { items.write[p_idx].dirty = true; _shape_item(p_idx); - control->update(); + control->queue_redraw(); child_controls_changed(); + _menu_changed(); } void PopupMenu::set_item_text_direction(int p_item, Control::TextDirection p_text_direction) { @@ -1063,7 +1139,7 @@ void PopupMenu::set_item_text_direction(int p_item, Control::TextDirection p_tex if (items[p_item].text_direction != p_text_direction) { items.write[p_item].text_direction = p_text_direction; items.write[p_item].dirty = true; - control->update(); + control->queue_redraw(); } } @@ -1075,7 +1151,7 @@ void PopupMenu::set_item_language(int p_item, const String &p_language) { if (items[p_item].language != p_language) { items.write[p_item].language = p_language; items.write[p_item].dirty = true; - control->update(); + control->queue_redraw(); } } @@ -1084,10 +1160,16 @@ void PopupMenu::set_item_icon(int p_idx, const Ref<Texture2D> &p_icon) { p_idx += get_item_count(); } ERR_FAIL_INDEX(p_idx, items.size()); + + if (items[p_idx].icon == p_icon) { + return; + } + items.write[p_idx].icon = p_icon; - control->update(); + control->queue_redraw(); child_controls_changed(); + _menu_changed(); } void PopupMenu::set_item_checked(int p_idx, bool p_checked) { @@ -1096,10 +1178,15 @@ void PopupMenu::set_item_checked(int p_idx, bool p_checked) { } ERR_FAIL_INDEX(p_idx, items.size()); + if (items[p_idx].checked == p_checked) { + return; + } + items.write[p_idx].checked = p_checked; - control->update(); + control->queue_redraw(); child_controls_changed(); + _menu_changed(); } void PopupMenu::set_item_id(int p_idx, int p_id) { @@ -1107,10 +1194,16 @@ void PopupMenu::set_item_id(int p_idx, int p_id) { p_idx += get_item_count(); } ERR_FAIL_INDEX(p_idx, items.size()); + + if (items[p_idx].id == p_id) { + return; + } + items.write[p_idx].id = p_id; - control->update(); + control->queue_redraw(); child_controls_changed(); + _menu_changed(); } void PopupMenu::set_item_accelerator(int p_idx, Key p_accel) { @@ -1118,11 +1211,17 @@ void PopupMenu::set_item_accelerator(int p_idx, Key p_accel) { p_idx += get_item_count(); } ERR_FAIL_INDEX(p_idx, items.size()); + + if (items[p_idx].accel == p_accel) { + return; + } + items.write[p_idx].accel = p_accel; items.write[p_idx].dirty = true; - control->update(); + control->queue_redraw(); child_controls_changed(); + _menu_changed(); } void PopupMenu::set_item_metadata(int p_idx, const Variant &p_meta) { @@ -1130,9 +1229,15 @@ void PopupMenu::set_item_metadata(int p_idx, const Variant &p_meta) { p_idx += get_item_count(); } ERR_FAIL_INDEX(p_idx, items.size()); + + if (items[p_idx].metadata == p_meta) { + return; + } + items.write[p_idx].metadata = p_meta; - control->update(); + control->queue_redraw(); child_controls_changed(); + _menu_changed(); } void PopupMenu::set_item_disabled(int p_idx, bool p_disabled) { @@ -1140,9 +1245,15 @@ void PopupMenu::set_item_disabled(int p_idx, bool p_disabled) { p_idx += get_item_count(); } ERR_FAIL_INDEX(p_idx, items.size()); + + if (items[p_idx].disabled == p_disabled) { + return; + } + items.write[p_idx].disabled = p_disabled; - control->update(); + control->queue_redraw(); child_controls_changed(); + _menu_changed(); } void PopupMenu::set_item_submenu(int p_idx, const String &p_submenu) { @@ -1150,16 +1261,23 @@ void PopupMenu::set_item_submenu(int p_idx, const String &p_submenu) { p_idx += get_item_count(); } ERR_FAIL_INDEX(p_idx, items.size()); + + if (items[p_idx].submenu == p_submenu) { + return; + } + items.write[p_idx].submenu = p_submenu; - control->update(); + control->queue_redraw(); child_controls_changed(); + _menu_changed(); } void PopupMenu::toggle_item_checked(int p_idx) { ERR_FAIL_INDEX(p_idx, items.size()); items.write[p_idx].checked = !items[p_idx].checked; - control->update(); + control->queue_redraw(); child_controls_changed(); + _menu_changed(); } String PopupMenu::get_item_text(int p_idx) const { @@ -1242,9 +1360,14 @@ Ref<Shortcut> PopupMenu::get_item_shortcut(int p_idx) const { return items[p_idx].shortcut; } -int PopupMenu::get_item_horizontal_offset(int p_idx) const { +int PopupMenu::get_item_indent(int p_idx) const { ERR_FAIL_INDEX_V(p_idx, items.size(), 0); - return items[p_idx].h_ofs; + return items[p_idx].indent; +} + +int PopupMenu::get_item_max_states(int p_idx) const { + ERR_FAIL_INDEX_V(p_idx, items.size(), -1); + return items[p_idx].max_states; } int PopupMenu::get_item_state(int p_idx) const { @@ -1257,8 +1380,13 @@ void PopupMenu::set_item_as_separator(int p_idx, bool p_separator) { p_idx += get_item_count(); } ERR_FAIL_INDEX(p_idx, items.size()); + + if (items[p_idx].separator == p_separator) { + return; + } + items.write[p_idx].separator = p_separator; - control->update(); + control->queue_redraw(); } bool PopupMenu::is_item_separator(int p_idx) const { @@ -1271,8 +1399,15 @@ void PopupMenu::set_item_as_checkable(int p_idx, bool p_checkable) { p_idx += get_item_count(); } ERR_FAIL_INDEX(p_idx, items.size()); + + int type = (int)(p_checkable ? Item::CHECKABLE_TYPE_CHECK_BOX : Item::CHECKABLE_TYPE_NONE); + if (type == items[p_idx].checkable_type) { + return; + } + items.write[p_idx].checkable_type = p_checkable ? Item::CHECKABLE_TYPE_CHECK_BOX : Item::CHECKABLE_TYPE_NONE; - control->update(); + control->queue_redraw(); + _menu_changed(); } void PopupMenu::set_item_as_radio_checkable(int p_idx, bool p_radio_checkable) { @@ -1280,8 +1415,15 @@ void PopupMenu::set_item_as_radio_checkable(int p_idx, bool p_radio_checkable) { p_idx += get_item_count(); } ERR_FAIL_INDEX(p_idx, items.size()); + + int type = (int)(p_radio_checkable ? Item::CHECKABLE_TYPE_RADIO_BUTTON : Item::CHECKABLE_TYPE_NONE); + if (type == items[p_idx].checkable_type) { + return; + } + items.write[p_idx].checkable_type = p_radio_checkable ? Item::CHECKABLE_TYPE_RADIO_BUTTON : Item::CHECKABLE_TYPE_NONE; - control->update(); + control->queue_redraw(); + _menu_changed(); } void PopupMenu::set_item_tooltip(int p_idx, const String &p_tooltip) { @@ -1289,8 +1431,14 @@ void PopupMenu::set_item_tooltip(int p_idx, const String &p_tooltip) { p_idx += get_item_count(); } ERR_FAIL_INDEX(p_idx, items.size()); + + if (items[p_idx].tooltip == p_tooltip) { + return; + } + items.write[p_idx].tooltip = p_tooltip; - control->update(); + control->queue_redraw(); + _menu_changed(); } void PopupMenu::set_item_shortcut(int p_idx, const Ref<Shortcut> &p_shortcut, bool p_global) { @@ -1298,6 +1446,11 @@ void PopupMenu::set_item_shortcut(int p_idx, const Ref<Shortcut> &p_shortcut, bo p_idx += get_item_count(); } ERR_FAIL_INDEX(p_idx, items.size()); + + if (items[p_idx].shortcut == p_shortcut && items[p_idx].shortcut_is_global == p_global && items[p_idx].shortcut.is_valid() == p_shortcut.is_valid()) { + return; + } + if (items[p_idx].shortcut.is_valid()) { _unref_shortcut(items[p_idx].shortcut); } @@ -1309,17 +1462,24 @@ void PopupMenu::set_item_shortcut(int p_idx, const Ref<Shortcut> &p_shortcut, bo _ref_shortcut(items[p_idx].shortcut); } - control->update(); + control->queue_redraw(); + _menu_changed(); } -void PopupMenu::set_item_horizontal_offset(int p_idx, int p_offset) { +void PopupMenu::set_item_indent(int p_idx, int p_indent) { if (p_idx < 0) { p_idx += get_item_count(); } ERR_FAIL_INDEX(p_idx, items.size()); - items.write[p_idx].h_ofs = p_offset; - control->update(); + + if (items.write[p_idx].indent == p_indent) { + return; + } + items.write[p_idx].indent = p_indent; + + control->queue_redraw(); child_controls_changed(); + _menu_changed(); } void PopupMenu::set_item_multistate(int p_idx, int p_state) { @@ -1327,8 +1487,14 @@ void PopupMenu::set_item_multistate(int p_idx, int p_state) { p_idx += get_item_count(); } ERR_FAIL_INDEX(p_idx, items.size()); + + if (items[p_idx].state == p_state) { + return; + } + items.write[p_idx].state = p_state; - control->update(); + control->queue_redraw(); + _menu_changed(); } void PopupMenu::set_item_shortcut_disabled(int p_idx, bool p_disabled) { @@ -1336,8 +1502,14 @@ void PopupMenu::set_item_shortcut_disabled(int p_idx, bool p_disabled) { p_idx += get_item_count(); } ERR_FAIL_INDEX(p_idx, items.size()); + + if (items[p_idx].shortcut_is_disabled == p_disabled) { + return; + } + items.write[p_idx].shortcut_is_disabled = p_disabled; - control->update(); + control->queue_redraw(); + _menu_changed(); } void PopupMenu::toggle_item_multistate(int p_idx) { @@ -1351,7 +1523,8 @@ void PopupMenu::toggle_item_multistate(int p_idx) { items.write[p_idx].state = 0; } - control->update(); + control->queue_redraw(); + _menu_changed(); } bool PopupMenu::is_item_checkable(int p_idx) const { @@ -1364,25 +1537,45 @@ bool PopupMenu::is_item_radio_checkable(int p_idx) const { return items[p_idx].checkable_type == Item::CHECKABLE_TYPE_RADIO_BUTTON; } +bool PopupMenu::is_item_shortcut_global(int p_idx) const { + ERR_FAIL_INDEX_V(p_idx, items.size(), false); + return items[p_idx].shortcut_is_global; +} + bool PopupMenu::is_item_shortcut_disabled(int p_idx) const { ERR_FAIL_INDEX_V(p_idx, items.size(), false); return items[p_idx].shortcut_is_disabled; } -void PopupMenu::set_current_index(int p_idx) { - ERR_FAIL_INDEX(p_idx, items.size()); +void PopupMenu::set_focused_item(int p_idx) { + if (p_idx != -1) { + ERR_FAIL_INDEX(p_idx, items.size()); + } + + if (mouse_over == p_idx) { + return; + } + mouse_over = p_idx; - scroll_to_item(mouse_over); - control->update(); + if (mouse_over != -1) { + scroll_to_item(mouse_over); + } + + control->queue_redraw(); } -int PopupMenu::get_current_index() const { +int PopupMenu::get_focused_item() const { return mouse_over; } void PopupMenu::set_item_count(int p_count) { ERR_FAIL_COND(p_count < 0); int prev_size = items.size(); + + if (prev_size == p_count) { + return; + } + items.resize(p_count); if (prev_size < p_count) { @@ -1391,9 +1584,10 @@ void PopupMenu::set_item_count(int p_count) { } } - control->update(); + control->queue_redraw(); child_controls_changed(); notify_property_list_changed(); + _menu_changed(); } int PopupMenu::get_item_count() const { @@ -1533,8 +1727,9 @@ void PopupMenu::remove_item(int p_idx) { } items.remove_at(p_idx); - control->update(); + control->queue_redraw(); child_controls_changed(); + _menu_changed(); } void PopupMenu::add_separator(const String &p_text, int p_id) { @@ -1546,7 +1741,8 @@ void PopupMenu::add_separator(const String &p_text, int p_id) { sep.xl_text = atr(p_text); } items.push_back(sep); - control->update(); + control->queue_redraw(); + _menu_changed(); } void PopupMenu::clear() { @@ -1557,15 +1753,16 @@ void PopupMenu::clear() { } items.clear(); mouse_over = -1; - control->update(); + control->queue_redraw(); child_controls_changed(); notify_property_list_changed(); + _menu_changed(); } void PopupMenu::_ref_shortcut(Ref<Shortcut> p_sc) { if (!shortcut_refcount.has(p_sc)) { shortcut_refcount[p_sc] = 1; - p_sc->connect("changed", callable_mp((CanvasItem *)this, &CanvasItem::update)); + p_sc->connect("changed", callable_mp((CanvasItem *)this, &CanvasItem::queue_redraw)); } else { shortcut_refcount[p_sc] += 1; } @@ -1575,7 +1772,7 @@ void PopupMenu::_unref_shortcut(Ref<Shortcut> p_sc) { ERR_FAIL_COND(!shortcut_refcount.has(p_sc)); shortcut_refcount[p_sc]--; if (shortcut_refcount[p_sc] == 0) { - p_sc->disconnect("changed", callable_mp((CanvasItem *)this, &CanvasItem::update)); + p_sc->disconnect("changed", callable_mp((CanvasItem *)this, &CanvasItem::queue_redraw)); shortcut_refcount.erase(p_sc); } } @@ -1834,7 +2031,7 @@ void PopupMenu::_bind_methods() { ClassDB::bind_method(D_METHOD("set_item_as_radio_checkable", "index", "enable"), &PopupMenu::set_item_as_radio_checkable); ClassDB::bind_method(D_METHOD("set_item_tooltip", "index", "tooltip"), &PopupMenu::set_item_tooltip); ClassDB::bind_method(D_METHOD("set_item_shortcut", "index", "shortcut", "global"), &PopupMenu::set_item_shortcut, DEFVAL(false)); - ClassDB::bind_method(D_METHOD("set_item_horizontal_offset", "index", "offset"), &PopupMenu::set_item_horizontal_offset); + ClassDB::bind_method(D_METHOD("set_item_indent", "index", "indent"), &PopupMenu::set_item_indent); ClassDB::bind_method(D_METHOD("set_item_multistate", "index", "state"), &PopupMenu::set_item_multistate); ClassDB::bind_method(D_METHOD("set_item_shortcut_disabled", "index", "disabled"), &PopupMenu::set_item_shortcut_disabled); @@ -1858,10 +2055,10 @@ void PopupMenu::_bind_methods() { ClassDB::bind_method(D_METHOD("is_item_shortcut_disabled", "index"), &PopupMenu::is_item_shortcut_disabled); ClassDB::bind_method(D_METHOD("get_item_tooltip", "index"), &PopupMenu::get_item_tooltip); ClassDB::bind_method(D_METHOD("get_item_shortcut", "index"), &PopupMenu::get_item_shortcut); - ClassDB::bind_method(D_METHOD("get_item_horizontal_offset", "index"), &PopupMenu::get_item_horizontal_offset); + ClassDB::bind_method(D_METHOD("get_item_indent", "index"), &PopupMenu::get_item_indent); - ClassDB::bind_method(D_METHOD("set_current_index", "index"), &PopupMenu::set_current_index); - ClassDB::bind_method(D_METHOD("get_current_index"), &PopupMenu::get_current_index); + ClassDB::bind_method(D_METHOD("set_focused_item", "index"), &PopupMenu::set_focused_item); + ClassDB::bind_method(D_METHOD("get_focused_item"), &PopupMenu::get_focused_item); ClassDB::bind_method(D_METHOD("set_item_count", "count"), &PopupMenu::set_item_count); ClassDB::bind_method(D_METHOD("get_item_count"), &PopupMenu::get_item_count); @@ -1898,6 +2095,7 @@ void PopupMenu::_bind_methods() { ADD_SIGNAL(MethodInfo("id_pressed", PropertyInfo(Variant::INT, "id"))); ADD_SIGNAL(MethodInfo("id_focused", PropertyInfo(Variant::INT, "id"))); ADD_SIGNAL(MethodInfo("index_pressed", PropertyInfo(Variant::INT, "index"))); + ADD_SIGNAL(MethodInfo("menu_changed")); } void PopupMenu::popup(const Rect2 &p_bounds) { diff --git a/scene/gui/popup_menu.h b/scene/gui/popup_menu.h index e203793c2e..ad7909842e 100644 --- a/scene/gui/popup_menu.h +++ b/scene/gui/popup_menu.h @@ -68,7 +68,7 @@ class PopupMenu : public Popup { Key accel = Key::NONE; int _ofs_cache = 0; int _height_cache = 0; - int h_ofs = 0; + int indent = 0; Ref<Shortcut> shortcut; bool shortcut_is_global = false; bool shortcut_is_disabled = false; @@ -129,13 +129,61 @@ class PopupMenu : public Popup { ScrollContainer *scroll_container = nullptr; Control *control = nullptr; + struct ThemeCache { + Ref<StyleBox> panel_style; + Ref<StyleBox> hover_style; + + Ref<StyleBox> separator_style; + Ref<StyleBox> labeled_separator_left; + Ref<StyleBox> labeled_separator_right; + + int v_separation = 0; + int h_separation = 0; + int indent = 0; + int item_start_padding = 0; + int item_end_padding = 0; + + Ref<Texture2D> checked; + Ref<Texture2D> checked_disabled; + Ref<Texture2D> unchecked; + Ref<Texture2D> unchecked_disabled; + Ref<Texture2D> radio_checked; + Ref<Texture2D> radio_checked_disabled; + Ref<Texture2D> radio_unchecked; + Ref<Texture2D> radio_unchecked_disabled; + + Ref<Texture2D> submenu; + Ref<Texture2D> submenu_mirrored; + + Ref<Font> font; + int font_size = 0; + Ref<Font> font_separator; + int font_separator_size = 0; + + Color font_color; + Color font_hover_color; + Color font_disabled_color; + Color font_accelerator_color; + int font_outline_size = 0; + Color font_outline_color; + + Color font_separator_color; + int font_separator_outline_size = 0; + Color font_separator_outline_color; + } theme_cache; + void _draw_items(); void _draw_background(); void _minimum_lifetime_timeout(); void _close_pressed(); + void _menu_changed(); protected: + virtual void _update_theme_item_cache() override; + + virtual void add_child_notify(Node *p_child) override; + virtual void remove_child_notify(Node *p_child) override; void _notification(int p_what); bool _set(const StringName &p_name, const Variant &p_value); bool _get(const StringName &p_name, Variant &r_ret) const; @@ -183,7 +231,7 @@ public: void set_item_as_radio_checkable(int p_idx, bool p_radio_checkable); void set_item_tooltip(int p_idx, const String &p_tooltip); void set_item_shortcut(int p_idx, const Ref<Shortcut> &p_shortcut, bool p_global = false); - void set_item_horizontal_offset(int p_idx, int p_offset); + void set_item_indent(int p_idx, int p_indent); void set_item_multistate(int p_idx, int p_state); void toggle_item_multistate(int p_idx); void set_item_shortcut_disabled(int p_idx, bool p_disabled); @@ -206,13 +254,15 @@ public: bool is_item_checkable(int p_idx) const; bool is_item_radio_checkable(int p_idx) const; bool is_item_shortcut_disabled(int p_idx) const; + bool is_item_shortcut_global(int p_idx) const; String get_item_tooltip(int p_idx) const; Ref<Shortcut> get_item_shortcut(int p_idx) const; - int get_item_horizontal_offset(int p_idx) const; + int get_item_indent(int p_idx) const; + int get_item_max_states(int p_idx) const; int get_item_state(int p_idx) const; - void set_current_index(int p_idx); - int get_current_index() const; + void set_focused_item(int p_idx); + int get_focused_item() const; void set_item_count(int p_count); int get_item_count() const; diff --git a/scene/gui/progress_bar.cpp b/scene/gui/progress_bar.cpp index 80859e8eb9..8369eaa227 100644 --- a/scene/gui/progress_bar.cpp +++ b/scene/gui/progress_bar.cpp @@ -33,18 +33,13 @@ #include "scene/resources/text_line.h" Size2 ProgressBar::get_minimum_size() const { - Ref<StyleBox> bg = get_theme_stylebox(SNAME("bg")); - Ref<StyleBox> fg = get_theme_stylebox(SNAME("fg")); - Ref<Font> font = get_theme_font(SNAME("font")); - int font_size = get_theme_font_size(SNAME("font_size")); - - Size2 minimum_size = bg->get_minimum_size(); - minimum_size.height = MAX(minimum_size.height, fg->get_minimum_size().height); - minimum_size.width = MAX(minimum_size.width, fg->get_minimum_size().width); - if (percent_visible) { + Size2 minimum_size = theme_cache.background_style->get_minimum_size(); + minimum_size.height = MAX(minimum_size.height, theme_cache.fill_style->get_minimum_size().height); + minimum_size.width = MAX(minimum_size.width, theme_cache.fill_style->get_minimum_size().width); + if (show_percentage) { String txt = "100%"; - TextLine tl = TextLine(txt, font, font_size); - minimum_size.height = MAX(minimum_size.height, bg->get_minimum_size().height + tl.get_size().y); + TextLine tl = TextLine(txt, theme_cache.font, theme_cache.font_size); + minimum_size.height = MAX(minimum_size.height, theme_cache.background_style->get_minimum_size().height + tl.get_size().y); } else { // this is needed, else the progressbar will collapse minimum_size.width = MAX(minimum_size.width, 1); minimum_size.height = MAX(minimum_size.height, 1); @@ -52,23 +47,30 @@ Size2 ProgressBar::get_minimum_size() const { return minimum_size; } +void ProgressBar::_update_theme_item_cache() { + Range::_update_theme_item_cache(); + + theme_cache.background_style = get_theme_stylebox(SNAME("background")); + theme_cache.fill_style = get_theme_stylebox(SNAME("fill")); + + theme_cache.font = get_theme_font(SNAME("font")); + theme_cache.font_size = get_theme_font_size(SNAME("font_size")); + theme_cache.font_color = get_theme_color(SNAME("font_color")); + theme_cache.font_outline_size = get_theme_constant(SNAME("outline_size")); + theme_cache.font_outline_color = get_theme_color(SNAME("font_outline_color")); +} + void ProgressBar::_notification(int p_what) { switch (p_what) { case NOTIFICATION_DRAW: { - Ref<StyleBox> bg = get_theme_stylebox(SNAME("bg")); - Ref<StyleBox> fg = get_theme_stylebox(SNAME("fg")); - Ref<Font> font = get_theme_font(SNAME("font")); - int font_size = get_theme_font_size(SNAME("font_size")); - Color font_color = get_theme_color(SNAME("font_color")); - - draw_style_box(bg, Rect2(Point2(), get_size())); + draw_style_box(theme_cache.background_style, Rect2(Point2(), get_size())); float r = get_as_ratio(); switch (mode) { case FILL_BEGIN_TO_END: case FILL_END_TO_BEGIN: { - int mp = fg->get_minimum_size().width; + int mp = theme_cache.fill_style->get_minimum_size().width; int p = round(r * (get_size().width - mp)); // We want FILL_BEGIN_TO_END to map to right to left when UI layout is RTL, // and left to right otherwise. And likewise for FILL_END_TO_BEGIN. @@ -76,23 +78,23 @@ void ProgressBar::_notification(int p_what) { if (p > 0) { if (right_to_left) { int p_remaining = round((1.0 - r) * (get_size().width - mp)); - draw_style_box(fg, Rect2(Point2(p_remaining, 0), Size2(p + fg->get_minimum_size().width, get_size().height))); + draw_style_box(theme_cache.fill_style, Rect2(Point2(p_remaining, 0), Size2(p + theme_cache.fill_style->get_minimum_size().width, get_size().height))); } else { - draw_style_box(fg, Rect2(Point2(0, 0), Size2(p + fg->get_minimum_size().width, get_size().height))); + draw_style_box(theme_cache.fill_style, Rect2(Point2(0, 0), Size2(p + theme_cache.fill_style->get_minimum_size().width, get_size().height))); } } } break; case FILL_TOP_TO_BOTTOM: case FILL_BOTTOM_TO_TOP: { - int mp = fg->get_minimum_size().height; + int mp = theme_cache.fill_style->get_minimum_size().height; int p = round(r * (get_size().height - mp)); if (p > 0) { if (mode == FILL_TOP_TO_BOTTOM) { - draw_style_box(fg, Rect2(Point2(0, 0), Size2(get_size().width, p + fg->get_minimum_size().height))); + draw_style_box(theme_cache.fill_style, Rect2(Point2(0, 0), Size2(get_size().width, p + theme_cache.fill_style->get_minimum_size().height))); } else { int p_remaining = round((1.0 - r) * (get_size().height - mp)); - draw_style_box(fg, Rect2(Point2(0, p_remaining), Size2(get_size().width, p + fg->get_minimum_size().height))); + draw_style_box(theme_cache.fill_style, Rect2(Point2(0, p_remaining), Size2(get_size().width, p + theme_cache.fill_style->get_minimum_size().height))); } } } break; @@ -100,16 +102,16 @@ void ProgressBar::_notification(int p_what) { break; } - if (percent_visible) { + if (show_percentage) { String txt = TS->format_number(itos(int(get_as_ratio() * 100))) + TS->percent_sign(); - TextLine tl = TextLine(txt, font, font_size); + TextLine tl = TextLine(txt, theme_cache.font, theme_cache.font_size); Vector2 text_pos = (Point2(get_size().width - tl.get_size().x, get_size().height - tl.get_size().y) / 2).round(); - Color font_outline_color = get_theme_color(SNAME("font_outline_color")); - int outline_size = get_theme_constant(SNAME("outline_size")); - if (outline_size > 0 && font_outline_color.a > 0) { - tl.draw_outline(get_canvas_item(), text_pos, outline_size, font_outline_color); + + if (theme_cache.font_outline_size > 0 && theme_cache.font_outline_color.a > 0) { + tl.draw_outline(get_canvas_item(), text_pos, theme_cache.font_outline_size, theme_cache.font_outline_color); } - tl.draw(get_canvas_item(), text_pos, font_color); + + tl.draw(get_canvas_item(), text_pos, theme_cache.font_color); } } break; } @@ -118,34 +120,34 @@ void ProgressBar::_notification(int p_what) { void ProgressBar::set_fill_mode(int p_fill) { ERR_FAIL_INDEX(p_fill, FILL_MODE_MAX); mode = (FillMode)p_fill; - update(); + queue_redraw(); } int ProgressBar::get_fill_mode() { return mode; } -void ProgressBar::set_percent_visible(bool p_visible) { - if (percent_visible == p_visible) { +void ProgressBar::set_show_percentage(bool p_visible) { + if (show_percentage == p_visible) { return; } - percent_visible = p_visible; + show_percentage = p_visible; update_minimum_size(); - update(); + queue_redraw(); } -bool ProgressBar::is_percent_visible() const { - return percent_visible; +bool ProgressBar::is_percentage_shown() const { + return show_percentage; } void ProgressBar::_bind_methods() { ClassDB::bind_method(D_METHOD("set_fill_mode", "mode"), &ProgressBar::set_fill_mode); ClassDB::bind_method(D_METHOD("get_fill_mode"), &ProgressBar::get_fill_mode); - ClassDB::bind_method(D_METHOD("set_percent_visible", "visible"), &ProgressBar::set_percent_visible); - ClassDB::bind_method(D_METHOD("is_percent_visible"), &ProgressBar::is_percent_visible); + ClassDB::bind_method(D_METHOD("set_show_percentage", "visible"), &ProgressBar::set_show_percentage); + ClassDB::bind_method(D_METHOD("is_percentage_shown"), &ProgressBar::is_percentage_shown); ADD_PROPERTY(PropertyInfo(Variant::INT, "fill_mode", PROPERTY_HINT_ENUM, "Begin to End,End to Begin,Top to Bottom,Bottom to Top"), "set_fill_mode", "get_fill_mode"); - ADD_PROPERTY(PropertyInfo(Variant::BOOL, "percent_visible"), "set_percent_visible", "is_percent_visible"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "show_percentage"), "set_show_percentage", "is_percentage_shown"); BIND_ENUM_CONSTANT(FILL_BEGIN_TO_END); BIND_ENUM_CONSTANT(FILL_END_TO_BEGIN); diff --git a/scene/gui/progress_bar.h b/scene/gui/progress_bar.h index 5ba21ad7d5..b6d7d2c7cf 100644 --- a/scene/gui/progress_bar.h +++ b/scene/gui/progress_bar.h @@ -36,9 +36,22 @@ class ProgressBar : public Range { GDCLASS(ProgressBar, Range); - bool percent_visible = true; + bool show_percentage = true; + + struct ThemeCache { + Ref<StyleBox> background_style; + Ref<StyleBox> fill_style; + + Ref<Font> font; + int font_size = 0; + Color font_color; + int font_outline_size = 0; + Color font_outline_color; + } theme_cache; protected: + virtual void _update_theme_item_cache() override; + void _notification(int p_what); static void _bind_methods(); @@ -54,8 +67,8 @@ public: void set_fill_mode(int p_fill); int get_fill_mode(); - void set_percent_visible(bool p_visible); - bool is_percent_visible() const; + void set_show_percentage(bool p_visible); + bool is_percentage_shown() const; Size2 get_minimum_size() const override; ProgressBar(); diff --git a/scene/gui/range.cpp b/scene/gui/range.cpp index fae6688452..1eb412abaf 100644 --- a/scene/gui/range.cpp +++ b/scene/gui/range.cpp @@ -46,7 +46,7 @@ void Range::_value_changed(double p_value) { void Range::_value_changed_notify() { _value_changed(shared->val); emit_signal(SNAME("value_changed"), shared->val); - update(); + queue_redraw(); } void Range::Shared::emit_value_changed() { @@ -61,7 +61,7 @@ void Range::Shared::emit_value_changed() { void Range::_changed_notify(const char *p_what) { emit_signal(SNAME("changed")); - update(); + queue_redraw(); } void Range::_validate_values() { @@ -106,6 +106,10 @@ void Range::set_value(double p_val) { } void Range::set_min(double p_min) { + if (shared->min == p_min) { + return; + } + shared->min = p_min; set_value(shared->val); _validate_values(); @@ -116,6 +120,10 @@ void Range::set_min(double p_min) { } void Range::set_max(double p_max) { + if (shared->max == p_max) { + return; + } + shared->max = p_max; set_value(shared->val); _validate_values(); @@ -124,11 +132,19 @@ void Range::set_max(double p_max) { } void Range::set_step(double p_step) { + if (shared->step == p_step) { + return; + } + shared->step = p_step; shared->emit_changed("step"); } void Range::set_page(double p_page) { + if (shared->page == p_page) { + return; + } + shared->page = p_page; set_value(shared->val); _validate_values(); @@ -300,6 +316,10 @@ bool Range::is_using_rounded_values() const { } void Range::set_exp_ratio(bool p_enable) { + if (shared->exp_ratio == p_enable) { + return; + } + shared->exp_ratio = p_enable; update_configuration_warnings(); diff --git a/scene/gui/reference_rect.cpp b/scene/gui/reference_rect.cpp index 5190a5a7d2..fa5ac5b864 100644 --- a/scene/gui/reference_rect.cpp +++ b/scene/gui/reference_rect.cpp @@ -46,8 +46,12 @@ void ReferenceRect::_notification(int p_what) { } void ReferenceRect::set_border_color(const Color &p_color) { + if (border_color == p_color) { + return; + } + border_color = p_color; - update(); + queue_redraw(); } Color ReferenceRect::get_border_color() const { @@ -55,8 +59,13 @@ Color ReferenceRect::get_border_color() const { } void ReferenceRect::set_border_width(float p_width) { - border_width = MAX(0.0, p_width); - update(); + float width_max = MAX(0.0, p_width); + if (border_width == width_max) { + return; + } + + border_width = width_max; + queue_redraw(); } float ReferenceRect::get_border_width() const { @@ -64,8 +73,12 @@ float ReferenceRect::get_border_width() const { } void ReferenceRect::set_editor_only(const bool &p_enabled) { + if (editor_only == p_enabled) { + return; + } + editor_only = p_enabled; - update(); + queue_redraw(); } bool ReferenceRect::get_editor_only() const { diff --git a/scene/gui/rich_text_label.cpp b/scene/gui/rich_text_label.cpp index 984f20ee58..c936fe9738 100644 --- a/scene/gui/rich_text_label.cpp +++ b/scene/gui/rich_text_label.cpp @@ -134,8 +134,7 @@ RichTextLabel::Item *RichTextLabel::_get_prev_item(Item *p_item, bool p_free) co } Rect2 RichTextLabel::_get_text_rect() { - Ref<StyleBox> style = get_theme_stylebox(SNAME("normal")); - return Rect2(style->get_offset(), get_size() - style->get_minimum_size()); + return Rect2(theme_cache.normal_style->get_offset(), get_size() - theme_cache.normal_style->get_minimum_size()); } RichTextLabel::Item *RichTextLabel::_get_item_at_pos(RichTextLabel::Item *p_item_from, RichTextLabel::Item *p_item_to, int p_position) { @@ -287,8 +286,6 @@ float RichTextLabel::_resize_line(ItemFrame *p_frame, int p_line, const Ref<Font switch (it->type) { case ITEM_TABLE: { ItemTable *table = static_cast<ItemTable *>(it); - int hseparation = get_theme_constant(SNAME("table_h_separation")); - int vseparation = get_theme_constant(SNAME("table_v_separation")); int col_count = table->columns.size(); for (int i = 0; i < col_count; i++) { @@ -309,12 +306,12 @@ float RichTextLabel::_resize_line(ItemFrame *p_frame, int p_line, const Ref<Font } // Compute minimum width for each cell. - const int available_width = p_width - hseparation * (col_count - 1); + const int available_width = p_width - theme_cache.table_h_separation * (col_count - 1); // Compute available width and total ratio (for expanders). int total_ratio = 0; int remaining_width = available_width; - table->total_width = hseparation; + table->total_width = theme_cache.table_h_separation; for (int i = 0; i < col_count; i++) { remaining_width -= table->columns[i].min_width; @@ -332,7 +329,7 @@ float RichTextLabel::_resize_line(ItemFrame *p_frame, int p_line, const Ref<Font if (table->columns[i].expand && total_ratio > 0 && remaining_width > 0) { table->columns[i].width += table->columns[i].expand_ratio * remaining_width / total_ratio; } - table->total_width += table->columns[i].width + hseparation; + table->total_width += table->columns[i].width + theme_cache.table_h_separation; } // Resize to max_width if needed and distribute the remaining space. @@ -394,9 +391,9 @@ float RichTextLabel::_resize_line(ItemFrame *p_frame, int p_line, const Ref<Font frame->lines[i].offset.y = prev_h; frame->lines[i].offset += offset; - float h = frame->lines[i].text_buf->get_size().y + (frame->lines[i].text_buf->get_line_count() - 1) * get_theme_constant(SNAME("line_separation")); + float h = frame->lines[i].text_buf->get_size().y + (frame->lines[i].text_buf->get_line_count() - 1) * theme_cache.line_separation; if (i > 0) { - h += get_theme_constant(SNAME("line_separation")); + h += theme_cache.line_separation; } if (frame->min_size_over.y > 0) { h = MAX(h, frame->min_size_over.y); @@ -405,15 +402,15 @@ float RichTextLabel::_resize_line(ItemFrame *p_frame, int p_line, const Ref<Font h = MIN(h, frame->max_size_over.y); } yofs += h; - prev_h = frame->lines[i].offset.y + frame->lines[i].text_buf->get_size().y + frame->lines[i].text_buf->get_line_count() * get_theme_constant(SNAME("line_separation")); + prev_h = frame->lines[i].offset.y + frame->lines[i].text_buf->get_size().y + frame->lines[i].text_buf->get_line_count() * theme_cache.line_separation; } yofs += frame->padding.size.y; - offset.x += table->columns[column].width + hseparation + frame->padding.size.x; + offset.x += table->columns[column].width + theme_cache.table_h_separation + frame->padding.size.x; row_height = MAX(yofs, row_height); if (column == col_count - 1) { offset.x = 0; - row_height += vseparation; + row_height += theme_cache.table_v_separation; table->total_height += row_height; offset.y += row_height; table->rows.push_back(row_height); @@ -453,6 +450,7 @@ float RichTextLabel::_shape_line(ItemFrame *p_frame, int p_line, const Ref<Font> case TextServer::AUTOWRAP_OFF: break; } + autowrap_flags = autowrap_flags | TextServer::BREAK_TRIM_EDGE_SPACES; // Clear cache. l.text_buf->clear(); @@ -550,8 +548,6 @@ float RichTextLabel::_shape_line(ItemFrame *p_frame, int p_line, const Ref<Font> } break; case ITEM_TABLE: { ItemTable *table = static_cast<ItemTable *>(it); - int hseparation = get_theme_constant(SNAME("table_h_separation")); - int vseparation = get_theme_constant(SNAME("table_v_separation")); int col_count = table->columns.size(); int t_char_count = 0; // Set minimums to zero. @@ -561,7 +557,7 @@ float RichTextLabel::_shape_line(ItemFrame *p_frame, int p_line, const Ref<Font> table->columns[i].width = 0; } // Compute minimum width for each cell. - const int available_width = p_width - hseparation * (col_count - 1); + const int available_width = p_width - theme_cache.table_h_separation * (col_count - 1); int idx = 0; for (Item *E : table->subitems) { @@ -590,7 +586,7 @@ float RichTextLabel::_shape_line(ItemFrame *p_frame, int p_line, const Ref<Font> // Compute available width and total ratio (for expanders). int total_ratio = 0; int remaining_width = available_width; - table->total_width = hseparation; + table->total_width = theme_cache.table_h_separation; for (int i = 0; i < col_count; i++) { remaining_width -= table->columns[i].min_width; @@ -608,7 +604,7 @@ float RichTextLabel::_shape_line(ItemFrame *p_frame, int p_line, const Ref<Font> if (table->columns[i].expand && total_ratio > 0 && remaining_width > 0) { table->columns[i].width += table->columns[i].expand_ratio * remaining_width / total_ratio; } - table->total_width += table->columns[i].width + hseparation; + table->total_width += table->columns[i].width + theme_cache.table_h_separation; } // Resize to max_width if needed and distribute the remaining space. @@ -671,9 +667,9 @@ float RichTextLabel::_shape_line(ItemFrame *p_frame, int p_line, const Ref<Font> frame->lines[i].offset.y = prev_h; frame->lines[i].offset += offset; - float h = frame->lines[i].text_buf->get_size().y + (frame->lines[i].text_buf->get_line_count() - 1) * get_theme_constant(SNAME("line_separation")); + float h = frame->lines[i].text_buf->get_size().y + (frame->lines[i].text_buf->get_line_count() - 1) * theme_cache.line_separation; if (i > 0) { - h += get_theme_constant(SNAME("line_separation")); + h += theme_cache.line_separation; } if (frame->min_size_over.y > 0) { h = MAX(h, frame->min_size_over.y); @@ -682,16 +678,16 @@ float RichTextLabel::_shape_line(ItemFrame *p_frame, int p_line, const Ref<Font> h = MIN(h, frame->max_size_over.y); } yofs += h; - prev_h = frame->lines[i].offset.y + frame->lines[i].text_buf->get_size().y + frame->lines[i].text_buf->get_line_count() * get_theme_constant(SNAME("line_separation")); + prev_h = frame->lines[i].offset.y + frame->lines[i].text_buf->get_size().y + frame->lines[i].text_buf->get_line_count() * theme_cache.line_separation; } yofs += frame->padding.size.y; - offset.x += table->columns[column].width + hseparation + frame->padding.size.x; + offset.x += table->columns[column].width + theme_cache.table_h_separation + frame->padding.size.x; row_height = MAX(yofs, row_height); // Add row height after last column of the row or last cell of the table. if (column == col_count - 1 || E->next() == nullptr) { offset.x = 0; - row_height += vseparation; + row_height += theme_cache.table_v_separation; table->total_height += row_height; offset.y += row_height; table->rows.push_back(row_height); @@ -722,7 +718,6 @@ int RichTextLabel::_draw_line(ItemFrame *p_frame, int p_line, const Vector2 &p_o ERR_FAIL_COND_V(p_line < 0 || p_line >= (int)p_frame->lines.size(), 0); Vector2 off; - int line_spacing = get_theme_constant(SNAME("line_separation")); Line &l = p_frame->lines[p_line]; MutexLock lock(l.text_buf->get_mutex()); @@ -742,7 +737,7 @@ int RichTextLabel::_draw_line(ItemFrame *p_frame, int p_line, const Vector2 &p_o bool trim_glyphs_ltr = (visible_characters >= 0) && ((visible_chars_behavior == TextServer::VC_GLYPHS_LTR) || ((visible_chars_behavior == TextServer::VC_GLYPHS_AUTO) && !lrtl)); bool trim_glyphs_rtl = (visible_characters >= 0) && ((visible_chars_behavior == TextServer::VC_GLYPHS_RTL) || ((visible_chars_behavior == TextServer::VC_GLYPHS_AUTO) && lrtl)); int total_glyphs = (trim_glyphs_ltr || trim_glyphs_rtl) ? get_total_glyph_count() : 0; - int visible_glyphs = total_glyphs * percent_visible; + int visible_glyphs = total_glyphs * visible_ratio; Vector<int> list_index; Vector<ItemList *> list_items; @@ -774,8 +769,8 @@ int RichTextLabel::_draw_line(ItemFrame *p_frame, int p_line, const Vector2 &p_o } } if (!prefix.is_empty()) { - Ref<Font> font = get_theme_font(SNAME("normal_font")); - int font_size = get_theme_font_size(SNAME("normal_font_size")); + Ref<Font> font = theme_cache.normal_font; + int font_size = theme_cache.normal_font_size; ItemFont *font_it = _find_font(l.from); if (font_it) { @@ -818,7 +813,7 @@ int RichTextLabel::_draw_line(ItemFrame *p_frame, int p_line, const Vector2 &p_o // Draw text. for (int line = 0; line < l.text_buf->get_line_count(); line++) { if (line > 0) { - off.y += line_spacing; + off.y += theme_cache.line_separation; } if (p_ofs.y + off.y >= ctrl_size.height) { @@ -893,10 +888,11 @@ int RichTextLabel::_draw_line(ItemFrame *p_frame, int p_line, const Vector2 &p_o } break; case ITEM_TABLE: { ItemTable *table = static_cast<ItemTable *>(it); - Color odd_row_bg = get_theme_color(SNAME("table_odd_row_bg")); - Color even_row_bg = get_theme_color(SNAME("table_even_row_bg")); - Color border = get_theme_color(SNAME("table_border")); - int hseparation = get_theme_constant(SNAME("table_h_separation")); + Color odd_row_bg = theme_cache.table_odd_row_bg; + Color even_row_bg = theme_cache.table_even_row_bg; + Color border = theme_cache.table_border; + int hseparation = theme_cache.table_h_separation; + int col_count = table->columns.size(); int row_count = table->rows.size(); @@ -1032,8 +1028,8 @@ int RichTextLabel::_draw_line(ItemFrame *p_frame, int p_line, const Vector2 &p_o uint64_t char_current_rand = item_shake->offset_random(glyphs[i].start); uint64_t char_previous_rand = item_shake->offset_previous_random(glyphs[i].start); uint64_t max_rand = 2147483647; - double current_offset = Math::range_lerp(char_current_rand % max_rand, 0, max_rand, 0.0f, 2.f * (float)Math_PI); - double previous_offset = Math::range_lerp(char_previous_rand % max_rand, 0, max_rand, 0.0f, 2.f * (float)Math_PI); + double current_offset = Math::remap(char_current_rand % max_rand, 0, max_rand, 0.0f, 2.f * (float)Math_PI); + double previous_offset = Math::remap(char_previous_rand % max_rand, 0, max_rand, 0.0f, 2.f * (float)Math_PI); double n_time = (double)(item_shake->elapsed_time / (0.5f / item_shake->rate)); n_time = (n_time > 1.0) ? 1.0 : n_time; item_shake->prev_off = Point2(Math::lerp(Math::sin(previous_offset), Math::sin(current_offset), n_time), Math::lerp(Math::cos(previous_offset), Math::cos(current_offset), n_time)) * (float)item_shake->strength / 10.0f; @@ -1092,8 +1088,8 @@ int RichTextLabel::_draw_line(ItemFrame *p_frame, int p_line, const Vector2 &p_o _draw_fbg_boxes(ci, rid, fbg_line_off, it_from, it_to, chr_range.x, chr_range.y, 0); // Draw main text. - Color selection_fg = get_theme_color(SNAME("font_selected_color")); - Color selection_bg = get_theme_color(SNAME("selection_color")); + Color selection_fg = theme_cache.font_selected_color; + Color selection_bg = theme_cache.selection_color; int sel_start = -1; int sel_end = -1; @@ -1135,7 +1131,7 @@ int RichTextLabel::_draw_line(ItemFrame *p_frame, int p_line, const Vector2 &p_o } else if (ul_started) { ul_started = false; float y_off = TS->shaped_text_get_underline_position(rid); - float underline_width = TS->shaped_text_get_underline_thickness(rid) * get_theme_default_base_scale(); + float underline_width = TS->shaped_text_get_underline_thickness(rid) * theme_cache.base_scale; draw_line(ul_start + Vector2(0, y_off), p_ofs + Vector2(off.x, off.y + y_off), ul_color, underline_width); } if (_find_hint(it, nullptr) && underline_hint) { @@ -1148,7 +1144,7 @@ int RichTextLabel::_draw_line(ItemFrame *p_frame, int p_line, const Vector2 &p_o } else if (dot_ul_started) { dot_ul_started = false; float y_off = TS->shaped_text_get_underline_position(rid); - float underline_width = TS->shaped_text_get_underline_thickness(rid) * get_theme_default_base_scale(); + float underline_width = TS->shaped_text_get_underline_thickness(rid) * theme_cache.base_scale; draw_dashed_line(dot_ul_start + Vector2(0, y_off), p_ofs + Vector2(off.x, off.y + y_off), dot_ul_color, underline_width, underline_width * 2); } if (_find_strikethrough(it)) { @@ -1161,7 +1157,7 @@ int RichTextLabel::_draw_line(ItemFrame *p_frame, int p_line, const Vector2 &p_o } else if (st_started) { st_started = false; float y_off = -TS->shaped_text_get_ascent(rid) + TS->shaped_text_get_size(rid).y / 2; - float underline_width = TS->shaped_text_get_underline_thickness(rid) * get_theme_default_base_scale(); + float underline_width = TS->shaped_text_get_underline_thickness(rid) * theme_cache.base_scale; draw_line(st_start + Vector2(0, y_off), p_ofs + Vector2(off.x, off.y + y_off), st_color, underline_width); } @@ -1247,8 +1243,8 @@ int RichTextLabel::_draw_line(ItemFrame *p_frame, int p_line, const Vector2 &p_o uint64_t char_current_rand = item_shake->offset_random(glyphs[i].start); uint64_t char_previous_rand = item_shake->offset_previous_random(glyphs[i].start); uint64_t max_rand = 2147483647; - double current_offset = Math::range_lerp(char_current_rand % max_rand, 0, max_rand, 0.0f, 2.f * (float)Math_PI); - double previous_offset = Math::range_lerp(char_previous_rand % max_rand, 0, max_rand, 0.0f, 2.f * (float)Math_PI); + double current_offset = Math::remap(char_current_rand % max_rand, 0, max_rand, 0.0f, 2.f * (float)Math_PI); + double previous_offset = Math::remap(char_previous_rand % max_rand, 0, max_rand, 0.0f, 2.f * (float)Math_PI); double n_time = (double)(item_shake->elapsed_time / (0.5f / item_shake->rate)); n_time = (n_time > 1.0) ? 1.0 : n_time; item_shake->prev_off = Point2(Math::lerp(Math::sin(previous_offset), Math::sin(current_offset), n_time), Math::lerp(Math::cos(previous_offset), Math::cos(current_offset), n_time)) * (float)item_shake->strength / 10.0f; @@ -1300,19 +1296,19 @@ int RichTextLabel::_draw_line(ItemFrame *p_frame, int p_line, const Vector2 &p_o if (ul_started) { ul_started = false; float y_off = TS->shaped_text_get_underline_position(rid); - float underline_width = TS->shaped_text_get_underline_thickness(rid) * get_theme_default_base_scale(); + float underline_width = TS->shaped_text_get_underline_thickness(rid) * theme_cache.base_scale; draw_line(ul_start + Vector2(0, y_off), p_ofs + Vector2(off.x, off.y + y_off), ul_color, underline_width); } if (dot_ul_started) { dot_ul_started = false; float y_off = TS->shaped_text_get_underline_position(rid); - float underline_width = TS->shaped_text_get_underline_thickness(rid) * get_theme_default_base_scale(); + float underline_width = TS->shaped_text_get_underline_thickness(rid) * theme_cache.base_scale; draw_dashed_line(dot_ul_start + Vector2(0, y_off), p_ofs + Vector2(off.x, off.y + y_off), dot_ul_color, underline_width, underline_width * 2); } if (st_started) { st_started = false; float y_off = -TS->shaped_text_get_ascent(rid) + TS->shaped_text_get_size(rid).y / 2; - float underline_width = TS->shaped_text_get_underline_thickness(rid) * get_theme_default_base_scale(); + float underline_width = TS->shaped_text_get_underline_thickness(rid) * theme_cache.base_scale; draw_line(st_start + Vector2(0, y_off), p_ofs + Vector2(off.x, off.y + y_off), st_color, underline_width); } } @@ -1322,19 +1318,19 @@ int RichTextLabel::_draw_line(ItemFrame *p_frame, int p_line, const Vector2 &p_o if (ul_started) { ul_started = false; float y_off = TS->shaped_text_get_underline_position(rid); - float underline_width = TS->shaped_text_get_underline_thickness(rid) * get_theme_default_base_scale(); + float underline_width = TS->shaped_text_get_underline_thickness(rid) * theme_cache.base_scale; draw_line(ul_start + Vector2(0, y_off), p_ofs + Vector2(off.x, off.y + y_off), ul_color, underline_width); } if (dot_ul_started) { dot_ul_started = false; float y_off = TS->shaped_text_get_underline_position(rid); - float underline_width = TS->shaped_text_get_underline_thickness(rid) * get_theme_default_base_scale(); + float underline_width = TS->shaped_text_get_underline_thickness(rid) * theme_cache.base_scale; draw_dashed_line(dot_ul_start + Vector2(0, y_off), p_ofs + Vector2(off.x, off.y + y_off), dot_ul_color, underline_width, underline_width * 2); } if (st_started) { st_started = false; float y_off = -TS->shaped_text_get_ascent(rid) + TS->shaped_text_get_size(rid).y / 2; - float underline_width = TS->shaped_text_get_underline_thickness(rid) * get_theme_default_base_scale(); + float underline_width = TS->shaped_text_get_underline_thickness(rid) * theme_cache.base_scale; draw_line(st_start + Vector2(0, y_off), p_ofs + Vector2(off.x, off.y + y_off), st_color, underline_width); } // Draw foreground color box @@ -1346,7 +1342,7 @@ int RichTextLabel::_draw_line(ItemFrame *p_frame, int p_line, const Vector2 &p_o return line_count; } -void RichTextLabel::_find_click(ItemFrame *p_frame, const Point2i &p_click, ItemFrame **r_click_frame, int *r_click_line, Item **r_click_item, int *r_click_char, bool *r_outside) { +void RichTextLabel::_find_click(ItemFrame *p_frame, const Point2i &p_click, ItemFrame **r_click_frame, int *r_click_line, Item **r_click_item, int *r_click_char, bool *r_outside, bool p_meta) { if (r_click_item) { *r_click_item = nullptr; } @@ -1369,8 +1365,8 @@ void RichTextLabel::_find_click(ItemFrame *p_frame, const Point2i &p_click, Item Point2 ofs = text_rect.get_position() + Vector2(0, main->lines[from_line].offset.y - vofs); while (ofs.y < size.height && from_line < to_line) { MutexLock lock(main->lines[from_line].text_buf->get_mutex()); - _find_click_in_line(p_frame, from_line, ofs, text_rect.size.x, p_click, r_click_frame, r_click_line, r_click_item, r_click_char); - ofs.y += main->lines[from_line].text_buf->get_size().y + main->lines[from_line].text_buf->get_line_count() * get_theme_constant(SNAME("line_separation")); + _find_click_in_line(p_frame, from_line, ofs, text_rect.size.x, p_click, r_click_frame, r_click_line, r_click_item, r_click_char, false, p_meta); + ofs.y += main->lines[from_line].text_buf->get_size().y + main->lines[from_line].text_buf->get_line_count() * theme_cache.line_separation; if (((r_click_item != nullptr) && ((*r_click_item) != nullptr)) || ((r_click_frame != nullptr) && ((*r_click_frame) != nullptr))) { if (r_outside != nullptr) { *r_outside = false; @@ -1381,7 +1377,7 @@ void RichTextLabel::_find_click(ItemFrame *p_frame, const Point2i &p_click, Item } } -float RichTextLabel::_find_click_in_line(ItemFrame *p_frame, int p_line, const Vector2 &p_ofs, int p_width, const Point2i &p_click, ItemFrame **r_click_frame, int *r_click_line, Item **r_click_item, int *r_click_char, bool p_table) { +float RichTextLabel::_find_click_in_line(ItemFrame *p_frame, int p_line, const Vector2 &p_ofs, int p_width, const Point2i &p_click, ItemFrame **r_click_frame, int *r_click_line, Item **r_click_item, int *r_click_char, bool p_table, bool p_meta) { Vector2 off; bool line_clicked = false; @@ -1448,9 +1444,6 @@ float RichTextLabel::_find_click_in_line(ItemFrame *p_frame, int p_line, const V if (p_click.y >= rect.position.y && p_click.y <= rect.position.y + rect.size.y) { switch (it->type) { case ITEM_TABLE: { - int hseparation = get_theme_constant(SNAME("table_h_separation")); - int vseparation = get_theme_constant(SNAME("table_v_separation")); - ItemTable *table = static_cast<ItemTable *>(it); int idx = 0; @@ -1468,7 +1461,7 @@ float RichTextLabel::_find_click_in_line(ItemFrame *p_frame, int p_line, const V if (rtl) { coff.x = rect.size.width - table->columns[col].width - coff.x; } - Rect2 crect = Rect2(rect.position + coff - frame->padding.position, Size2(table->columns[col].width + hseparation, table->rows[row] + vseparation) + frame->padding.position + frame->padding.size); + Rect2 crect = Rect2(rect.position + coff - frame->padding.position, Size2(table->columns[col].width + theme_cache.table_h_separation, table->rows[row] + theme_cache.table_v_separation) + frame->padding.position + frame->padding.size); if (col == col_count - 1) { if (rtl) { crect.size.x = crect.position.x + crect.size.x; @@ -1479,7 +1472,7 @@ float RichTextLabel::_find_click_in_line(ItemFrame *p_frame, int p_line, const V } if (crect.has_point(p_click)) { for (int j = 0; j < (int)frame->lines.size(); j++) { - _find_click_in_line(frame, j, rect.position + Vector2(0, frame->lines[j].offset.y), rect.size.x, p_click, &table_click_frame, &table_click_line, &table_click_item, &table_click_char, true); + _find_click_in_line(frame, j, rect.position + Vector2(0, frame->lines[j].offset.y), rect.size.x, p_click, &table_click_frame, &table_click_line, &table_click_item, &table_click_char, true, p_meta); if (table_click_frame && table_click_item) { // Save cell detected cell hit data. table_range = Vector2i(INT32_MAX, 0); @@ -1507,12 +1500,20 @@ float RichTextLabel::_find_click_in_line(ItemFrame *p_frame, int p_line, const V } Rect2 rect = Rect2(p_ofs + off - Vector2(0, TS->shaped_text_get_ascent(rid)) - p_frame->padding.position, TS->shaped_text_get_size(rid) + p_frame->padding.position + p_frame->padding.size); if (p_table) { - rect.size.y += get_theme_constant(SNAME("table_v_separation")); + rect.size.y += theme_cache.table_v_separation; } if (p_click.y >= rect.position.y && p_click.y <= rect.position.y + rect.size.y) { if ((!rtl && p_click.x >= rect.position.x) || (rtl && p_click.x <= rect.position.x + rect.size.x)) { - char_pos = TS->shaped_text_hit_test_position(rid, p_click.x - rect.position.x); + if (p_meta) { + int64_t glyph_idx = TS->shaped_text_hit_test_grapheme(rid, p_click.x - rect.position.x); + if (glyph_idx >= 0) { + const Glyph *glyphs = TS->shaped_text_get_glyphs(rid); + char_pos = glyphs[glyph_idx].start; + } + } else { + char_pos = TS->shaped_text_hit_test_position(rid, p_click.x - rect.position.x); + } } line_clicked = true; text_rect_begin = rtl ? rect.position.x + rect.size.x : rect.position.x; @@ -1538,7 +1539,7 @@ float RichTextLabel::_find_click_in_line(ItemFrame *p_frame, int p_line, const V return table_offy; } - off.y += TS->shaped_text_get_descent(rid) + get_theme_constant(SNAME("line_separation")); + off.y += TS->shaped_text_get_descent(rid) + theme_cache.line_separation; } // Text line hit. @@ -1553,8 +1554,8 @@ float RichTextLabel::_find_click_in_line(ItemFrame *p_frame, int p_line, const V int stop = text_rect_begin; *r_click_item = _find_indentable(it); while (*r_click_item) { - Ref<Font> font = get_theme_font(SNAME("normal_font")); - int font_size = get_theme_font_size(SNAME("normal_font_size")); + Ref<Font> font = theme_cache.normal_font; + int font_size = theme_cache.normal_font_size; ItemFont *font_it = _find_font(*r_click_item); if (font_it) { if (font_it->font.is_valid()) { @@ -1613,7 +1614,7 @@ void RichTextLabel::_scroll_changed(double) { scroll_updated = true; - update(); + queue_redraw(); } void RichTextLabel::_update_fx(RichTextLabel::ItemFrame *p_frame, double p_delta_time) { @@ -1667,7 +1668,48 @@ int RichTextLabel::_find_first_line(int p_from, int p_to, int p_vofs) const { } _FORCE_INLINE_ float RichTextLabel::_calculate_line_vertical_offset(const RichTextLabel::Line &line) const { - return line.get_height(get_theme_constant(SNAME("line_separation"))); + return line.get_height(theme_cache.line_separation); +} + +void RichTextLabel::_update_theme_item_cache() { + Control::_update_theme_item_cache(); + + theme_cache.normal_style = get_theme_stylebox(SNAME("normal")); + theme_cache.focus_style = get_theme_stylebox(SNAME("focus")); + theme_cache.progress_bg_style = get_theme_stylebox(SNAME("background"), SNAME("ProgressBar")); + theme_cache.progress_fg_style = get_theme_stylebox(SNAME("fill"), SNAME("ProgressBar")); + + theme_cache.line_separation = get_theme_constant(SNAME("line_separation")); + + theme_cache.normal_font = get_theme_font(SNAME("normal_font")); + theme_cache.normal_font_size = get_theme_font_size(SNAME("normal_font_size")); + + theme_cache.default_color = get_theme_color(SNAME("default_color")); + theme_cache.font_selected_color = get_theme_color(SNAME("font_selected_color")); + theme_cache.selection_color = get_theme_color(SNAME("selection_color")); + theme_cache.font_outline_color = get_theme_color(SNAME("font_outline_color")); + theme_cache.font_shadow_color = get_theme_color(SNAME("font_shadow_color")); + theme_cache.shadow_outline_size = get_theme_constant(SNAME("shadow_outline_size")); + theme_cache.shadow_offset_x = get_theme_constant(SNAME("shadow_offset_x")); + theme_cache.shadow_offset_y = get_theme_constant(SNAME("shadow_offset_y")); + theme_cache.outline_size = get_theme_constant(SNAME("outline_size")); + + theme_cache.bold_font = get_theme_font(SNAME("bold_font")); + theme_cache.bold_font_size = get_theme_font_size(SNAME("bold_font_size")); + theme_cache.bold_italics_font = get_theme_font(SNAME("bold_italics_font")); + theme_cache.bold_italics_font_size = get_theme_font_size(SNAME("bold_italics_font_size")); + theme_cache.italics_font = get_theme_font(SNAME("italics_font")); + theme_cache.italics_font_size = get_theme_font_size(SNAME("italics_font_size")); + theme_cache.mono_font = get_theme_font(SNAME("mono_font")); + theme_cache.mono_font_size = get_theme_font_size(SNAME("mono_font_size")); + + theme_cache.table_h_separation = get_theme_constant(SNAME("table_h_separation")); + theme_cache.table_v_separation = get_theme_constant(SNAME("table_v_separation")); + theme_cache.table_odd_row_bg = get_theme_color(SNAME("table_odd_row_bg")); + theme_cache.table_even_row_bg = get_theme_color(SNAME("table_even_row_bg")); + theme_cache.table_border = get_theme_color(SNAME("table_border")); + + theme_cache.base_scale = get_theme_default_base_scale(); } void RichTextLabel::_notification(int p_what) { @@ -1677,20 +1719,20 @@ void RichTextLabel::_notification(int p_what) { meta_hovering = nullptr; emit_signal(SNAME("meta_hover_ended"), current_meta); current_meta = false; - update(); + queue_redraw(); } } break; case NOTIFICATION_RESIZED: { _stop_thread(); main->first_resized_line.store(0); //invalidate ALL - update(); + queue_redraw(); } break; case NOTIFICATION_THEME_CHANGED: { _stop_thread(); main->first_invalid_font_line.store(0); //invalidate ALL - update(); + queue_redraw(); } break; case NOTIFICATION_ENTER_TREE: { @@ -1700,7 +1742,7 @@ void RichTextLabel::_notification(int p_what) { } main->first_invalid_line.store(0); //invalidate ALL - update(); + queue_redraw(); } break; case NOTIFICATION_PREDELETE: @@ -1712,22 +1754,22 @@ void RichTextLabel::_notification(int p_what) { case NOTIFICATION_TRANSLATION_CHANGED: { _stop_thread(); main->first_invalid_line.store(0); //invalidate ALL - update(); + queue_redraw(); } break; case NOTIFICATION_INTERNAL_PHYSICS_PROCESS: { - update(); + queue_redraw(); } break; case NOTIFICATION_DRAW: { RID ci = get_canvas_item(); Size2 size = get_size(); - draw_style_box(get_theme_stylebox(SNAME("normal")), Rect2(Point2(), size)); + draw_style_box(theme_cache.normal_style, Rect2(Point2(), size)); if (has_focus()) { RenderingServer::get_singleton()->canvas_item_add_clip_ignore(ci, true); - draw_style_box(get_theme_stylebox(SNAME("focus")), Rect2(Point2(), size)); + draw_style_box(theme_cache.focus_style, Rect2(Point2(), size)); RenderingServer::get_singleton()->canvas_item_add_clip_ignore(ci, false); } @@ -1737,24 +1779,20 @@ void RichTextLabel::_notification(int p_what) { } else { // Draw loading progress bar. if ((progress_delay > 0) && (OS::get_singleton()->get_ticks_msec() - loading_started >= (uint64_t)progress_delay)) { - Ref<StyleBox> bg = get_theme_stylebox(SNAME("bg"), SNAME("ProgressBar")); - Ref<StyleBox> fg = get_theme_stylebox(SNAME("fg"), SNAME("ProgressBar")); - Ref<StyleBox> style = get_theme_stylebox(SNAME("normal")); - - Vector2 p_size = Vector2(size.width - (style->get_offset().x + vscroll->get_combined_minimum_size().width) * 2, vscroll->get_combined_minimum_size().width); - Vector2 p_pos = Vector2(style->get_offset().x, size.height - style->get_offset().y - vscroll->get_combined_minimum_size().width); + Vector2 p_size = Vector2(size.width - (theme_cache.normal_style->get_offset().x + vscroll->get_combined_minimum_size().width) * 2, vscroll->get_combined_minimum_size().width); + Vector2 p_pos = Vector2(theme_cache.normal_style->get_offset().x, size.height - theme_cache.normal_style->get_offset().y - vscroll->get_combined_minimum_size().width); - draw_style_box(bg, Rect2(p_pos, p_size)); + draw_style_box(theme_cache.progress_bg_style, Rect2(p_pos, p_size)); bool right_to_left = is_layout_rtl(); double r = loaded.load(); - int mp = fg->get_minimum_size().width; + int mp = theme_cache.progress_fg_style->get_minimum_size().width; int p = round(r * (p_size.width - mp)); if (right_to_left) { int p_remaining = round((1.0 - r) * (p_size.width - mp)); - draw_style_box(fg, Rect2(p_pos + Point2(p_remaining, 0), Size2(p + fg->get_minimum_size().width, p_size.height))); + draw_style_box(theme_cache.progress_fg_style, Rect2(p_pos + Point2(p_remaining, 0), Size2(p + theme_cache.progress_fg_style->get_minimum_size().width, p_size.height))); } else { - draw_style_box(fg, Rect2(p_pos, Size2(p + fg->get_minimum_size().width, p_size.height))); + draw_style_box(theme_cache.progress_fg_style, Rect2(p_pos, Size2(p + theme_cache.progress_fg_style->get_minimum_size().width, p_size.height))); } } } @@ -1767,13 +1805,7 @@ void RichTextLabel::_notification(int p_what) { int to_line = main->first_invalid_line.load(); int from_line = _find_first_line(0, to_line, vofs); - Ref<Font> base_font = get_theme_font(SNAME("normal_font")); - Color base_color = get_theme_color(SNAME("default_color")); - Color outline_color = get_theme_color(SNAME("font_outline_color")); - int outline_size = get_theme_constant(SNAME("outline_size")); - Color font_shadow_color = get_theme_color(SNAME("font_shadow_color")); - int shadow_outline_size = get_theme_constant(SNAME("shadow_outline_size")); - Point2 shadow_ofs(get_theme_constant(SNAME("shadow_offset_x")), get_theme_constant(SNAME("shadow_offset_y"))); + Point2 shadow_ofs(theme_cache.shadow_offset_x, theme_cache.shadow_offset_y); visible_paragraph_count = 0; visible_line_count = 0; @@ -1785,8 +1817,8 @@ void RichTextLabel::_notification(int p_what) { MutexLock lock(main->lines[from_line].text_buf->get_mutex()); visible_paragraph_count++; - visible_line_count += _draw_line(main, from_line, ofs, text_rect.size.x, base_color, outline_size, outline_color, font_shadow_color, shadow_outline_size, shadow_ofs, processed_glyphs); - ofs.y += main->lines[from_line].text_buf->get_size().y + main->lines[from_line].text_buf->get_line_count() * get_theme_constant(SNAME("line_separation")); + visible_line_count += _draw_line(main, from_line, ofs, text_rect.size.x, theme_cache.default_color, theme_cache.outline_size, theme_cache.font_outline_color, theme_cache.font_shadow_color, theme_cache.shadow_outline_size, shadow_ofs, processed_glyphs); + ofs.y += main->lines[from_line].text_buf->get_size().y + main->lines[from_line].text_buf->get_line_count() * theme_cache.line_separation; from_line++; } } break; @@ -1798,7 +1830,7 @@ void RichTextLabel::_notification(int p_what) { } double dt = get_process_delta_time(); _update_fx(main, dt); - update(); + queue_redraw(); } } break; @@ -1825,7 +1857,7 @@ Control::CursorShape RichTextLabel::get_cursor_shape(const Point2 &p_pos) const Item *item = nullptr; bool outside = true; - const_cast<RichTextLabel *>(this)->_find_click(main, p_pos, nullptr, nullptr, &item, nullptr, &outside); + const_cast<RichTextLabel *>(this)->_find_click(main, p_pos, nullptr, nullptr, &item, nullptr, &outside, true); if (item && !outside && const_cast<RichTextLabel *>(this)->_find_meta(item, nullptr)) { return CURSOR_POINTING_HAND; @@ -1850,7 +1882,7 @@ void RichTextLabel::gui_input(const Ref<InputEvent> &p_event) { selection.drag_attempt = false; - _find_click(main, b->get_position(), &c_frame, &c_line, &c_item, &c_index, &outside); + _find_click(main, b->get_position(), &c_frame, &c_line, &c_item, &c_index, &outside, false); if (c_item != nullptr) { if (selection.enabled) { selection.click_frame = c_frame; @@ -1888,7 +1920,7 @@ void RichTextLabel::gui_input(const Ref<InputEvent> &p_event) { selection.drag_attempt = false; - _find_click(main, b->get_position(), &c_frame, &c_line, &c_item, &c_index, &outside); + _find_click(main, b->get_position(), &c_frame, &c_line, &c_item, &c_index, &outside, false); if (c_frame) { const Line &l = c_frame->lines[c_line]; @@ -1910,7 +1942,7 @@ void RichTextLabel::gui_input(const Ref<InputEvent> &p_event) { if (DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_CLIPBOARD_PRIMARY)) { DisplayServer::get_singleton()->clipboard_set_primary(get_selected_text()); } - update(); + queue_redraw(); break; } } @@ -1938,7 +1970,7 @@ void RichTextLabel::gui_input(const Ref<InputEvent> &p_event) { Item *c_item = nullptr; bool outside = true; - _find_click(main, b->get_position(), nullptr, nullptr, &c_item, nullptr, &outside); + _find_click(main, b->get_position(), nullptr, nullptr, &c_item, nullptr, &outside, true); if (c_item) { Variant meta; @@ -1994,11 +2026,11 @@ void RichTextLabel::gui_input(const Ref<InputEvent> &p_event) { handled = true; } if (k->is_action("ui_up") && vscroll->is_visible_in_tree()) { - vscroll->set_value(vscroll->get_value() - get_theme_font(SNAME("normal_font"))->get_height(get_theme_font_size(SNAME("normal_font_size")))); + vscroll->set_value(vscroll->get_value() - theme_cache.normal_font->get_height(theme_cache.normal_font_size)); handled = true; } if (k->is_action("ui_down") && vscroll->is_visible_in_tree()) { - vscroll->set_value(vscroll->get_value() + get_theme_font(SNAME("normal_font"))->get_height(get_theme_font_size(SNAME("normal_font_size")))); + vscroll->set_value(vscroll->get_value() + theme_cache.normal_font->get_height(theme_cache.normal_font_size)); handled = true; } if (k->is_action("ui_home") && vscroll->is_visible_in_tree()) { @@ -2044,7 +2076,7 @@ void RichTextLabel::gui_input(const Ref<InputEvent> &p_event) { int c_index = 0; bool outside; - _find_click(main, m->get_position(), &c_frame, &c_line, &c_item, &c_index, &outside); + _find_click(main, m->get_position(), &c_frame, &c_line, &c_item, &c_index, &outside, false); if (selection.click_item && c_item) { selection.from_frame = selection.click_frame; selection.from_line = selection.click_line; @@ -2076,7 +2108,7 @@ void RichTextLabel::gui_input(const Ref<InputEvent> &p_event) { } selection.active = true; - update(); + queue_redraw(); } Variant meta; @@ -2102,7 +2134,7 @@ String RichTextLabel::get_tooltip(const Point2 &p_pos) const { Item *c_item = nullptr; bool outside; - const_cast<RichTextLabel *>(this)->_find_click(main, p_pos, nullptr, nullptr, &c_item, nullptr, &outside); + const_cast<RichTextLabel *>(this)->_find_click(main, p_pos, nullptr, nullptr, &c_item, nullptr, &outside, true); String description; if (c_item && !outside && const_cast<RichTextLabel *>(this)->_find_hint(c_item, &description)) { @@ -2531,9 +2563,11 @@ bool RichTextLabel::_find_layout_subitem(Item *from, Item *to) { void RichTextLabel::_thread_function(void *self) { RichTextLabel *rtl = reinterpret_cast<RichTextLabel *>(self); + rtl->set_physics_process_internal(true); rtl->_process_line_caches(); + rtl->set_physics_process_internal(false); rtl->updating.store(false); - rtl->call_deferred(SNAME("update")); + rtl->call_deferred(SNAME("queue_redraw")); } void RichTextLabel::_stop_thread() { @@ -2554,7 +2588,7 @@ void RichTextLabel::set_threaded(bool p_threaded) { if (threaded != p_threaded) { _stop_thread(); threaded = p_threaded; - update(); + queue_redraw(); } } @@ -2578,14 +2612,12 @@ bool RichTextLabel::_validate_line_caches() { MutexLock data_lock(data_mutex); Rect2 text_rect = _get_text_rect(); - Ref<Font> base_font = get_theme_font(SNAME("normal_font")); - int base_font_size = get_theme_font_size(SNAME("normal_font_size")); int ctrl_height = get_size().height; // Update fonts. if (main->first_invalid_font_line.load() != (int)main->lines.size()) { for (int i = main->first_invalid_font_line.load(); i < (int)main->lines.size(); i++) { - _update_line_font(main, i, base_font, base_font_size); + _update_line_font(main, i, theme_cache.normal_font, theme_cache.normal_font_size); } main->first_resized_line.store(main->first_invalid_font_line.load()); main->first_invalid_font_line.store(main->lines.size()); @@ -2600,7 +2632,7 @@ bool RichTextLabel::_validate_line_caches() { 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); + total_height = _resize_line(main, i, theme_cache.normal_font, theme_cache.normal_font_size, text_rect.get_size().width - scroll_w, total_height); updating_scroll = true; bool exceeds = total_height > ctrl_height && scroll_active; @@ -2620,7 +2652,7 @@ bool RichTextLabel::_validate_line_caches() { 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); + total_height = _resize_line(main, j, theme_cache.normal_font, theme_cache.normal_font_size, text_rect.get_size().width - scroll_w, total_height); main->first_resized_line.store(j); } @@ -2649,11 +2681,10 @@ bool RichTextLabel::_validate_line_caches() { loaded.store(true); thread.start(RichTextLabel::_thread_function, reinterpret_cast<void *>(this)); loading_started = OS::get_singleton()->get_ticks_msec(); - set_physics_process_internal(true); return false; } else { _process_line_caches(); - update(); + queue_redraw(); return true; } } @@ -2667,15 +2698,13 @@ void RichTextLabel::_process_line_caches() { MutexLock data_lock(data_mutex); Rect2 text_rect = _get_text_rect(); - int base_font_size = get_theme_font_size(SNAME("normal_font_size")); - Ref<Font> base_font = get_theme_font(SNAME("normal_font")); int ctrl_height = get_size().height; 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 = (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); + total_height = _shape_line(main, i, theme_cache.normal_font, theme_cache.normal_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) { @@ -2696,7 +2725,7 @@ void RichTextLabel::_process_line_caches() { // 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); + total_height = _resize_line(main, j, theme_cache.normal_font, theme_cache.normal_font_size, text_rect.get_size().width - scroll_w, total_height); main->first_invalid_line.store(j); main->first_resized_line.store(j); @@ -2791,7 +2820,7 @@ void RichTextLabel::add_text(const String &p_text) { pos = end + 1; } - update(); + queue_redraw(); } void RichTextLabel::_add_item(Item *p_item, bool p_enter, bool p_ensure_newline) { @@ -2829,7 +2858,7 @@ void RichTextLabel::_add_item(Item *p_item, bool p_enter, bool p_ensure_newline) if (fixed_width != -1) { update_minimum_size(); } - update(); + queue_redraw(); } void RichTextLabel::_remove_item(Item *p_item, const int p_line, const int p_subitem_line) { @@ -2910,7 +2939,7 @@ void RichTextLabel::add_newline() { _add_item(item, false); current_frame->lines.resize(current_frame->lines.size() + 1); _invalidate_current_line(current_frame); - update(); + queue_redraw(); } bool RichTextLabel::remove_line(const int p_line) { @@ -2949,7 +2978,7 @@ bool RichTextLabel::remove_line(const int p_line) { } main->first_invalid_line.store(0); - update(); + queue_redraw(); return true; } @@ -2989,38 +3018,33 @@ void RichTextLabel::push_font(const Ref<Font> &p_font, int p_size) { } void RichTextLabel::push_normal() { - Ref<Font> normal_font = get_theme_font(SNAME("normal_font")); - ERR_FAIL_COND(normal_font.is_null()); + ERR_FAIL_COND(theme_cache.normal_font.is_null()); - push_font(normal_font, get_theme_font_size(SNAME("normal_font_size"))); + push_font(theme_cache.normal_font, theme_cache.normal_font_size); } void RichTextLabel::push_bold() { - Ref<Font> bold_font = get_theme_font(SNAME("bold_font")); - ERR_FAIL_COND(bold_font.is_null()); + ERR_FAIL_COND(theme_cache.bold_font.is_null()); - push_font(bold_font, get_theme_font_size(SNAME("bold_font_size"))); + push_font(theme_cache.bold_font, theme_cache.bold_font_size); } void RichTextLabel::push_bold_italics() { - Ref<Font> bold_italics_font = get_theme_font(SNAME("bold_italics_font")); - ERR_FAIL_COND(bold_italics_font.is_null()); + ERR_FAIL_COND(theme_cache.bold_italics_font.is_null()); - push_font(bold_italics_font, get_theme_font_size(SNAME("bold_italics_font_size"))); + push_font(theme_cache.bold_italics_font, theme_cache.bold_italics_font_size); } void RichTextLabel::push_italics() { - Ref<Font> italics_font = get_theme_font(SNAME("italics_font")); - ERR_FAIL_COND(italics_font.is_null()); + ERR_FAIL_COND(theme_cache.italics_font.is_null()); - push_font(italics_font, get_theme_font_size(SNAME("italics_font_size"))); + push_font(theme_cache.italics_font, theme_cache.italics_font_size); } void RichTextLabel::push_mono() { - Ref<Font> mono_font = get_theme_font(SNAME("mono_font")); - ERR_FAIL_COND(mono_font.is_null()); + ERR_FAIL_COND(theme_cache.mono_font.is_null()); - push_font(mono_font, get_theme_font_size(SNAME("mono_font_size"))); + push_font(theme_cache.mono_font, theme_cache.mono_font_size); } void RichTextLabel::push_font_size(int p_font_size) { @@ -3370,11 +3394,15 @@ void RichTextLabel::clear() { } void RichTextLabel::set_tab_size(int p_spaces) { + if (tab_size == p_spaces) { + return; + } + _stop_thread(); tab_size = p_spaces; main->first_resized_line.store(0); - update(); + queue_redraw(); } int RichTextLabel::get_tab_size() const { @@ -3393,8 +3421,12 @@ bool RichTextLabel::is_fit_content_height_enabled() const { } void RichTextLabel::set_meta_underline(bool p_underline) { + if (underline_meta == p_underline) { + return; + } + underline_meta = p_underline; - update(); + queue_redraw(); } bool RichTextLabel::is_meta_underlined() const { @@ -3403,7 +3435,7 @@ bool RichTextLabel::is_meta_underlined() const { void RichTextLabel::set_hint_underline(bool p_underline) { underline_hint = p_underline; - update(); + queue_redraw(); } bool RichTextLabel::is_hint_underlined() const { @@ -3429,7 +3461,7 @@ void RichTextLabel::set_scroll_active(bool p_active) { scroll_active = p_active; vscroll->set_drag_node_enabled(p_active); - update(); + queue_redraw(); } bool RichTextLabel::is_scroll_active() const { @@ -3459,13 +3491,6 @@ void RichTextLabel::append_text(const String &p_bbcode) { int pos = 0; List<String> tag_stack; - Ref<Font> normal_font = get_theme_font(SNAME("normal_font")); - Ref<Font> bold_font = get_theme_font(SNAME("bold_font")); - Ref<Font> italics_font = get_theme_font(SNAME("italics_font")); - Ref<Font> bold_italics_font = get_theme_font(SNAME("bold_italics_font")); - Ref<Font> mono_font = get_theme_font(SNAME("mono_font")); - - Color base_color = get_theme_color(SNAME("default_color")); int indent_level = 0; @@ -3610,9 +3635,9 @@ void RichTextLabel::append_text(const String &p_bbcode) { //use bold font in_bold = true; if (in_italics) { - push_font(bold_italics_font, get_theme_font_size(SNAME("bold_italics_font_size"))); + push_font(theme_cache.bold_italics_font, theme_cache.bold_italics_font_size); } else { - push_font(bold_font, get_theme_font_size(SNAME("bold_font_size"))); + push_font(theme_cache.bold_font, theme_cache.bold_font_size); } pos = brk_end + 1; tag_stack.push_front(tag); @@ -3620,15 +3645,15 @@ void RichTextLabel::append_text(const String &p_bbcode) { //use italics font in_italics = true; if (in_bold) { - push_font(bold_italics_font, get_theme_font_size(SNAME("bold_italics_font_size"))); + push_font(theme_cache.bold_italics_font, theme_cache.bold_italics_font_size); } else { - push_font(italics_font, get_theme_font_size(SNAME("italics_font_size"))); + push_font(theme_cache.italics_font, theme_cache.italics_font_size); } pos = brk_end + 1; tag_stack.push_front(tag); } else if (tag == "code") { //use monospace font - push_font(mono_font, get_theme_font_size(SNAME("mono_font_size"))); + push_font(theme_cache.mono_font, theme_cache.mono_font_size); pos = brk_end + 1; tag_stack.push_front(tag); } else if (tag.begins_with("table=")) { @@ -3913,11 +3938,11 @@ void RichTextLabel::append_text(const String &p_bbcode) { tag_stack.push_front("hint"); } else if (tag.begins_with("dropcap")) { Vector<String> subtag = tag.substr(5, tag.length()).split(" "); - int fs = get_theme_font_size(SNAME("normal_font_size")) * 3; - Ref<Font> f = get_theme_font(SNAME("normal_font")); - Color color = get_theme_color(SNAME("default_color")); - Color outline_color = get_theme_color(SNAME("outline_color")); - int outline_size = get_theme_constant(SNAME("outline_size")); + int fs = theme_cache.normal_font_size * 3; + Ref<Font> f = theme_cache.normal_font; + Color color = theme_cache.default_color; + Color outline_color = theme_cache.font_outline_color; + int outline_size = theme_cache.outline_size; Rect2 dropcap_margins = Rect2(); for (int i = 0; i < subtag.size(); i++) { @@ -4035,14 +4060,14 @@ void RichTextLabel::append_text(const String &p_bbcode) { tag_stack.push_front(bbcode_name); } else if (tag.begins_with("color=")) { String color_str = tag.substr(6, tag.length()); - Color color = Color::from_string(color_str, base_color); + Color color = Color::from_string(color_str, theme_cache.default_color); push_color(color); pos = brk_end + 1; tag_stack.push_front("color"); } else if (tag.begins_with("outline_color=")) { String color_str = tag.substr(14, tag.length()); - Color color = Color::from_string(color_str, base_color); + Color color = Color::from_string(color_str, theme_cache.default_color); push_outline_color(color); pos = brk_end + 1; tag_stack.push_front("outline_color"); @@ -4057,7 +4082,7 @@ void RichTextLabel::append_text(const String &p_bbcode) { String fnt_ftr = tag.substr(18, tag.length()); Vector<String> subtag = fnt_ftr.split(","); if (subtag.size() > 0) { - Ref<Font> font = normal_font; + Ref<Font> font = theme_cache.normal_font; int font_size = 0; ItemFont *font_it = _find_font(current); if (font_it) { @@ -4269,7 +4294,7 @@ void RichTextLabel::append_text(const String &p_bbcode) { } else if (tag.begins_with("bgcolor=")) { String color_str = tag.substr(8, tag.length()); - Color color = Color::from_string(color_str, base_color); + Color color = Color::from_string(color_str, theme_cache.default_color); push_bgcolor(color); pos = brk_end + 1; @@ -4277,7 +4302,7 @@ void RichTextLabel::append_text(const String &p_bbcode) { } else if (tag.begins_with("fgcolor=")) { String color_str = tag.substr(8, tag.length()); - Color color = Color::from_string(color_str, base_color); + Color color = Color::from_string(color_str, theme_cache.default_color); push_fgcolor(color); pos = brk_end + 1; @@ -4320,6 +4345,8 @@ void RichTextLabel::append_text(const String &p_bbcode) { } void RichTextLabel::scroll_to_paragraph(int p_paragraph) { + _validate_line_caches(); + if (p_paragraph <= 0) { vscroll->set_value(0); } else if (p_paragraph >= main->first_invalid_line.load()) { @@ -4345,6 +4372,8 @@ void RichTextLabel::scroll_to_line(int p_line) { vscroll->set_value(0); return; } + _validate_line_caches(); + int line_count = 0; int to_line = main->first_invalid_line.load(); for (int i = 0; i < to_line; i++) { @@ -4352,7 +4381,7 @@ void RichTextLabel::scroll_to_line(int p_line) { if ((line_count <= p_line) && (line_count + main->lines[i].text_buf->get_line_count() >= p_line)) { float line_offset = 0.f; for (int j = 0; j < p_line - line_count; j++) { - line_offset += main->lines[i].text_buf->get_line_size(j).y + get_theme_constant(SNAME("line_separation")); + line_offset += main->lines[i].text_buf->get_line_size(j).y + theme_cache.line_separation; } vscroll->set_value(main->lines[i].offset.y + line_offset); return; @@ -4370,7 +4399,7 @@ float RichTextLabel::get_line_offset(int p_line) { if ((line_count <= p_line) && (p_line <= line_count + main->lines[i].text_buf->get_line_count())) { float line_offset = 0.f; for (int j = 0; j < p_line - line_count; j++) { - line_offset += main->lines[i].text_buf->get_line_size(j).y + get_theme_constant(SNAME("line_separation")); + line_offset += main->lines[i].text_buf->get_line_size(j).y + theme_cache.line_separation; } return main->lines[i].offset.y + line_offset; } @@ -4405,6 +4434,10 @@ int RichTextLabel::get_visible_line_count() const { } void RichTextLabel::set_selection_enabled(bool p_enabled) { + if (selection.enabled == p_enabled) { + return; + } + selection.enabled = p_enabled; if (!p_enabled) { if (selection.active) { @@ -4417,6 +4450,10 @@ void RichTextLabel::set_selection_enabled(bool p_enabled) { } void RichTextLabel::set_deselect_on_focus_loss_enabled(const bool p_enabled) { + if (deselect_on_focus_loss_enabled == p_enabled) { + return; + } + deselect_on_focus_loss_enabled = p_enabled; if (p_enabled && selection.active && !has_focus()) { deselect(); @@ -4542,7 +4579,7 @@ bool RichTextLabel::search(const String &p_string, bool p_from_selection, bool p if (!(p_search_previous && char_idx < 0) && _search_line(selection.from_frame, selection.from_line, p_string, char_idx, p_search_previous)) { scroll_to_line(selection.from_frame->line + selection.from_line); - update(); + queue_redraw(); return true; } char_idx = p_search_previous ? -1 : 0; @@ -4567,7 +4604,7 @@ bool RichTextLabel::search(const String &p_string, bool p_from_selection, bool p // Search for next element if (_search_table(parent_table, parent_element, p_string, p_search_previous)) { scroll_to_line(selection.from_frame->line + selection.from_line); - update(); + queue_redraw(); return true; } } @@ -4591,7 +4628,7 @@ bool RichTextLabel::search(const String &p_string, bool p_from_selection, bool p if (_search_line(main, current_line, p_string, char_idx, p_search_previous)) { scroll_to_line(current_line); - update(); + queue_redraw(); return true; } p_search_previous ? current_line-- : current_line++; @@ -4701,7 +4738,7 @@ String RichTextLabel::get_selected_text() const { void RichTextLabel::deselect() { selection.active = false; - update(); + queue_redraw(); } void RichTextLabel::selection_copy() { @@ -4756,7 +4793,7 @@ void RichTextLabel::select_all() { selection.to_char = to_frame->lines[to_line].char_count; selection.to_item = to_item; selection.active = true; - update(); + queue_redraw(); } bool RichTextLabel::is_selection_enabled() const { @@ -4784,6 +4821,10 @@ int RichTextLabel::get_selection_to() const { } void RichTextLabel::set_text(const String &p_bbcode) { + if (text == p_bbcode) { + return; + } + text = p_bbcode; if (use_bbcode) { parse_bbcode(p_bbcode); @@ -4840,7 +4881,7 @@ void RichTextLabel::set_text_direction(Control::TextDirection p_text_direction) text_direction = p_text_direction; main->first_invalid_line.store(0); //invalidate ALL _validate_line_caches(); - update(); + queue_redraw(); } } @@ -4851,7 +4892,7 @@ void RichTextLabel::set_structured_text_bidi_override(TextServer::StructuredText st_parser = p_parser; main->first_invalid_line.store(0); //invalidate ALL _validate_line_caches(); - update(); + queue_redraw(); } } @@ -4866,7 +4907,7 @@ void RichTextLabel::set_structured_text_bidi_override_options(Array p_args) { st_args = p_args; main->first_invalid_line.store(0); //invalidate ALL _validate_line_caches(); - update(); + queue_redraw(); } } @@ -4885,7 +4926,7 @@ void RichTextLabel::set_language(const String &p_language) { language = p_language; main->first_invalid_line.store(0); //invalidate ALL _validate_line_caches(); - update(); + queue_redraw(); } } @@ -4900,7 +4941,7 @@ void RichTextLabel::set_autowrap_mode(TextServer::AutowrapMode p_mode) { autowrap_mode = p_mode; main->first_invalid_line = 0; //invalidate ALL _validate_line_caches(); - update(); + queue_redraw(); } } @@ -4908,27 +4949,31 @@ TextServer::AutowrapMode RichTextLabel::get_autowrap_mode() const { return autowrap_mode; } -void RichTextLabel::set_percent_visible(float p_percent) { - if (percent_visible != p_percent) { +void RichTextLabel::set_visible_ratio(float p_ratio) { + if (visible_ratio != p_ratio) { _stop_thread(); - if (p_percent < 0 || p_percent >= 1) { + if (p_ratio >= 1.0) { visible_characters = -1; - percent_visible = 1; + visible_ratio = 1.0; + } else if (p_ratio < 0.0) { + visible_characters = 0; + visible_ratio = 0.0; } else { - visible_characters = get_total_character_count() * p_percent; - percent_visible = p_percent; + visible_characters = get_total_character_count() * p_ratio; + visible_ratio = p_ratio; } + if (visible_chars_behavior == TextServer::VC_CHARS_BEFORE_SHAPING) { - main->first_invalid_line.store(0); //invalidate ALL + main->first_invalid_line.store(0); // Invalidate ALL. _validate_line_caches(); } - update(); + queue_redraw(); } } -float RichTextLabel::get_percent_visible() const { - return percent_visible; +float RichTextLabel::get_visible_ratio() const { + return visible_ratio; } void RichTextLabel::set_effects(Array p_effects) { @@ -4954,16 +4999,20 @@ void RichTextLabel::install_effect(const Variant effect) { } int RichTextLabel::get_content_height() const { + const_cast<RichTextLabel *>(this)->_validate_line_caches(); + int total_height = 0; int to_line = main->first_invalid_line.load(); if (to_line) { MutexLock lock(main->lines[to_line - 1].text_buf->get_mutex()); - total_height = main->lines[to_line - 1].offset.y + main->lines[to_line - 1].text_buf->get_size().y + main->lines[to_line - 1].text_buf->get_line_count() * get_theme_constant(SNAME("line_separation")); + total_height = main->lines[to_line - 1].offset.y + main->lines[to_line - 1].text_buf->get_size().y + main->lines[to_line - 1].text_buf->get_line_count() * theme_cache.line_separation; } return total_height; } int RichTextLabel::get_content_width() const { + const_cast<RichTextLabel *>(this)->_validate_line_caches(); + int total_width = 0; int to_line = main->first_invalid_line.load(); for (int i = 0; i < to_line; i++) { @@ -5099,8 +5148,8 @@ void RichTextLabel::_bind_methods() { ClassDB::bind_method(D_METHOD("get_visible_characters_behavior"), &RichTextLabel::get_visible_characters_behavior); ClassDB::bind_method(D_METHOD("set_visible_characters_behavior", "behavior"), &RichTextLabel::set_visible_characters_behavior); - ClassDB::bind_method(D_METHOD("set_percent_visible", "percent_visible"), &RichTextLabel::set_percent_visible); - ClassDB::bind_method(D_METHOD("get_percent_visible"), &RichTextLabel::get_percent_visible); + ClassDB::bind_method(D_METHOD("set_visible_ratio", "ratio"), &RichTextLabel::set_visible_ratio); + ClassDB::bind_method(D_METHOD("get_visible_ratio"), &RichTextLabel::get_visible_ratio); ClassDB::bind_method(D_METHOD("get_character_line", "character"), &RichTextLabel::get_character_line); ClassDB::bind_method(D_METHOD("get_character_paragraph", "character"), &RichTextLabel::get_character_paragraph); @@ -5132,30 +5181,35 @@ void RichTextLabel::_bind_methods() { // Note: set "bbcode_enabled" first, to avoid unnecessary "text" resets. ADD_PROPERTY(PropertyInfo(Variant::BOOL, "bbcode_enabled"), "set_use_bbcode", "is_using_bbcode"); - - ADD_PROPERTY(PropertyInfo(Variant::BOOL, "threaded"), "set_threaded", "is_threaded"); - ADD_PROPERTY(PropertyInfo(Variant::INT, "progress_bar_delay", PROPERTY_HINT_NONE, "suffix:ms"), "set_progress_bar_delay", "get_progress_bar_delay"); - - ADD_PROPERTY(PropertyInfo(Variant::INT, "tab_size", PROPERTY_HINT_RANGE, "0,24,1"), "set_tab_size", "get_tab_size"); ADD_PROPERTY(PropertyInfo(Variant::STRING, "text", PROPERTY_HINT_MULTILINE_TEXT), "set_text", "get_text"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "fit_content_height"), "set_fit_content_height", "is_fit_content_height_enabled"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "scroll_active"), "set_scroll_active", "is_scroll_active"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "scroll_following"), "set_scroll_follow", "is_scroll_following"); - ADD_PROPERTY(PropertyInfo(Variant::BOOL, "selection_enabled"), "set_selection_enabled", "is_selection_enabled"); - ADD_PROPERTY(PropertyInfo(Variant::BOOL, "override_selected_font_color"), "set_override_selected_font_color", "is_overriding_selected_font_color"); - ADD_PROPERTY(PropertyInfo(Variant::BOOL, "deselect_on_focus_loss_enabled"), "set_deselect_on_focus_loss_enabled", "is_deselect_on_focus_loss_enabled"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "autowrap_mode", PROPERTY_HINT_ENUM, "Off,Arbitrary,Word,Word (Smart)"), "set_autowrap_mode", "get_autowrap_mode"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "tab_size", PROPERTY_HINT_RANGE, "0,24,1"), "set_tab_size", "get_tab_size"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "context_menu_enabled"), "set_context_menu_enabled", "is_context_menu_enabled"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "shortcut_keys_enabled"), "set_shortcut_keys_enabled", "is_shortcut_keys_enabled"); + + ADD_GROUP("Markup", ""); ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "custom_effects", PROPERTY_HINT_ARRAY_TYPE, vformat("%s/%s:%s", Variant::OBJECT, PROPERTY_HINT_RESOURCE_TYPE, "RichTextEffect"), (PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_SCRIPT_VARIABLE)), "set_effects", "get_effects"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "meta_underlined"), "set_meta_underline", "is_meta_underlined"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "hint_underlined"), "set_hint_underline", "is_hint_underlined"); - ADD_PROPERTY(PropertyInfo(Variant::INT, "autowrap_mode", PROPERTY_HINT_ENUM, "Off,Arbitrary,Word,Word (Smart)"), "set_autowrap_mode", "get_autowrap_mode"); - // Note: "visible_characters" and "percent_visible" should be set after "text" to be correctly applied. + ADD_GROUP("Threading", ""); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "threaded"), "set_threaded", "is_threaded"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "progress_bar_delay", PROPERTY_HINT_NONE, "suffix:ms"), "set_progress_bar_delay", "get_progress_bar_delay"); + + ADD_GROUP("Text Selection", ""); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "selection_enabled"), "set_selection_enabled", "is_selection_enabled"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "override_selected_font_color"), "set_override_selected_font_color", "is_overriding_selected_font_color"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "deselect_on_focus_loss_enabled"), "set_deselect_on_focus_loss_enabled", "is_deselect_on_focus_loss_enabled"); + + ADD_GROUP("Displayed Text", ""); + // Note: "visible_characters" and "visible_ratio" should be set after "text" to be correctly applied. ADD_PROPERTY(PropertyInfo(Variant::INT, "visible_characters", PROPERTY_HINT_RANGE, "-1,128000,1"), "set_visible_characters", "get_visible_characters"); ADD_PROPERTY(PropertyInfo(Variant::INT, "visible_characters_behavior", PROPERTY_HINT_ENUM, "Characters Before Shaping,Characters After Shaping,Glyphs (Layout Direction),Glyphs (Left-to-Right),Glyphs (Right-to-Left)"), "set_visible_characters_behavior", "get_visible_characters_behavior"); - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "percent_visible", PROPERTY_HINT_RANGE, "0,1,0.001"), "set_percent_visible", "get_percent_visible"); - - ADD_PROPERTY(PropertyInfo(Variant::BOOL, "context_menu_enabled"), "set_context_menu_enabled", "is_context_menu_enabled"); - ADD_PROPERTY(PropertyInfo(Variant::BOOL, "shortcut_keys_enabled"), "set_shortcut_keys_enabled", "is_shortcut_keys_enabled"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "visible_ratio", PROPERTY_HINT_RANGE, "0,1,0.001"), "set_visible_ratio", "get_visible_ratio"); ADD_GROUP("BiDi", ""); ADD_PROPERTY(PropertyInfo(Variant::INT, "text_direction", PROPERTY_HINT_ENUM, "Auto,Left-to-Right,Right-to-Left,Inherited"), "set_text_direction", "get_text_direction"); @@ -5214,7 +5268,7 @@ void RichTextLabel::set_visible_characters_behavior(TextServer::VisibleCharacter visible_chars_behavior = p_behavior; main->first_invalid_line.store(0); //invalidate ALL _validate_line_caches(); - update(); + queue_redraw(); } } @@ -5224,18 +5278,18 @@ void RichTextLabel::set_visible_characters(int p_visible) { visible_characters = p_visible; if (p_visible == -1) { - percent_visible = 1; + visible_ratio = 1; } else { int total_char_count = get_total_character_count(); if (total_char_count > 0) { - percent_visible = (float)p_visible / (float)total_char_count; + visible_ratio = (float)p_visible / (float)total_char_count; } } if (visible_chars_behavior == TextServer::VC_CHARS_BEFORE_SHAPING) { main->first_invalid_line.store(0); //invalidate ALL _validate_line_caches(); } - update(); + queue_redraw(); } } @@ -5312,13 +5366,16 @@ int RichTextLabel::get_total_glyph_count() const { } void RichTextLabel::set_fixed_size_to_width(int p_width) { + if (fixed_width == p_width) { + return; + } + fixed_width = p_width; update_minimum_size(); } Size2 RichTextLabel::get_minimum_size() const { - Ref<StyleBox> style = get_theme_stylebox(SNAME("normal")); - Size2 size = style->get_minimum_size(); + Size2 size = theme_cache.normal_style->get_minimum_size(); if (fixed_width != -1) { size.x += fixed_width; diff --git a/scene/gui/rich_text_label.h b/scene/gui/rich_text_label.h index e5f0469c01..8bc28a9ecf 100644 --- a/scene/gui/rich_text_label.h +++ b/scene/gui/rich_text_label.h @@ -83,6 +83,7 @@ public: }; protected: + virtual void _update_theme_item_cache() override; void _notification(int p_what); static void _bind_methods(); @@ -440,11 +441,11 @@ private: void _menu_option(int p_option); int visible_characters = -1; - float percent_visible = 1.0; + float visible_ratio = 1.0; TextServer::VisibleCharactersBehavior visible_chars_behavior = TextServer::VC_CHARS_BEFORE_SHAPING; bool _is_click_inside_selection() const; - void _find_click(ItemFrame *p_frame, const Point2i &p_click, ItemFrame **r_click_frame = nullptr, int *r_click_line = nullptr, Item **r_click_item = nullptr, int *r_click_char = nullptr, bool *r_outside = nullptr); + void _find_click(ItemFrame *p_frame, const Point2i &p_click, ItemFrame **r_click_frame = nullptr, int *r_click_line = nullptr, Item **r_click_item = nullptr, int *r_click_char = nullptr, bool *r_outside = nullptr, bool p_meta = false); String _get_line_text(ItemFrame *p_frame, int p_line, Selection p_sel) const; bool _search_line(ItemFrame *p_frame, int p_line, const String &p_string, int p_char_idx, bool p_reverse_search); @@ -455,7 +456,7 @@ private: void _update_line_font(ItemFrame *p_frame, int p_line, const Ref<Font> &p_base_font, int p_base_font_size); int _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); - float _find_click_in_line(ItemFrame *p_frame, int p_line, const Vector2 &p_ofs, int p_width, const Point2i &p_click, ItemFrame **r_click_frame = nullptr, int *r_click_line = nullptr, Item **r_click_item = nullptr, int *r_click_char = nullptr, bool p_table = false); + float _find_click_in_line(ItemFrame *p_frame, int p_line, const Vector2 &p_ofs, int p_width, const Point2i &p_click, ItemFrame **r_click_frame = nullptr, int *r_click_line = nullptr, Item **r_click_item = nullptr, int *r_click_char = nullptr, bool p_table = false, bool p_meta = false); String _roman(int p_num, bool p_capitalize) const; String _letters(int p_num, bool p_capitalize) const; @@ -512,6 +513,46 @@ private: bool fit_content_height = false; + struct ThemeCache { + Ref<StyleBox> normal_style; + Ref<StyleBox> focus_style; + Ref<StyleBox> progress_bg_style; + Ref<StyleBox> progress_fg_style; + + int line_separation; + + Ref<Font> normal_font; + int normal_font_size; + + Color default_color; + Color font_selected_color; + Color selection_color; + Color font_outline_color; + Color font_shadow_color; + int shadow_outline_size; + int shadow_offset_x; + int shadow_offset_y; + int outline_size; + Color outline_color; + + Ref<Font> bold_font; + int bold_font_size; + Ref<Font> bold_italics_font; + int bold_italics_font_size; + Ref<Font> italics_font; + int italics_font_size; + Ref<Font> mono_font; + int mono_font_size; + + int table_h_separation; + int table_v_separation; + Color table_odd_row_bg; + Color table_even_row_bg; + Color table_border; + + float base_scale = 1.0; + } theme_cache; + public: String get_parsed_text() const; void add_text(const String &p_text); @@ -660,8 +701,8 @@ public: int get_total_character_count() const; int get_total_glyph_count() const; - void set_percent_visible(float p_percent); - float get_percent_visible() const; + void set_visible_ratio(float p_ratio); + float get_visible_ratio() const; TextServer::VisibleCharactersBehavior get_visible_characters_behavior() const; void set_visible_characters_behavior(TextServer::VisibleCharactersBehavior p_behavior); diff --git a/scene/gui/scroll_bar.cpp b/scene/gui/scroll_bar.cpp index 48c57d9b1b..6c05b171e3 100644 --- a/scene/gui/scroll_bar.cpp +++ b/scene/gui/scroll_bar.cpp @@ -70,8 +70,8 @@ void ScrollBar::gui_input(const Ref<InputEvent> &p_event) { if (b->is_pressed()) { double ofs = orientation == VERTICAL ? b->get_position().y : b->get_position().x; - Ref<Texture2D> decr = get_theme_icon(SNAME("decrement")); - Ref<Texture2D> incr = get_theme_icon(SNAME("increment")); + Ref<Texture2D> decr = theme_cache.decrement_icon; + Ref<Texture2D> incr = theme_cache.increment_icon; double decr_size = orientation == VERTICAL ? decr->get_height() : decr->get_width(); double incr_size = orientation == VERTICAL ? incr->get_height() : incr->get_width(); @@ -82,14 +82,14 @@ void ScrollBar::gui_input(const Ref<InputEvent> &p_event) { if (ofs < decr_size) { decr_active = true; set_value(get_value() - (custom_step >= 0 ? custom_step : get_step())); - update(); + queue_redraw(); return; } if (ofs > total - incr_size) { incr_active = true; set_value(get_value() + (custom_step >= 0 ? custom_step : get_step())); - update(); + queue_redraw(); return; } @@ -117,7 +117,7 @@ void ScrollBar::gui_input(const Ref<InputEvent> &p_event) { drag.active = true; drag.pos_at_click = grabber_ofs + ofs; drag.value_at_click = get_as_ratio(); - update(); + queue_redraw(); } else { if (scrolling) { target_scroll = CLAMP(target_scroll + get_page(), get_min(), get_max() - get_page()); @@ -137,7 +137,7 @@ void ScrollBar::gui_input(const Ref<InputEvent> &p_event) { incr_active = false; decr_active = false; drag.active = false; - update(); + queue_redraw(); } } @@ -146,7 +146,7 @@ void ScrollBar::gui_input(const Ref<InputEvent> &p_event) { if (drag.active) { double ofs = orientation == VERTICAL ? m->get_position().y : m->get_position().x; - Ref<Texture2D> decr = get_theme_icon(SNAME("decrement")); + Ref<Texture2D> decr = theme_cache.decrement_icon; double decr_size = orientation == VERTICAL ? decr->get_height() : decr->get_width(); ofs -= decr_size; @@ -156,8 +156,8 @@ void ScrollBar::gui_input(const Ref<InputEvent> &p_event) { set_as_ratio(drag.value_at_click + diff); } else { double ofs = orientation == VERTICAL ? m->get_position().y : m->get_position().x; - Ref<Texture2D> decr = get_theme_icon(SNAME("decrement")); - Ref<Texture2D> incr = get_theme_icon(SNAME("increment")); + Ref<Texture2D> decr = theme_cache.decrement_icon; + Ref<Texture2D> incr = theme_cache.increment_icon; double decr_size = orientation == VERTICAL ? decr->get_height() : decr->get_width(); double incr_size = orientation == VERTICAL ? incr->get_height() : incr->get_width(); @@ -177,7 +177,7 @@ void ScrollBar::gui_input(const Ref<InputEvent> &p_event) { if (new_hilite != highlight) { highlight = new_hilite; - update(); + queue_redraw(); } } } @@ -217,6 +217,24 @@ void ScrollBar::gui_input(const Ref<InputEvent> &p_event) { } } +void ScrollBar::_update_theme_item_cache() { + Range::_update_theme_item_cache(); + + theme_cache.scroll_style = get_theme_stylebox(SNAME("scroll")); + theme_cache.scroll_focus_style = get_theme_stylebox(SNAME("scroll_focus")); + theme_cache.scroll_offset_style = get_theme_stylebox(SNAME("hscroll")); + theme_cache.grabber_style = get_theme_stylebox(SNAME("grabber")); + theme_cache.grabber_hl_style = get_theme_stylebox(SNAME("grabber_highlight")); + theme_cache.grabber_pressed_style = get_theme_stylebox(SNAME("grabber_pressed")); + + theme_cache.increment_icon = get_theme_icon(SNAME("increment")); + theme_cache.increment_hl_icon = get_theme_icon(SNAME("increment_highlight")); + theme_cache.increment_pressed_icon = get_theme_icon(SNAME("increment_pressed")); + theme_cache.decrement_icon = get_theme_icon(SNAME("decrement")); + theme_cache.decrement_hl_icon = get_theme_icon(SNAME("decrement_highlight")); + theme_cache.decrement_pressed_icon = get_theme_icon(SNAME("decrement_pressed")); +} + void ScrollBar::_notification(int p_what) { switch (p_what) { case NOTIFICATION_DRAW: { @@ -225,30 +243,30 @@ void ScrollBar::_notification(int p_what) { Ref<Texture2D> decr, incr; if (decr_active) { - decr = get_theme_icon(SNAME("decrement_pressed")); + decr = theme_cache.decrement_pressed_icon; } else if (highlight == HIGHLIGHT_DECR) { - decr = get_theme_icon(SNAME("decrement_highlight")); + decr = theme_cache.decrement_hl_icon; } else { - decr = get_theme_icon(SNAME("decrement")); + decr = theme_cache.decrement_icon; } if (incr_active) { - incr = get_theme_icon(SNAME("increment_pressed")); + incr = theme_cache.increment_pressed_icon; } else if (highlight == HIGHLIGHT_INCR) { - incr = get_theme_icon(SNAME("increment_highlight")); + incr = theme_cache.increment_hl_icon; } else { - incr = get_theme_icon(SNAME("increment")); + incr = theme_cache.increment_icon; } - Ref<StyleBox> bg = has_focus() ? get_theme_stylebox(SNAME("scroll_focus")) : get_theme_stylebox(SNAME("scroll")); + Ref<StyleBox> bg = has_focus() ? theme_cache.scroll_focus_style : theme_cache.scroll_style; Ref<StyleBox> grabber; if (drag.active) { - grabber = get_theme_stylebox(SNAME("grabber_pressed")); + grabber = theme_cache.grabber_pressed_style; } else if (highlight == HIGHLIGHT_RANGE) { - grabber = get_theme_stylebox(SNAME("grabber_highlight")); + grabber = theme_cache.grabber_hl_style; } else { - grabber = get_theme_stylebox(SNAME("grabber")); + grabber = theme_cache.grabber_style; } Point2 ofs; @@ -303,7 +321,7 @@ void ScrollBar::_notification(int p_what) { if (drag_node) { drag_node->connect("gui_input", callable_mp(this, &ScrollBar::_drag_node_input)); - drag_node->connect("tree_exiting", callable_mp(this, &ScrollBar::_drag_node_exit), CONNECT_ONESHOT); + drag_node->connect("tree_exiting", callable_mp(this, &ScrollBar::_drag_node_exit), CONNECT_ONE_SHOT); } } break; @@ -408,13 +426,13 @@ void ScrollBar::_notification(int p_what) { case NOTIFICATION_MOUSE_EXIT: { highlight = HIGHLIGHT_NONE; - update(); + queue_redraw(); } break; } } double ScrollBar::get_grabber_min_size() const { - Ref<StyleBox> grabber = get_theme_stylebox(SNAME("grabber")); + Ref<StyleBox> grabber = theme_cache.grabber_style; Size2 gminsize = grabber->get_minimum_size() + grabber->get_center_size(); return (orientation == VERTICAL) ? gminsize.height : gminsize.width; } @@ -435,17 +453,17 @@ double ScrollBar::get_area_size() const { switch (orientation) { case VERTICAL: { double area = get_size().height; - area -= get_theme_stylebox(SNAME("scroll"))->get_minimum_size().height; - area -= get_theme_icon(SNAME("increment"))->get_height(); - area -= get_theme_icon(SNAME("decrement"))->get_height(); + area -= theme_cache.scroll_style->get_minimum_size().height; + area -= theme_cache.increment_icon->get_height(); + area -= theme_cache.decrement_icon->get_height(); area -= get_grabber_min_size(); return area; } break; case HORIZONTAL: { double area = get_size().width; - area -= get_theme_stylebox(SNAME("scroll"))->get_minimum_size().width; - area -= get_theme_icon(SNAME("increment"))->get_width(); - area -= get_theme_icon(SNAME("decrement"))->get_width(); + area -= theme_cache.scroll_style->get_minimum_size().width; + area -= theme_cache.increment_icon->get_width(); + area -= theme_cache.decrement_icon->get_width(); area -= get_grabber_min_size(); return area; } break; @@ -459,13 +477,13 @@ double ScrollBar::get_area_offset() const { double ofs = 0.0; if (orientation == VERTICAL) { - ofs += get_theme_stylebox(SNAME("hscroll"))->get_margin(SIDE_TOP); - ofs += get_theme_icon(SNAME("decrement"))->get_height(); + ofs += theme_cache.scroll_offset_style->get_margin(SIDE_TOP); + ofs += theme_cache.decrement_icon->get_height(); } if (orientation == HORIZONTAL) { - ofs += get_theme_stylebox(SNAME("hscroll"))->get_margin(SIDE_LEFT); - ofs += get_theme_icon(SNAME("decrement"))->get_width(); + ofs += theme_cache.scroll_offset_style->get_margin(SIDE_LEFT); + ofs += theme_cache.decrement_icon->get_width(); } return ofs; @@ -476,9 +494,9 @@ double ScrollBar::get_grabber_offset() const { } Size2 ScrollBar::get_minimum_size() const { - Ref<Texture2D> incr = get_theme_icon(SNAME("increment")); - Ref<Texture2D> decr = get_theme_icon(SNAME("decrement")); - Ref<StyleBox> bg = get_theme_stylebox(SNAME("scroll")); + Ref<Texture2D> incr = theme_cache.increment_icon; + Ref<Texture2D> decr = theme_cache.decrement_icon; + Ref<StyleBox> bg = theme_cache.scroll_style; Size2 minsize; if (orientation == VERTICAL) { @@ -595,7 +613,7 @@ void ScrollBar::set_drag_node(const NodePath &p_path) { if (drag_node) { drag_node->connect("gui_input", callable_mp(this, &ScrollBar::_drag_node_input)); - drag_node->connect("tree_exiting", callable_mp(this, &ScrollBar::_drag_node_exit), CONNECT_ONESHOT); + drag_node->connect("tree_exiting", callable_mp(this, &ScrollBar::_drag_node_exit), CONNECT_ONE_SHOT); } } } diff --git a/scene/gui/scroll_bar.h b/scene/gui/scroll_bar.h index 1823f86a67..13ca62d7ff 100644 --- a/scene/gui/scroll_bar.h +++ b/scene/gui/scroll_bar.h @@ -86,14 +86,31 @@ class ScrollBar : public Range { double target_scroll = 0.0; bool smooth_scroll_enabled = false; + struct ThemeCache { + Ref<StyleBox> scroll_style; + Ref<StyleBox> scroll_focus_style; + Ref<StyleBox> scroll_offset_style; + Ref<StyleBox> grabber_style; + Ref<StyleBox> grabber_hl_style; + Ref<StyleBox> grabber_pressed_style; + + Ref<Texture2D> increment_icon; + Ref<Texture2D> increment_hl_icon; + Ref<Texture2D> increment_pressed_icon; + Ref<Texture2D> decrement_icon; + Ref<Texture2D> decrement_hl_icon; + Ref<Texture2D> decrement_pressed_icon; + } theme_cache; + void _drag_node_exit(); void _drag_node_input(const Ref<InputEvent> &p_input); virtual void gui_input(const Ref<InputEvent> &p_event) override; protected: - void _notification(int p_what); + virtual void _update_theme_item_cache() override; + void _notification(int p_what); static void _bind_methods(); public: diff --git a/scene/gui/scroll_container.cpp b/scene/gui/scroll_container.cpp index 8fd547813d..c12ac115b7 100644 --- a/scene/gui/scroll_container.cpp +++ b/scene/gui/scroll_container.cpp @@ -35,7 +35,6 @@ #include "scene/main/window.h" Size2 ScrollContainer::get_minimum_size() const { - Ref<StyleBox> sb = get_theme_stylebox(SNAME("bg")); Size2 min_size; // Calculated in this function, as it needs to traverse all child controls once to calculate; @@ -77,10 +76,16 @@ Size2 ScrollContainer::get_minimum_size() const { min_size.x += v_scroll->get_minimum_size().x; } - min_size += sb->get_minimum_size(); + min_size += theme_cache.panel_style->get_minimum_size(); return min_size; } +void ScrollContainer::_update_theme_item_cache() { + Container::_update_theme_item_cache(); + + theme_cache.panel_style = get_theme_stylebox(SNAME("panel")); +} + void ScrollContainer::_cancel_drag() { set_physics_process_internal(false); drag_touching_deaccel = false; @@ -271,9 +276,8 @@ void ScrollContainer::_reposition_children() { Size2 size = get_size(); Point2 ofs; - Ref<StyleBox> sb = get_theme_stylebox(SNAME("bg")); - size -= sb->get_minimum_size(); - ofs += sb->get_offset(); + size -= theme_cache.panel_style->get_minimum_size(); + ofs += theme_cache.panel_style->get_offset(); bool rtl = is_layout_rtl(); if (h_scroll->is_visible_in_tree() && h_scroll->get_parent() == this) { //scrolls may have been moved out for reasons @@ -312,7 +316,7 @@ void ScrollContainer::_reposition_children() { fit_child_in_rect(c, r); } - update(); + queue_redraw(); } void ScrollContainer::_notification(int p_what) { @@ -337,8 +341,7 @@ void ScrollContainer::_notification(int p_what) { } break; case NOTIFICATION_DRAW: { - Ref<StyleBox> sb = get_theme_stylebox(SNAME("bg")); - draw_style_box(sb, Rect2(Vector2(), get_size())); + draw_style_box(theme_cache.panel_style, Rect2(Vector2(), get_size())); } break; case NOTIFICATION_INTERNAL_PHYSICS_PROCESS: { @@ -413,8 +416,7 @@ void ScrollContainer::_notification(int p_what) { void ScrollContainer::update_scrollbars() { Size2 size = get_size(); - Ref<StyleBox> sb = get_theme_stylebox(SNAME("bg")); - size -= sb->get_minimum_size(); + size -= theme_cache.panel_style->get_minimum_size(); Size2 hmin = h_scroll->get_combined_minimum_size(); Size2 vmin = v_scroll->get_combined_minimum_size(); diff --git a/scene/gui/scroll_container.h b/scene/gui/scroll_container.h index bfa74cfd0f..f4899846f4 100644 --- a/scene/gui/scroll_container.h +++ b/scene/gui/scroll_container.h @@ -69,9 +69,14 @@ private: int deadzone = 0; bool follow_focus = false; + struct ThemeCache { + Ref<StyleBox> panel_style; + } theme_cache; + void _cancel_drag(); protected: + virtual void _update_theme_item_cache() override; Size2 get_minimum_size() const override; void _gui_focus_changed(Control *p_control); diff --git a/scene/gui/separator.cpp b/scene/gui/separator.cpp index e3400d9c8f..8177c1e469 100644 --- a/scene/gui/separator.cpp +++ b/scene/gui/separator.cpp @@ -33,24 +33,30 @@ Size2 Separator::get_minimum_size() const { Size2 ms(3, 3); if (orientation == VERTICAL) { - ms.x = get_theme_constant(SNAME("separation")); + ms.x = theme_cache.separation; } else { // HORIZONTAL - ms.y = get_theme_constant(SNAME("separation")); + ms.y = theme_cache.separation; } return ms; } +void Separator::_update_theme_item_cache() { + Control::_update_theme_item_cache(); + + theme_cache.separation = get_theme_constant(SNAME("separation")); + theme_cache.separator_style = get_theme_stylebox(SNAME("separator")); +} + void Separator::_notification(int p_what) { switch (p_what) { case NOTIFICATION_DRAW: { Size2i size = get_size(); - Ref<StyleBox> style = get_theme_stylebox(SNAME("separator")); - Size2i ssize = style->get_minimum_size() + style->get_center_size(); + Size2i ssize = theme_cache.separator_style->get_minimum_size() + theme_cache.separator_style->get_center_size(); if (orientation == VERTICAL) { - style->draw(get_canvas_item(), Rect2((size.x - ssize.x) / 2, 0, ssize.x, size.y)); + theme_cache.separator_style->draw(get_canvas_item(), Rect2((size.x - ssize.x) / 2, 0, ssize.x, size.y)); } else { - style->draw(get_canvas_item(), Rect2(0, (size.y - ssize.y) / 2, size.x, ssize.y)); + theme_cache.separator_style->draw(get_canvas_item(), Rect2(0, (size.y - ssize.y) / 2, size.x, ssize.y)); } } break; } diff --git a/scene/gui/separator.h b/scene/gui/separator.h index e6578a4d04..44e18a3f00 100644 --- a/scene/gui/separator.h +++ b/scene/gui/separator.h @@ -35,8 +35,16 @@ class Separator : public Control { GDCLASS(Separator, Control); + struct ThemeCache { + int separation = 0; + Ref<StyleBox> separator_style; + } theme_cache; + protected: Orientation orientation = Orientation::HORIZONTAL; + + virtual void _update_theme_item_cache() override; + void _notification(int p_what); public: diff --git a/scene/gui/slider.cpp b/scene/gui/slider.cpp index 64c07007dc..ff3adfb9ac 100644 --- a/scene/gui/slider.cpp +++ b/scene/gui/slider.cpp @@ -33,11 +33,8 @@ #include "core/os/keyboard.h" Size2 Slider::get_minimum_size() const { - Ref<StyleBox> style = get_theme_stylebox(SNAME("slider")); - Size2i ss = style->get_minimum_size() + style->get_center_size(); - - Ref<Texture2D> grabber = get_theme_icon(SNAME("grabber")); - Size2i rs = grabber->get_size(); + Size2i ss = theme_cache.slider_style->get_minimum_size() + theme_cache.slider_style->get_center_size(); + Size2i rs = theme_cache.grabber_icon->get_size(); if (orientation == HORIZONTAL) { return Size2i(ss.width, MAX(ss.height, rs.height)); @@ -58,7 +55,13 @@ void Slider::gui_input(const Ref<InputEvent> &p_event) { if (mb.is_valid()) { if (mb->get_button_index() == MouseButton::LEFT) { if (mb->is_pressed()) { - Ref<Texture2D> grabber = get_theme_icon(mouse_inside || has_focus() ? "grabber_highlight" : "grabber"); + Ref<Texture2D> grabber; + if (mouse_inside || has_focus()) { + grabber = theme_cache.grabber_hl_icon; + } else { + grabber = theme_cache.grabber_icon; + } + grab.pos = orientation == VERTICAL ? mb->get_position().y : mb->get_position().x; double grab_width = (double)grabber->get_size().width; @@ -95,7 +98,7 @@ void Slider::gui_input(const Ref<InputEvent> &p_event) { if (mm.is_valid()) { if (grab.active) { Size2i size = get_size(); - Ref<Texture2D> grabber = get_theme_icon(SNAME("grabber")); + Ref<Texture2D> grabber = theme_cache.grabber_icon; double motion = (orientation == VERTICAL ? mm->get_position().y : mm->get_position().x) - grab.pos; if (orientation == VERTICAL) { motion = -motion; @@ -145,21 +148,34 @@ void Slider::gui_input(const Ref<InputEvent> &p_event) { } } +void Slider::_update_theme_item_cache() { + Range::_update_theme_item_cache(); + + theme_cache.slider_style = get_theme_stylebox(SNAME("slider")); + theme_cache.grabber_area_style = get_theme_stylebox(SNAME("grabber_area")); + theme_cache.grabber_area_hl_style = get_theme_stylebox(SNAME("grabber_area_highlight")); + + theme_cache.grabber_icon = get_theme_icon(SNAME("grabber")); + theme_cache.grabber_hl_icon = get_theme_icon(SNAME("grabber_highlight")); + theme_cache.grabber_disabled_icon = get_theme_icon(SNAME("grabber_disabled")); + theme_cache.tick_icon = get_theme_icon(SNAME("tick")); +} + void Slider::_notification(int p_what) { switch (p_what) { case NOTIFICATION_THEME_CHANGED: { update_minimum_size(); - update(); + queue_redraw(); } break; case NOTIFICATION_MOUSE_ENTER: { mouse_inside = true; - update(); + queue_redraw(); } break; case NOTIFICATION_MOUSE_EXIT: { mouse_inside = false; - update(); + queue_redraw(); } break; case NOTIFICATION_VISIBILITY_CHANGED: @@ -171,13 +187,30 @@ void Slider::_notification(int p_what) { case NOTIFICATION_DRAW: { RID ci = get_canvas_item(); Size2i size = get_size(); - Ref<StyleBox> style = get_theme_stylebox(SNAME("slider")); - bool highlighted = mouse_inside || has_focus(); - Ref<StyleBox> grabber_area = get_theme_stylebox(highlighted ? "grabber_area_highlight" : "grabber_area"); - Ref<Texture2D> grabber = get_theme_icon(editable ? (highlighted ? "grabber_highlight" : "grabber") : "grabber_disabled"); - Ref<Texture2D> tick = get_theme_icon(SNAME("tick")); double ratio = Math::is_nan(get_as_ratio()) ? 0 : get_as_ratio(); + Ref<StyleBox> style = theme_cache.slider_style; + Ref<Texture2D> tick = theme_cache.tick_icon; + + bool highlighted = mouse_inside || has_focus(); + Ref<Texture2D> grabber; + if (editable) { + if (highlighted) { + grabber = theme_cache.grabber_hl_icon; + } else { + grabber = theme_cache.grabber_icon; + } + } else { + grabber = theme_cache.grabber_disabled_icon; + } + + Ref<StyleBox> grabber_area; + if (highlighted) { + grabber_area = theme_cache.grabber_area_hl_style; + } else { + grabber_area = theme_cache.grabber_area_style; + } + if (orientation == VERTICAL) { int widget_width = style->get_minimum_size().width + style->get_center_size().width; double areasize = size.height - grabber->get_size().height; @@ -227,8 +260,12 @@ double Slider::get_custom_step() const { } void Slider::set_ticks(int p_count) { + if (ticks == p_count) { + return; + } + ticks = p_count; - update(); + queue_redraw(); } int Slider::get_ticks() const { @@ -240,13 +277,21 @@ bool Slider::get_ticks_on_borders() const { } void Slider::set_ticks_on_borders(bool _tob) { + if (ticks_on_borders == _tob) { + return; + } + ticks_on_borders = _tob; - update(); + queue_redraw(); } void Slider::set_editable(bool p_editable) { + if (editable == p_editable) { + return; + } + editable = p_editable; - update(); + queue_redraw(); } bool Slider::is_editable() const { diff --git a/scene/gui/slider.h b/scene/gui/slider.h index 5abaee27aa..51adb354fb 100644 --- a/scene/gui/slider.h +++ b/scene/gui/slider.h @@ -49,11 +49,24 @@ class Slider : public Range { bool editable = true; bool scrollable = true; + struct ThemeCache { + Ref<StyleBox> slider_style; + Ref<StyleBox> grabber_area_style; + Ref<StyleBox> grabber_area_hl_style; + + Ref<Texture2D> grabber_icon; + Ref<Texture2D> grabber_hl_icon; + Ref<Texture2D> grabber_disabled_icon; + Ref<Texture2D> tick_icon; + } theme_cache; + protected: + bool ticks_on_borders = false; + virtual void gui_input(const Ref<InputEvent> &p_event) override; + virtual void _update_theme_item_cache() override; void _notification(int p_what); static void _bind_methods(); - bool ticks_on_borders = false; public: virtual Size2 get_minimum_size() const override; diff --git a/scene/gui/spin_box.cpp b/scene/gui/spin_box.cpp index 8a7f52b0d9..fe14049d93 100644 --- a/scene/gui/spin_box.cpp +++ b/scene/gui/spin_box.cpp @@ -41,12 +41,16 @@ Size2 SpinBox::get_minimum_size() const { void SpinBox::_value_changed(double p_value) { String value = TS->format_number(String::num(get_value(), Math::range_step_decimals(get_step()))); - if (!prefix.is_empty()) { - value = prefix + " " + value; - } - if (!suffix.is_empty()) { - value += " " + suffix; + + if (!line_edit->has_focus()) { + if (!prefix.is_empty()) { + value = prefix + " " + value; + } + if (!suffix.is_empty()) { + value += " " + suffix; + } } + line_edit->set_text(value); Range::_value_changed(p_value); } @@ -105,8 +109,9 @@ void SpinBox::_range_click_timeout() { void SpinBox::_release_mouse() { if (drag.enabled) { drag.enabled = false; - Input::get_singleton()->set_mouse_mode(Input::MOUSE_MODE_VISIBLE); + Input::get_singleton()->set_mouse_mode(Input::MOUSE_MODE_HIDDEN); warp_mouse(drag.capture_pos); + Input::get_singleton()->set_mouse_mode(Input::MOUSE_MODE_VISIBLE); } } @@ -181,8 +186,14 @@ void SpinBox::gui_input(const Ref<InputEvent> &p_event) { } } +void SpinBox::_line_edit_focus_enter() { + int col = line_edit->get_caret_column(); + _value_changed(0); // Update the LineEdit's text. + line_edit->set_caret_column(col); +} + void SpinBox::_line_edit_focus_exit() { - // discontinue because the focus_exit was caused by right-click context menu + // Discontinue because the focus_exit was caused by right-click context menu. if (line_edit->is_menu_visible()) { return; } @@ -199,25 +210,29 @@ inline void SpinBox::_adjust_width_for_icon(const Ref<Texture2D> &icon) { } } +void SpinBox::_update_theme_item_cache() { + Range::_update_theme_item_cache(); + + theme_cache.updown_icon = get_theme_icon(SNAME("updown")); +} + void SpinBox::_notification(int p_what) { switch (p_what) { case NOTIFICATION_DRAW: { - Ref<Texture2D> updown = get_theme_icon(SNAME("updown")); - - _adjust_width_for_icon(updown); + _adjust_width_for_icon(theme_cache.updown_icon); RID ci = get_canvas_item(); Size2i size = get_size(); if (is_layout_rtl()) { - updown->draw(ci, Point2i(0, (size.height - updown->get_height()) / 2)); + theme_cache.updown_icon->draw(ci, Point2i(0, (size.height - theme_cache.updown_icon->get_height()) / 2)); } else { - updown->draw(ci, Point2i(size.width - updown->get_width(), (size.height - updown->get_height()) / 2)); + theme_cache.updown_icon->draw(ci, Point2i(size.width - theme_cache.updown_icon->get_width(), (size.height - theme_cache.updown_icon->get_height()) / 2)); } } break; case NOTIFICATION_ENTER_TREE: { - _adjust_width_for_icon(get_theme_icon(SNAME("updown"))); + _adjust_width_for_icon(theme_cache.updown_icon); _value_changed(0); } break; @@ -227,7 +242,7 @@ void SpinBox::_notification(int p_what) { case NOTIFICATION_TRANSLATION_CHANGED: { _value_changed(0); - update(); + queue_redraw(); } break; case NOTIFICATION_THEME_CHANGED: { @@ -236,7 +251,7 @@ void SpinBox::_notification(int p_what) { } break; case NOTIFICATION_LAYOUT_DIRECTION_CHANGED: { - update(); + queue_redraw(); } break; } } @@ -250,6 +265,10 @@ HorizontalAlignment SpinBox::get_horizontal_alignment() const { } void SpinBox::set_suffix(const String &p_suffix) { + if (suffix == p_suffix) { + return; + } + suffix = p_suffix; _value_changed(0); } @@ -259,6 +278,10 @@ String SpinBox::get_suffix() const { } void SpinBox::set_prefix(const String &p_prefix) { + if (prefix == p_prefix) { + return; + } + prefix = p_prefix; _value_changed(0); } @@ -338,6 +361,7 @@ SpinBox::SpinBox() { line_edit->set_horizontal_alignment(HORIZONTAL_ALIGNMENT_LEFT); line_edit->connect("text_submitted", callable_mp(this, &SpinBox::_text_submitted), CONNECT_DEFERRED); + line_edit->connect("focus_entered", callable_mp(this, &SpinBox::_line_edit_focus_enter), CONNECT_DEFERRED); line_edit->connect("focus_exited", callable_mp(this, &SpinBox::_line_edit_focus_exit), CONNECT_DEFERRED); line_edit->connect("gui_input", callable_mp(this, &SpinBox::_line_edit_input)); diff --git a/scene/gui/spin_box.h b/scene/gui/spin_box.h index 0aae9efe78..c2f2ac3f5a 100644 --- a/scene/gui/spin_box.h +++ b/scene/gui/spin_box.h @@ -64,13 +64,19 @@ class SpinBox : public Range { double diff_y = 0.0; } drag; + void _line_edit_focus_enter(); void _line_edit_focus_exit(); inline void _adjust_width_for_icon(const Ref<Texture2D> &icon); + struct ThemeCache { + Ref<Texture2D> updown_icon; + } theme_cache; + protected: virtual void gui_input(const Ref<InputEvent> &p_event) override; + virtual void _update_theme_item_cache() override; void _notification(int p_what); static void _bind_methods(); diff --git a/scene/gui/split_container.cpp b/scene/gui/split_container.cpp index d7aa516ee6..2ca1d6239e 100644 --- a/scene/gui/split_container.cpp +++ b/scene/gui/split_container.cpp @@ -33,11 +33,99 @@ #include "label.h" #include "margin_container.h" +void SplitContainerDragger::gui_input(const Ref<InputEvent> &p_event) { + ERR_FAIL_COND(p_event.is_null()); + + SplitContainer *sc = Object::cast_to<SplitContainer>(get_parent()); + + if (sc->collapsed || !sc->_getch(0) || !sc->_getch(1) || sc->dragger_visibility != SplitContainer::DRAGGER_VISIBLE) { + return; + } + + Ref<InputEventMouseButton> mb = p_event; + + if (mb.is_valid()) { + if (mb->get_button_index() == MouseButton::LEFT) { + if (mb->is_pressed()) { + sc->_compute_middle_sep(true); + dragging = true; + drag_ofs = sc->split_offset; + if (sc->vertical) { + drag_from = get_transform().xform(mb->get_position()).y; + } else { + drag_from = get_transform().xform(mb->get_position()).x; + } + } else { + dragging = false; + queue_redraw(); + } + } + } + + Ref<InputEventMouseMotion> mm = p_event; + + if (mm.is_valid()) { + if (!dragging) { + return; + } + + Vector2i in_parent_pos = get_transform().xform(mm->get_position()); + if (!sc->vertical && is_layout_rtl()) { + sc->split_offset = drag_ofs - ((sc->vertical ? in_parent_pos.y : in_parent_pos.x) - drag_from); + } else { + sc->split_offset = drag_ofs + ((sc->vertical ? in_parent_pos.y : in_parent_pos.x) - drag_from); + } + sc->_compute_middle_sep(true); + sc->queue_sort(); + sc->emit_signal(SNAME("dragged"), sc->get_split_offset()); + } +} + +Control::CursorShape SplitContainerDragger::get_cursor_shape(const Point2 &p_pos) const { + SplitContainer *sc = Object::cast_to<SplitContainer>(get_parent()); + + if (!sc->collapsed && sc->dragger_visibility == SplitContainer::DRAGGER_VISIBLE) { + return (sc->vertical ? CURSOR_VSPLIT : CURSOR_HSPLIT); + } + + return Control::get_cursor_shape(p_pos); +} + +void SplitContainerDragger::_notification(int p_what) { + switch (p_what) { + case NOTIFICATION_MOUSE_ENTER: { + mouse_inside = true; + SplitContainer *sc = Object::cast_to<SplitContainer>(get_parent()); + if (sc->get_theme_constant(SNAME("autohide"))) { + queue_redraw(); + } + } break; + + case NOTIFICATION_MOUSE_EXIT: { + mouse_inside = false; + SplitContainer *sc = Object::cast_to<SplitContainer>(get_parent()); + if (sc->get_theme_constant(SNAME("autohide"))) { + queue_redraw(); + } + } break; + + case NOTIFICATION_DRAW: { + SplitContainer *sc = Object::cast_to<SplitContainer>(get_parent()); + if (!dragging && !mouse_inside && sc->get_theme_constant(SNAME("autohide"))) { + return; + } + + Ref<Texture2D> tex = sc->get_theme_icon(SNAME("grabber")); + draw_texture(tex, (get_size() - tex->get_size()) / 2); + } break; + } +} + Control *SplitContainer::_getch(int p_idx) const { int idx = 0; - for (int i = 0; i < get_child_count(); i++) { - Control *c = Object::cast_to<Control>(get_child(i)); + for (int i = 0; i < get_child_count(false); i++) { + Control *c = Object::cast_to<Control>(get_child(i, false)); if (!c || !c->is_visible()) { continue; } @@ -55,58 +143,84 @@ Control *SplitContainer::_getch(int p_idx) const { return nullptr; } -void SplitContainer::_resort() { - int axis = vertical ? 1 : 0; +Ref<Texture2D> SplitContainer::_get_grabber_icon() const { + if (is_fixed) { + return theme_cache.grabber_icon; + } else { + if (vertical) { + return theme_cache.grabber_icon_v; + } else { + return theme_cache.grabber_icon_h; + } + } +} +void SplitContainer::_compute_middle_sep(bool p_clamp) { Control *first = _getch(0); Control *second = _getch(1); - // If we have only one element - if (!first || !second) { - if (first) { - fit_child_in_rect(first, Rect2(Point2(), get_size())); - } else if (second) { - fit_child_in_rect(second, Rect2(Point2(), get_size())); - } - return; - } - - // Determine expanded children + // Determine expanded children. bool first_expanded = (vertical ? first->get_v_size_flags() : first->get_h_size_flags()) & SIZE_EXPAND; bool second_expanded = (vertical ? second->get_v_size_flags() : second->get_h_size_flags()) & SIZE_EXPAND; - // Determine the separation between items - Ref<Texture2D> g = get_theme_icon(SNAME("grabber")); - int sep = get_theme_constant(SNAME("separation")); - sep = (dragger_visibility != DRAGGER_HIDDEN_COLLAPSED) ? MAX(sep, vertical ? g->get_height() : g->get_width()) : 0; + // Compute the minimum size. + int axis = vertical ? 1 : 0; + int size = get_size()[axis]; + int ms_first = first->get_combined_minimum_size()[axis]; + int ms_second = second->get_combined_minimum_size()[axis]; - // Compute the minimum size - Size2 ms_first = first->get_combined_minimum_size(); - Size2 ms_second = second->get_combined_minimum_size(); + // Determine the separation between items. + Ref<Texture2D> g = _get_grabber_icon(); + int sep = (dragger_visibility != DRAGGER_HIDDEN_COLLAPSED) ? MAX(theme_cache.separation, vertical ? g->get_height() : g->get_width()) : 0; - // Compute the separator position without the split offset - float ratio = first->get_stretch_ratio() / (first->get_stretch_ratio() + second->get_stretch_ratio()); - int no_offset_middle_sep = 0; + // Compute the wished separation_point. + int wished_middle_sep = 0; + int split_offset_with_collapse = 0; + if (!collapsed) { + split_offset_with_collapse = split_offset; + } if (first_expanded && second_expanded) { - no_offset_middle_sep = get_size()[axis] * ratio - sep / 2; + float ratio = first->get_stretch_ratio() / (first->get_stretch_ratio() + second->get_stretch_ratio()); + wished_middle_sep = size * ratio - sep / 2 + split_offset_with_collapse; } else if (first_expanded) { - no_offset_middle_sep = get_size()[axis] - ms_second[axis] - sep; + wished_middle_sep = size - sep + split_offset_with_collapse; } else { - no_offset_middle_sep = ms_first[axis]; + wished_middle_sep = split_offset_with_collapse; } - // Compute the final middle separation - middle_sep = no_offset_middle_sep; - if (!collapsed) { - int clamped_split_offset = CLAMP(split_offset, ms_first[axis] - no_offset_middle_sep, (get_size()[axis] - ms_second[axis] - sep) - no_offset_middle_sep); - middle_sep += clamped_split_offset; - if (should_clamp_split_offset) { - split_offset = clamped_split_offset; + // Clamp the middle sep to acceptatble values. + middle_sep = CLAMP(wished_middle_sep, ms_first, size - sep - ms_second); + + // Clamp the split_offset if requested. + if (p_clamp) { + split_offset -= wished_middle_sep - middle_sep; + p_clamp = false; + } +} + +void SplitContainer::_resort() { + Control *first = _getch(0); + Control *second = _getch(1); - should_clamp_split_offset = false; + // If we have only one element. + if (!first || !second) { + if (first) { + fit_child_in_rect(first, Rect2(Point2(), get_size())); + } else if (second) { + fit_child_in_rect(second, Rect2(Point2(), get_size())); } + dragging_area_control->hide(); + return; } + // If we have more that one. + _compute_middle_sep(false); + + // Determine the separation between items. + Ref<Texture2D> g = _get_grabber_icon(); + int sep = (dragger_visibility != DRAGGER_HIDDEN_COLLAPSED) ? MAX(theme_cache.separation, vertical ? g->get_height() : g->get_width()) : 0; + + // Move the children, including the dragger. if (vertical) { fit_child_in_rect(first, Rect2(Point2(0, 0), Size2(get_size().width, middle_sep))); int sofs = middle_sep + sep; @@ -124,16 +238,27 @@ void SplitContainer::_resort() { } } - update(); + // Handle the dragger visibility and position. + if (dragger_visibility == DRAGGER_VISIBLE && !collapsed) { + dragging_area_control->show(); + + int dragger_ctrl_size = MAX(sep, theme_cache.minimum_grab_thickness); + if (vertical) { + dragging_area_control->set_rect(Rect2(Point2(0, middle_sep - (dragger_ctrl_size - sep) / 2), Size2(get_size().width, dragger_ctrl_size))); + } else { + dragging_area_control->set_rect(Rect2(Point2(middle_sep - (dragger_ctrl_size - sep) / 2, 0), Size2(dragger_ctrl_size, get_size().height))); + } + + dragging_area_control->queue_redraw(); + } else { + dragging_area_control->hide(); + } } Size2 SplitContainer::get_minimum_size() const { - /* Calculate MINIMUM SIZE */ - Size2i minimum; - Ref<Texture2D> g = get_theme_icon(SNAME("grabber")); - int sep = get_theme_constant(SNAME("separation")); - sep = (dragger_visibility != DRAGGER_HIDDEN_COLLAPSED) ? MAX(sep, vertical ? g->get_height() : g->get_width()) : 0; + Ref<Texture2D> g = _get_grabber_icon(); + int sep = (dragger_visibility != DRAGGER_HIDDEN_COLLAPSED) ? MAX(theme_cache.separation, vertical ? g->get_height() : g->get_width()) : 0; for (int i = 0; i < 2; i++) { if (!_getch(i)) { @@ -162,6 +287,23 @@ Size2 SplitContainer::get_minimum_size() const { return minimum; } +void SplitContainer::_validate_property(PropertyInfo &p_property) const { + if (is_fixed && p_property.name == "vertical") { + p_property.usage = PROPERTY_USAGE_NONE; + } +} + +void SplitContainer::_update_theme_item_cache() { + Container::_update_theme_item_cache(); + + theme_cache.separation = get_theme_constant(SNAME("separation")); + theme_cache.minimum_grab_thickness = get_theme_constant(SNAME("minimum_grab_thickness")); + theme_cache.autohide = get_theme_constant(SNAME("autohide")); + theme_cache.grabber_icon = get_theme_icon(SNAME("grabber")); + theme_cache.grabber_icon_h = get_theme_icon(SNAME("h_grabber")); + theme_cache.grabber_icon_v = get_theme_icon(SNAME("v_grabber")); +} + void SplitContainer::_notification(int p_what) { switch (p_what) { case NOTIFICATION_TRANSLATION_CHANGED: @@ -173,130 +315,12 @@ void SplitContainer::_notification(int p_what) { _resort(); } break; - case NOTIFICATION_MOUSE_EXIT: { - mouse_inside = false; - if (get_theme_constant(SNAME("autohide"))) { - update(); - } - } break; - - case NOTIFICATION_DRAW: { - if (!_getch(0) || !_getch(1)) { - return; - } - - if (collapsed || (!dragging && !mouse_inside && get_theme_constant(SNAME("autohide")))) { - return; - } - - if (dragger_visibility != DRAGGER_VISIBLE) { - return; - } - - int sep = dragger_visibility != DRAGGER_HIDDEN_COLLAPSED ? get_theme_constant(SNAME("separation")) : 0; - Ref<Texture2D> tex = get_theme_icon(SNAME("grabber")); - Size2 size = get_size(); - - if (vertical) { - draw_texture(tex, Point2i((size.x - tex->get_width()) / 2, middle_sep + (sep - tex->get_height()) / 2)); - } else { - draw_texture(tex, Point2i(middle_sep + (sep - tex->get_width()) / 2, (size.y - tex->get_height()) / 2)); - } - } break; - case NOTIFICATION_THEME_CHANGED: { update_minimum_size(); } break; } } -void SplitContainer::gui_input(const Ref<InputEvent> &p_event) { - ERR_FAIL_COND(p_event.is_null()); - - if (collapsed || !_getch(0) || !_getch(1) || dragger_visibility != DRAGGER_VISIBLE) { - return; - } - - Ref<InputEventMouseButton> mb = p_event; - - if (mb.is_valid()) { - if (mb->get_button_index() == MouseButton::LEFT) { - if (mb->is_pressed()) { - int sep = get_theme_constant(SNAME("separation")); - - if (vertical) { - if (mb->get_position().y > middle_sep && mb->get_position().y < middle_sep + sep) { - dragging = true; - drag_from = mb->get_position().y; - drag_ofs = split_offset; - } - } else { - if (mb->get_position().x > middle_sep && mb->get_position().x < middle_sep + sep) { - dragging = true; - drag_from = mb->get_position().x; - drag_ofs = split_offset; - } - } - } else { - dragging = false; - } - } - } - - Ref<InputEventMouseMotion> mm = p_event; - - if (mm.is_valid()) { - bool mouse_inside_state = false; - if (vertical) { - mouse_inside_state = mm->get_position().y > middle_sep && mm->get_position().y < middle_sep + get_theme_constant(SNAME("separation")); - } else { - mouse_inside_state = mm->get_position().x > middle_sep && mm->get_position().x < middle_sep + get_theme_constant(SNAME("separation")); - } - - if (mouse_inside != mouse_inside_state) { - mouse_inside = mouse_inside_state; - if (get_theme_constant(SNAME("autohide"))) { - update(); - } - } - - if (!dragging) { - return; - } - - if (!vertical && is_layout_rtl()) { - split_offset = drag_ofs + (drag_from - (vertical ? mm->get_position().y : mm->get_position().x)); - } else { - split_offset = drag_ofs + ((vertical ? mm->get_position().y : mm->get_position().x) - drag_from); - } - should_clamp_split_offset = true; - queue_sort(); - emit_signal(SNAME("dragged"), get_split_offset()); - } -} - -Control::CursorShape SplitContainer::get_cursor_shape(const Point2 &p_pos) const { - if (dragging) { - return (vertical ? CURSOR_VSPLIT : CURSOR_HSPLIT); - } - - if (!collapsed && _getch(0) && _getch(1) && dragger_visibility == DRAGGER_VISIBLE) { - int sep = get_theme_constant(SNAME("separation")); - - if (vertical) { - if (p_pos.y > middle_sep && p_pos.y < middle_sep + sep) { - return CURSOR_VSPLIT; - } - } else { - if (p_pos.x > middle_sep && p_pos.x < middle_sep + sep) { - return CURSOR_HSPLIT; - } - } - } - - return Control::get_cursor_shape(p_pos); -} - void SplitContainer::set_split_offset(int p_offset) { if (split_offset == p_offset) { return; @@ -312,8 +336,11 @@ int SplitContainer::get_split_offset() const { } void SplitContainer::clamp_split_offset() { - should_clamp_split_offset = true; + if (!_getch(0) || !_getch(1)) { + return; + } + _compute_middle_sep(true); queue_sort(); } @@ -327,9 +354,12 @@ void SplitContainer::set_collapsed(bool p_collapsed) { } void SplitContainer::set_dragger_visibility(DraggerVisibility p_visibility) { + if (dragger_visibility == p_visibility) { + return; + } + dragger_visibility = p_visibility; queue_sort(); - update(); } SplitContainer::DraggerVisibility SplitContainer::get_dragger_visibility() const { @@ -340,6 +370,17 @@ bool SplitContainer::is_collapsed() const { return collapsed; } +void SplitContainer::set_vertical(bool p_vertical) { + ERR_FAIL_COND_MSG(is_fixed, "Can't change orientation of " + get_class() + "."); + vertical = p_vertical; + update_minimum_size(); + _resort(); +} + +bool SplitContainer::is_vertical() const { + return vertical; +} + Vector<int> SplitContainer::get_allowed_size_flags_horizontal() const { Vector<int> flags; flags.append(SIZE_FILL); @@ -375,11 +416,15 @@ void SplitContainer::_bind_methods() { ClassDB::bind_method(D_METHOD("set_dragger_visibility", "mode"), &SplitContainer::set_dragger_visibility); ClassDB::bind_method(D_METHOD("get_dragger_visibility"), &SplitContainer::get_dragger_visibility); + ClassDB::bind_method(D_METHOD("set_vertical", "vertical"), &SplitContainer::set_vertical); + ClassDB::bind_method(D_METHOD("is_vertical"), &SplitContainer::is_vertical); + ADD_SIGNAL(MethodInfo("dragged", PropertyInfo(Variant::INT, "offset"))); ADD_PROPERTY(PropertyInfo(Variant::INT, "split_offset", PROPERTY_HINT_NONE, "suffix:px"), "set_split_offset", "get_split_offset"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "collapsed"), "set_collapsed", "is_collapsed"); ADD_PROPERTY(PropertyInfo(Variant::INT, "dragger_visibility", PROPERTY_HINT_ENUM, "Visible,Hidden,Hidden and Collapsed"), "set_dragger_visibility", "get_dragger_visibility"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "vertical"), "set_vertical", "is_vertical"); BIND_ENUM_CONSTANT(DRAGGER_VISIBLE); BIND_ENUM_CONSTANT(DRAGGER_HIDDEN); @@ -388,4 +433,7 @@ void SplitContainer::_bind_methods() { SplitContainer::SplitContainer(bool p_vertical) { vertical = p_vertical; + + dragging_area_control = memnew(SplitContainerDragger); + add_child(dragging_area_control, false, Node::INTERNAL_MODE_BACK); } diff --git a/scene/gui/split_container.h b/scene/gui/split_container.h index a69ffe4de9..d297e3a3ea 100644 --- a/scene/gui/split_container.h +++ b/scene/gui/split_container.h @@ -33,8 +33,26 @@ #include "scene/gui/container.h" +class SplitContainerDragger : public Control { + GDCLASS(SplitContainerDragger, Control); + +protected: + void _notification(int p_what); + virtual void gui_input(const Ref<InputEvent> &p_event) override; + +private: + bool dragging = false; + int drag_from = 0; + int drag_ofs = 0; + bool mouse_inside = false; + +public: + virtual CursorShape get_cursor_shape(const Point2 &p_pos = Point2i()) const override; +}; + class SplitContainer : public Container { GDCLASS(SplitContainer, Container); + friend class SplitContainerDragger; public: enum DraggerVisibility { @@ -44,24 +62,38 @@ public: }; private: - bool should_clamp_split_offset = false; int split_offset = 0; int middle_sep = 0; bool vertical = false; - bool dragging = false; - int drag_from = 0; - int drag_ofs = 0; bool collapsed = false; DraggerVisibility dragger_visibility = DRAGGER_VISIBLE; - bool mouse_inside = false; + + SplitContainerDragger *dragging_area_control = nullptr; + + struct ThemeCache { + int separation = 0; + int minimum_grab_thickness = 0; + int autohide = 0; + Ref<Texture2D> grabber_icon; + Ref<Texture2D> grabber_icon_h; + Ref<Texture2D> grabber_icon_v; + } theme_cache; Control *_getch(int p_idx) const; + Ref<Texture2D> _get_grabber_icon() const; + void _compute_middle_sep(bool p_clamp); void _resort(); + void _dragging_area_gui_input(const Ref<InputEvent> &p_event); + protected: - virtual void gui_input(const Ref<InputEvent> &p_event) override; + bool is_fixed = false; + + virtual void _update_theme_item_cache() override; + void _notification(int p_what); + void _validate_property(PropertyInfo &p_property) const; static void _bind_methods(); public: @@ -75,7 +107,8 @@ public: void set_dragger_visibility(DraggerVisibility p_visibility); DraggerVisibility get_dragger_visibility() const; - virtual CursorShape get_cursor_shape(const Point2 &p_pos = Point2i()) const override; + void set_vertical(bool p_vertical); + bool is_vertical() const; virtual Size2 get_minimum_size() const override; @@ -92,7 +125,7 @@ class HSplitContainer : public SplitContainer { public: HSplitContainer() : - SplitContainer(false) {} + SplitContainer(false) { is_fixed = true; } }; class VSplitContainer : public SplitContainer { @@ -100,7 +133,7 @@ class VSplitContainer : public SplitContainer { public: VSplitContainer() : - SplitContainer(true) {} + SplitContainer(true) { is_fixed = true; } }; #endif // SPLIT_CONTAINER_H diff --git a/scene/gui/subviewport_container.cpp b/scene/gui/subviewport_container.cpp index 68281b6a72..88e68ec763 100644 --- a/scene/gui/subviewport_container.cpp +++ b/scene/gui/subviewport_container.cpp @@ -53,10 +53,14 @@ Size2 SubViewportContainer::get_minimum_size() const { } void SubViewportContainer::set_stretch(bool p_enable) { + if (stretch == p_enable) { + return; + } + stretch = p_enable; update_minimum_size(); queue_sort(); - update(); + queue_redraw(); } bool SubViewportContainer::is_stretch_enabled() const { @@ -84,7 +88,7 @@ void SubViewportContainer::set_stretch_shrink(int p_shrink) { c->set_size(get_size() / shrink); } - update(); + queue_redraw(); } int SubViewportContainer::get_stretch_shrink() const { diff --git a/scene/gui/tab_bar.cpp b/scene/gui/tab_bar.cpp index d36a364677..cf6681f809 100644 --- a/scene/gui/tab_bar.cpp +++ b/scene/gui/tab_bar.cpp @@ -44,14 +44,7 @@ Size2 TabBar::get_minimum_size() const { 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("h_separation")); - - int y_margin = MAX(MAX(tab_unselected->get_minimum_size().height, tab_selected->get_minimum_size().height), tab_disabled->get_minimum_size().height); + int y_margin = MAX(MAX(theme_cache.tab_unselected_style->get_minimum_size().height, theme_cache.tab_selected_style->get_minimum_size().height), theme_cache.tab_disabled_style->get_minimum_size().height); for (int i = 0; i < tabs.size(); i++) { if (tabs[i].hidden) { @@ -62,22 +55,22 @@ Size2 TabBar::get_minimum_size() const { Ref<StyleBox> style; if (tabs[i].disabled) { - style = tab_disabled; + style = theme_cache.tab_disabled_style; } else if (current == i) { - style = tab_selected; + style = theme_cache.tab_selected_style; } else { - style = tab_unselected; + style = theme_cache.tab_unselected_style; } 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 + y_margin); - ms.width += tex->get_size().width + hseparation; + ms.width += tex->get_size().width + theme_cache.h_separation; } if (!tabs[i].text.is_empty()) { - ms.width += tabs[i].size_text + hseparation; + ms.width += tabs[i].size_text + theme_cache.h_separation; } ms.height = MAX(ms.height, tabs[i].text_buf->get_size().y + y_margin); @@ -87,22 +80,22 @@ Size2 TabBar::get_minimum_size() const { Ref<Texture2D> rb = tabs[i].right_button; if (close_visible) { - ms.width += button_highlight->get_minimum_size().width + rb->get_width(); + ms.width += theme_cache.button_hl_style->get_minimum_size().width + rb->get_width(); } else { - ms.width += button_highlight->get_margin(SIDE_LEFT) + rb->get_width() + hseparation; + ms.width += theme_cache.button_hl_style->get_margin(SIDE_LEFT) + rb->get_width() + theme_cache.h_separation; } ms.height = MAX(ms.height, rb->get_height() + y_margin); } if (close_visible) { - ms.width += button_highlight->get_margin(SIDE_LEFT) + close->get_width() + hseparation; + ms.width += theme_cache.button_hl_style->get_margin(SIDE_LEFT) + theme_cache.close_icon->get_width() + theme_cache.h_separation; - ms.height = MAX(ms.height, close->get_height() + y_margin); + ms.height = MAX(ms.height, theme_cache.close_icon->get_height() + y_margin); } if (ms.width - ofs > style->get_minimum_size().width) { - ms.width -= hseparation; + ms.width -= theme_cache.h_separation; } } @@ -122,46 +115,43 @@ void TabBar::gui_input(const Ref<InputEvent> &p_event) { Point2 pos = mm->get_position(); if (buttons_visible) { - Ref<Texture2D> incr = get_theme_icon(SNAME("increment")); - Ref<Texture2D> decr = get_theme_icon(SNAME("decrement")); - if (is_layout_rtl()) { - if (pos.x < decr->get_width()) { + if (pos.x < theme_cache.decrement_icon->get_width()) { if (highlight_arrow != 1) { highlight_arrow = 1; - update(); + queue_redraw(); } - } else if (pos.x < incr->get_width() + decr->get_width()) { + } else if (pos.x < theme_cache.increment_icon->get_width() + theme_cache.decrement_icon->get_width()) { if (highlight_arrow != 0) { highlight_arrow = 0; - update(); + queue_redraw(); } } else if (highlight_arrow != -1) { highlight_arrow = -1; - update(); + queue_redraw(); } } else { - int limit_minus_buttons = get_size().width - incr->get_width() - decr->get_width(); - if (pos.x > limit_minus_buttons + decr->get_width()) { + int limit_minus_buttons = get_size().width - theme_cache.increment_icon->get_width() - theme_cache.decrement_icon->get_width(); + if (pos.x > limit_minus_buttons + theme_cache.decrement_icon->get_width()) { if (highlight_arrow != 1) { highlight_arrow = 1; - update(); + queue_redraw(); } } else if (pos.x > limit_minus_buttons) { if (highlight_arrow != 0) { highlight_arrow = 0; - update(); + queue_redraw(); } } else if (highlight_arrow != -1) { highlight_arrow = -1; - update(); + queue_redraw(); } } } if (get_viewport()->gui_is_dragging() && can_drop_data(pos, get_viewport()->gui_get_drag_data())) { dragging_valid_tab = true; - update(); + queue_redraw(); } _update_hover(); @@ -172,22 +162,22 @@ void TabBar::gui_input(const Ref<InputEvent> &p_event) { Ref<InputEventMouseButton> mb = p_event; if (mb.is_valid()) { - if (mb->is_pressed() && mb->get_button_index() == MouseButton::WHEEL_UP && !mb->is_command_pressed()) { + if (mb->is_pressed() && mb->get_button_index() == MouseButton::WHEEL_UP && !mb->is_command_or_control_pressed()) { if (scrolling_enabled && buttons_visible) { if (offset > 0) { offset--; _update_cache(); - update(); + queue_redraw(); } } } - if (mb->is_pressed() && mb->get_button_index() == MouseButton::WHEEL_DOWN && !mb->is_command_pressed()) { + if (mb->is_pressed() && mb->get_button_index() == MouseButton::WHEEL_DOWN && !mb->is_command_or_control_pressed()) { if (scrolling_enabled && buttons_visible) { if (missing_right && offset < tabs.size()) { offset++; _update_cache(); - update(); + queue_redraw(); } } } @@ -198,7 +188,7 @@ void TabBar::gui_input(const Ref<InputEvent> &p_event) { } rb_pressing = false; - update(); + queue_redraw(); } if (cb_pressing && !mb->is_pressed() && mb->get_button_index() == MouseButton::LEFT) { @@ -207,46 +197,43 @@ void TabBar::gui_input(const Ref<InputEvent> &p_event) { } cb_pressing = false; - update(); + queue_redraw(); } if (mb->is_pressed() && (mb->get_button_index() == MouseButton::LEFT || (select_with_rmb && mb->get_button_index() == MouseButton::RIGHT))) { Point2 pos = mb->get_position(); if (buttons_visible) { - Ref<Texture2D> incr = get_theme_icon(SNAME("increment")); - Ref<Texture2D> decr = get_theme_icon(SNAME("decrement")); - if (is_layout_rtl()) { - if (pos.x < decr->get_width()) { + if (pos.x < theme_cache.decrement_icon->get_width()) { if (missing_right) { offset++; _update_cache(); - update(); + queue_redraw(); } return; - } else if (pos.x < incr->get_width() + decr->get_width()) { + } else if (pos.x < theme_cache.increment_icon->get_width() + theme_cache.decrement_icon->get_width()) { if (offset > 0) { offset--; _update_cache(); - update(); + queue_redraw(); } return; } } else { - int limit = get_size().width - incr->get_width() - decr->get_width(); - if (pos.x > limit + decr->get_width()) { + int limit = get_size().width - theme_cache.increment_icon->get_width() - theme_cache.decrement_icon->get_width(); + if (pos.x > limit + theme_cache.decrement_icon->get_width()) { if (missing_right) { offset++; _update_cache(); - update(); + queue_redraw(); } return; } else if (pos.x > limit) { if (offset > 0) { offset--; _update_cache(); - update(); + queue_redraw(); } return; } @@ -266,13 +253,13 @@ void TabBar::gui_input(const Ref<InputEvent> &p_event) { if (tabs[i].rb_rect.has_point(pos)) { rb_pressing = true; - update(); + queue_redraw(); return; } if (tabs[i].cb_rect.has_point(pos) && (cb_displaypolicy == CLOSE_BUTTON_SHOW_ALWAYS || (cb_displaypolicy == CLOSE_BUTTON_SHOW_ACTIVE_ONLY && i == current))) { cb_pressing = true; - update(); + queue_redraw(); return; } @@ -299,9 +286,6 @@ void TabBar::gui_input(const Ref<InputEvent> &p_event) { } void TabBar::_shape(int p_tab) { - Ref<Font> font = get_theme_font(SNAME("font")); - int font_size = get_theme_font_size(SNAME("font_size")); - tabs.write[p_tab].xl_text = atr(tabs[p_tab].text); tabs.write[p_tab].text_buf->clear(); tabs.write[p_tab].text_buf->set_width(-1); @@ -311,13 +295,43 @@ 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[p_tab].xl_text, font, font_size, tabs[p_tab].language); + tabs.write[p_tab].text_buf->add_string(tabs[p_tab].xl_text, theme_cache.font, theme_cache.font_size, tabs[p_tab].language); +} + +void TabBar::_update_theme_item_cache() { + Control::_update_theme_item_cache(); + + theme_cache.h_separation = get_theme_constant(SNAME("h_separation")); + + theme_cache.tab_unselected_style = get_theme_stylebox(SNAME("tab_unselected")); + theme_cache.tab_selected_style = get_theme_stylebox(SNAME("tab_selected")); + theme_cache.tab_disabled_style = get_theme_stylebox(SNAME("tab_disabled")); + + theme_cache.increment_icon = get_theme_icon(SNAME("increment")); + theme_cache.increment_hl_icon = get_theme_icon(SNAME("increment_highlight")); + theme_cache.decrement_icon = get_theme_icon(SNAME("decrement")); + theme_cache.decrement_hl_icon = get_theme_icon(SNAME("decrement_highlight")); + theme_cache.drop_mark_icon = get_theme_icon(SNAME("drop_mark")); + theme_cache.drop_mark_color = get_theme_color(SNAME("drop_mark_color")); + + theme_cache.font = get_theme_font(SNAME("font")); + theme_cache.font_size = get_theme_font_size(SNAME("font_size")); + theme_cache.outline_size = get_theme_constant(SNAME("outline_size")); + + theme_cache.font_selected_color = get_theme_color(SNAME("font_selected_color")); + theme_cache.font_unselected_color = get_theme_color(SNAME("font_unselected_color")); + theme_cache.font_disabled_color = get_theme_color(SNAME("font_disabled_color")); + theme_cache.font_outline_color = get_theme_color(SNAME("font_outline_color")); + + theme_cache.close_icon = get_theme_icon(SNAME("close")); + theme_cache.button_pressed_style = get_theme_stylebox(SNAME("button_pressed")); + theme_cache.button_hl_style = get_theme_stylebox(SNAME("button_highlight")); } void TabBar::_notification(int p_what) { switch (p_what) { case NOTIFICATION_LAYOUT_DIRECTION_CHANGED: { - update(); + queue_redraw(); } break; case NOTIFICATION_THEME_CHANGED: @@ -343,7 +357,7 @@ void TabBar::_notification(int p_what) { case NOTIFICATION_DRAG_END: { if (dragging_valid_tab) { dragging_valid_tab = false; - update(); + queue_redraw(); } } break; @@ -352,18 +366,9 @@ void TabBar::_notification(int p_what) { return; } - 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")); - Color font_selected_color = get_theme_color(SNAME("font_selected_color")); - Color font_unselected_color = get_theme_color(SNAME("font_unselected_color")); - Color font_disabled_color = get_theme_color(SNAME("font_disabled_color")); - Ref<Texture2D> incr = get_theme_icon(SNAME("increment")); - Ref<Texture2D> decr = get_theme_icon(SNAME("decrement")); - bool rtl = is_layout_rtl(); Vector2 size = get_size(); - int limit_minus_buttons = size.width - incr->get_width() - decr->get_width(); + int limit_minus_buttons = size.width - theme_cache.increment_icon->get_width() - theme_cache.decrement_icon->get_width(); int ofs = tabs[offset].ofs_cache; @@ -378,14 +383,14 @@ void TabBar::_notification(int p_what) { Color col; if (tabs[i].disabled) { - sb = tab_disabled; - col = font_disabled_color; + sb = theme_cache.tab_disabled_style; + col = theme_cache.font_disabled_color; } else if (i == current) { - sb = tab_selected; - col = font_selected_color; + sb = theme_cache.tab_selected_style; + col = theme_cache.font_selected_color; } else { - sb = tab_unselected; - col = font_unselected_color; + sb = theme_cache.tab_unselected_style; + col = theme_cache.font_unselected_color; } _draw_tab(sb, col, i, rtl ? size.width - ofs - tabs[i].size_cache : ofs); @@ -396,41 +401,38 @@ 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 && !tabs[current].hidden) { - Ref<StyleBox> sb = tabs[current].disabled ? tab_disabled : tab_selected; + Ref<StyleBox> sb = tabs[current].disabled ? theme_cache.tab_disabled_style : theme_cache.tab_selected_style; 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); + _draw_tab(sb, theme_cache.font_selected_color, current, x); } if (buttons_visible) { - Ref<Texture2D> incr_hl = get_theme_icon(SNAME("increment_highlight")); - Ref<Texture2D> decr_hl = get_theme_icon(SNAME("decrement_highlight")); - - int vofs = (size.height - incr->get_size().height) / 2; + int vofs = (size.height - theme_cache.increment_icon->get_size().height) / 2; if (rtl) { if (missing_right) { - draw_texture(highlight_arrow == 1 ? decr_hl : decr, Point2(0, vofs)); + draw_texture(highlight_arrow == 1 ? theme_cache.decrement_hl_icon : theme_cache.decrement_icon, Point2(0, vofs)); } else { - draw_texture(decr, Point2(0, vofs), Color(1, 1, 1, 0.5)); + draw_texture(theme_cache.decrement_icon, Point2(0, vofs), Color(1, 1, 1, 0.5)); } if (offset > 0) { - draw_texture(highlight_arrow == 0 ? incr_hl : incr, Point2(incr->get_size().width, vofs)); + draw_texture(highlight_arrow == 0 ? theme_cache.increment_hl_icon : theme_cache.increment_icon, Point2(theme_cache.increment_icon->get_size().width, vofs)); } else { - draw_texture(incr, Point2(incr->get_size().width, vofs), Color(1, 1, 1, 0.5)); + draw_texture(theme_cache.increment_icon, Point2(theme_cache.increment_icon->get_size().width, vofs), Color(1, 1, 1, 0.5)); } } else { if (offset > 0) { - draw_texture(highlight_arrow == 0 ? decr_hl : decr, Point2(limit_minus_buttons, vofs)); + draw_texture(highlight_arrow == 0 ? theme_cache.decrement_hl_icon : theme_cache.decrement_icon, Point2(limit_minus_buttons, vofs)); } else { - draw_texture(decr, Point2(limit_minus_buttons, vofs), Color(1, 1, 1, 0.5)); + draw_texture(theme_cache.decrement_icon, Point2(limit_minus_buttons, vofs), Color(1, 1, 1, 0.5)); } if (missing_right) { - draw_texture(highlight_arrow == 1 ? incr_hl : incr, Point2(limit_minus_buttons + decr->get_size().width, vofs)); + draw_texture(highlight_arrow == 1 ? theme_cache.increment_hl_icon : theme_cache.increment_icon, Point2(limit_minus_buttons + theme_cache.decrement_icon->get_size().width, vofs)); } else { - draw_texture(incr, Point2(limit_minus_buttons + decr->get_size().width, vofs), Color(1, 1, 1, 0.5)); + draw_texture(theme_cache.increment_icon, Point2(limit_minus_buttons + theme_cache.decrement_icon->get_size().width, vofs), Color(1, 1, 1, 0.5)); } } } @@ -462,10 +464,7 @@ void TabBar::_notification(int p_what) { } } - Ref<Texture2D> drop_mark = get_theme_icon(SNAME("drop_mark")); - Color drop_mark_color = get_theme_color(SNAME("drop_mark_color")); - - drop_mark->draw(get_canvas_item(), Point2(x - drop_mark->get_width() / 2, (size.height - drop_mark->get_height()) / 2), drop_mark_color); + theme_cache.drop_mark_icon->draw(get_canvas_item(), Point2(x - theme_cache.drop_mark_icon->get_width() / 2, (size.height - theme_cache.drop_mark_icon->get_height()) / 2), theme_cache.drop_mark_color); } } break; } @@ -475,10 +474,6 @@ void TabBar::_draw_tab(Ref<StyleBox> &p_tab_style, Color &p_font_color, int p_in 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("h_separation")); - Rect2 sb_rect = Rect2(p_x, 0, tabs[p_index].size_cache, get_size().height); p_tab_style->draw(ci, sb_rect); @@ -491,7 +486,7 @@ void TabBar::_draw_tab(Ref<StyleBox> &p_tab_style, Color &p_font_color, int p_in if (icon.is_valid()) { 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)); - p_x = rtl ? p_x - icon->get_width() - hseparation : p_x + icon->get_width() + hseparation; + p_x = rtl ? p_x - icon->get_width() - theme_cache.h_separation : p_x + icon->get_width() + theme_cache.h_separation; } // Draw the text. @@ -499,17 +494,17 @@ void TabBar::_draw_tab(Ref<StyleBox> &p_tab_style, Color &p_font_color, int p_in 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); - if (outline_size > 0 && font_outline_color.a > 0) { - tabs[p_index].text_buf->draw_outline(ci, text_pos, outline_size, font_outline_color); + if (theme_cache.outline_size > 0 && theme_cache.font_outline_color.a > 0) { + tabs[p_index].text_buf->draw_outline(ci, text_pos, theme_cache.outline_size, theme_cache.font_outline_color); } tabs[p_index].text_buf->draw(ci, text_pos, p_font_color); - p_x = rtl ? p_x - tabs[p_index].size_text - hseparation : p_x + tabs[p_index].size_text + hseparation; + p_x = rtl ? p_x - tabs[p_index].size_text - theme_cache.h_separation : p_x + tabs[p_index].size_text + theme_cache.h_separation; } // 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<StyleBox> style = theme_cache.button_hl_style; Ref<Texture2D> rb = tabs[p_index].right_button; Rect2 rb_rect; @@ -521,7 +516,7 @@ void TabBar::_draw_tab(Ref<StyleBox> &p_tab_style, Color &p_font_color, int p_in if (rb_hover == p_index) { if (rb_pressing) { - get_theme_stylebox(SNAME("button_pressed"))->draw(ci, rb_rect); + theme_cache.button_pressed_style->draw(ci, rb_rect); } else { style->draw(ci, rb_rect); } @@ -534,8 +529,8 @@ void TabBar::_draw_tab(Ref<StyleBox> &p_tab_style, Color &p_font_color, int p_in // 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("button_highlight")); - Ref<Texture2D> cb = get_theme_icon(SNAME("close")); + Ref<StyleBox> style = theme_cache.button_hl_style; + Ref<Texture2D> cb = theme_cache.close_icon; Rect2 cb_rect; cb_rect.size = style->get_minimum_size() + cb->get_size(); @@ -546,7 +541,7 @@ void TabBar::_draw_tab(Ref<StyleBox> &p_tab_style, Color &p_font_color, int p_in if (!tabs[p_index].disabled && cb_hover == p_index) { if (cb_pressing) { - get_theme_stylebox(SNAME("button_pressed"))->draw(ci, cb_rect); + theme_cache.button_pressed_style->draw(ci, cb_rect); } else { style->draw(ci, cb_rect); } @@ -581,7 +576,7 @@ void TabBar::set_tab_count(int p_count) { } } - update(); + queue_redraw(); update_minimum_size(); notify_property_list_changed(); } @@ -607,7 +602,7 @@ void TabBar::set_current_tab(int p_current) { if (scroll_to_selected) { ensure_tab_visible(current); } - update(); + queue_redraw(); emit_signal(SNAME("tab_changed"), p_current); } @@ -634,6 +629,11 @@ 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()); + + if (tabs[p_tab].text == p_title) { + return; + } + tabs.write[p_tab].text = p_title; _shape(p_tab); @@ -642,7 +642,7 @@ void TabBar::set_tab_title(int p_tab, const String &p_title) { if (scroll_to_selected) { ensure_tab_visible(current); } - update(); + queue_redraw(); update_minimum_size(); } @@ -658,7 +658,7 @@ void TabBar::set_tab_text_direction(int p_tab, Control::TextDirection p_text_dir if (tabs[p_tab].text_direction != p_text_direction) { tabs.write[p_tab].text_direction = p_text_direction; _shape(p_tab); - update(); + queue_redraw(); } } @@ -678,7 +678,7 @@ void TabBar::set_tab_language(int p_tab, const String &p_language) { if (scroll_to_selected) { ensure_tab_visible(current); } - update(); + queue_redraw(); update_minimum_size(); } } @@ -690,6 +690,11 @@ 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()); + + if (tabs[p_tab].icon == p_icon) { + return; + } + tabs.write[p_tab].icon = p_icon; _update_cache(); @@ -697,7 +702,7 @@ void TabBar::set_tab_icon(int p_tab, const Ref<Texture2D> &p_icon) { if (scroll_to_selected) { ensure_tab_visible(current); } - update(); + queue_redraw(); update_minimum_size(); } @@ -708,6 +713,11 @@ 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()); + + if (tabs[p_tab].disabled == p_disabled) { + return; + } + tabs.write[p_tab].disabled = p_disabled; _update_cache(); @@ -715,7 +725,7 @@ void TabBar::set_tab_disabled(int p_tab, bool p_disabled) { if (scroll_to_selected) { ensure_tab_visible(current); } - update(); + queue_redraw(); update_minimum_size(); } @@ -726,6 +736,11 @@ bool TabBar::is_tab_disabled(int p_tab) const { void TabBar::set_tab_hidden(int p_tab, bool p_hidden) { ERR_FAIL_INDEX(p_tab, tabs.size()); + + if (tabs[p_tab].hidden == p_hidden) { + return; + } + tabs.write[p_tab].hidden = p_hidden; _update_cache(); @@ -733,7 +748,7 @@ void TabBar::set_tab_hidden(int p_tab, bool p_hidden) { if (scroll_to_selected) { ensure_tab_visible(current); } - update(); + queue_redraw(); update_minimum_size(); } @@ -744,6 +759,11 @@ bool TabBar::is_tab_hidden(int p_tab) const { void TabBar::set_tab_button_icon(int p_tab, const Ref<Texture2D> &p_icon) { ERR_FAIL_INDEX(p_tab, tabs.size()); + + if (tabs[p_tab].right_button == p_icon) { + return; + } + tabs.write[p_tab].right_button = p_icon; _update_cache(); @@ -751,7 +771,7 @@ void TabBar::set_tab_button_icon(int p_tab, const Ref<Texture2D> &p_icon) { if (scroll_to_selected) { ensure_tab_visible(current); } - update(); + queue_redraw(); update_minimum_size(); } @@ -792,7 +812,7 @@ void TabBar::_update_hover() { } if (hover_buttons != -1) { - update(); + queue_redraw(); break; } } @@ -813,7 +833,7 @@ void TabBar::_update_hover() { cb_hover = hover_buttons; if (rb_hover != rb_hover_old || cb_hover != cb_hover_old) { - update(); + queue_redraw(); } } } @@ -824,14 +844,8 @@ void TabBar::_update_cache() { return; } - Ref<StyleBox> tab_disabled = get_theme_stylebox(SNAME("tab_disabled")); - Ref<StyleBox> tab_unselected = get_theme_stylebox(SNAME("tab_unselected")); - Ref<StyleBox> tab_selected = get_theme_stylebox(SNAME("tab_selected")); - Ref<Texture2D> incr = get_theme_icon(SNAME("increment")); - Ref<Texture2D> decr = get_theme_icon(SNAME("decrement")); - int limit = get_size().width; - int limit_minus_buttons = limit - incr->get_width() - decr->get_width(); + int limit_minus_buttons = limit - theme_cache.increment_icon->get_width() - theme_cache.decrement_icon->get_width(); int w = 0; @@ -915,7 +929,7 @@ void TabBar::_on_mouse_exited() { highlight_arrow = -1; dragging_valid_tab = false; - update(); + queue_redraw(); } void TabBar::add_tab(const String &p_str, const Ref<Texture2D> &p_icon) { @@ -930,7 +944,7 @@ void TabBar::add_tab(const String &p_str, const Ref<Texture2D> &p_icon) { if (scroll_to_selected) { ensure_tab_visible(current); } - update(); + queue_redraw(); update_minimum_size(); if (tabs.size() == 1 && is_inside_tree()) { @@ -949,7 +963,7 @@ void TabBar::clear_tabs() { current = 0; previous = 0; - update(); + queue_redraw(); update_minimum_size(); notify_property_list_changed(); } @@ -979,7 +993,7 @@ void TabBar::remove_tab(int p_idx) { } } - update(); + queue_redraw(); update_minimum_size(); notify_property_list_changed(); @@ -1127,7 +1141,7 @@ void TabBar::drop_data(const Point2 &p_point, const Variant &p_data) { set_current_tab(hover_now); } else { _update_cache(); - update(); + queue_redraw(); } update_minimum_size(); @@ -1155,10 +1169,15 @@ 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); + + if (tab_alignment == p_alignment) { + return; + } + tab_alignment = p_alignment; _update_cache(); - update(); + queue_redraw(); } TabBar::AlignmentMode TabBar::get_tab_alignment() const { @@ -1180,7 +1199,7 @@ void TabBar::set_clip_tabs(bool p_clip_tabs) { if (scroll_to_selected) { ensure_tab_visible(current); } - update(); + queue_redraw(); update_minimum_size(); } @@ -1221,59 +1240,54 @@ void TabBar::move_tab(int p_from, int p_to) { if (scroll_to_selected) { ensure_tab_visible(current); } - update(); + queue_redraw(); notify_property_list_changed(); } int TabBar::get_tab_width(int p_idx) const { ERR_FAIL_INDEX_V(p_idx, tabs.size(), 0); - 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("h_separation")); - Ref<StyleBox> style; if (tabs[p_idx].disabled) { - style = tab_disabled; + style = theme_cache.tab_disabled_style; } else if (current == p_idx) { - style = tab_selected; + style = theme_cache.tab_selected_style; } else { - style = tab_unselected; + style = theme_cache.tab_unselected_style; } int x = style->get_minimum_size().width; Ref<Texture2D> tex = tabs[p_idx].icon; if (tex.is_valid()) { - x += tex->get_width() + hseparation; + x += tex->get_width() + theme_cache.h_separation; } if (!tabs[p_idx].text.is_empty()) { - x += tabs[p_idx].size_text + hseparation; + x += tabs[p_idx].size_text + theme_cache.h_separation; } 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<StyleBox> btn_style = theme_cache.button_hl_style; Ref<Texture2D> rb = tabs[p_idx].right_button; 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; + x += btn_style->get_margin(SIDE_LEFT) + rb->get_width() + theme_cache.h_separation; } } if (close_visible) { - Ref<StyleBox> btn_style = get_theme_stylebox(SNAME("button_highlight")); - Ref<Texture2D> cb = get_theme_icon(SNAME("close")); - x += btn_style->get_margin(SIDE_LEFT) + cb->get_width() + hseparation; + Ref<StyleBox> btn_style = theme_cache.button_hl_style; + Ref<Texture2D> cb = theme_cache.close_icon; + x += btn_style->get_margin(SIDE_LEFT) + cb->get_width() + theme_cache.h_separation; } if (x > style->get_minimum_size().width) { - x -= hseparation; + x -= theme_cache.h_separation; } return x; @@ -1284,9 +1298,7 @@ void TabBar::_ensure_no_over_offset() { return; } - Ref<Texture2D> incr = get_theme_icon(SNAME("increment")); - Ref<Texture2D> decr = get_theme_icon(SNAME("decrement")); - int limit_minus_buttons = get_size().width - incr->get_width() - decr->get_width(); + int limit_minus_buttons = get_size().width - theme_cache.increment_icon->get_width() - theme_cache.decrement_icon->get_width(); int prev_offset = offset; @@ -1307,7 +1319,7 @@ void TabBar::_ensure_no_over_offset() { if (prev_offset != offset) { _update_cache(); - update(); + queue_redraw(); } } @@ -1324,14 +1336,12 @@ void TabBar::ensure_tab_visible(int p_idx) { if (p_idx < offset) { offset = p_idx; _update_cache(); - update(); + queue_redraw(); return; } - Ref<Texture2D> incr = get_theme_icon(SNAME("increment")); - Ref<Texture2D> decr = get_theme_icon(SNAME("decrement")); - int limit_minus_buttons = get_size().width - incr->get_width() - decr->get_width(); + int limit_minus_buttons = get_size().width - theme_cache.increment_icon->get_width() - theme_cache.decrement_icon->get_width(); int total_w = tabs[max_drawn_tab].ofs_cache - tabs[offset].ofs_cache; for (int i = max_drawn_tab; i <= p_idx; i++) { @@ -1359,7 +1369,7 @@ void TabBar::ensure_tab_visible(int p_idx) { if (prev_offset != offset) { _update_cache(); - update(); + queue_redraw(); } } @@ -1374,6 +1384,11 @@ 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); + + if (cb_displaypolicy == p_policy) { + return; + } + cb_displaypolicy = p_policy; _update_cache(); @@ -1381,7 +1396,7 @@ void TabBar::set_tab_close_display_policy(CloseButtonDisplayPolicy p_policy) { if (scroll_to_selected) { ensure_tab_visible(current); } - update(); + queue_redraw(); update_minimum_size(); } @@ -1391,6 +1406,11 @@ TabBar::CloseButtonDisplayPolicy TabBar::get_tab_close_display_policy() const { void TabBar::set_max_tab_width(int p_width) { ERR_FAIL_COND(p_width < 0); + + if (max_width == p_width) { + return; + } + max_width = p_width; _update_cache(); @@ -1398,7 +1418,7 @@ void TabBar::set_max_tab_width(int p_width) { if (scroll_to_selected) { ensure_tab_visible(current); } - update(); + queue_redraw(); update_minimum_size(); } diff --git a/scene/gui/tab_bar.h b/scene/gui/tab_bar.h index d123385e47..ac4a6a195e 100644 --- a/scene/gui/tab_bar.h +++ b/scene/gui/tab_bar.h @@ -104,6 +104,34 @@ private: bool scroll_to_selected = true; int tabs_rearrange_group = -1; + struct ThemeCache { + int h_separation = 0; + + Ref<StyleBox> tab_unselected_style; + Ref<StyleBox> tab_selected_style; + Ref<StyleBox> tab_disabled_style; + + Ref<Texture2D> increment_icon; + Ref<Texture2D> increment_hl_icon; + Ref<Texture2D> decrement_icon; + Ref<Texture2D> decrement_hl_icon; + Ref<Texture2D> drop_mark_icon; + Color drop_mark_color; + + Ref<Font> font; + int font_size; + int outline_size = 0; + + Color font_selected_color; + Color font_unselected_color; + Color font_disabled_color; + Color font_outline_color; + + Ref<Texture2D> close_icon; + Ref<StyleBox> button_pressed_style; + Ref<StyleBox> button_hl_style; + } theme_cache; + int get_tab_width(int p_idx) const; void _ensure_no_over_offset(); @@ -117,6 +145,7 @@ private: protected: virtual void gui_input(const Ref<InputEvent> &p_event) override; + virtual void _update_theme_item_cache() override; bool _set(const StringName &p_name, const Variant &p_value); bool _get(const StringName &p_name, Variant &r_ret) const; void _get_property_list(List<PropertyInfo> *p_list) const; diff --git a/scene/gui/tab_container.cpp b/scene/gui/tab_container.cpp index 12f91a9873..f45d132a66 100644 --- a/scene/gui/tab_container.cpp +++ b/scene/gui/tab_container.cpp @@ -60,26 +60,24 @@ void TabContainer::gui_input(const Ref<InputEvent> &p_event) { } // Handle menu button. - Ref<Texture2D> menu = get_theme_icon(SNAME("menu")); - if (is_layout_rtl()) { - if (popup && pos.x < menu->get_width()) { + if (popup && pos.x < theme_cache.menu_icon->get_width()) { emit_signal(SNAME("pre_popup_pressed")); Vector2 popup_pos = get_screen_position(); - popup_pos.y += menu->get_height(); + popup_pos.y += theme_cache.menu_icon->get_height(); popup->set_position(popup_pos); popup->popup(); return; } } else { - if (popup && pos.x > size.width - menu->get_width()) { + if (popup && pos.x > size.width - theme_cache.menu_icon->get_width()) { emit_signal(SNAME("pre_popup_pressed")); Vector2 popup_pos = get_screen_position(); popup_pos.x += size.width - popup->get_size().width; - popup_pos.y += menu->get_height(); + popup_pos.y += theme_cache.menu_icon->get_height(); popup->set_position(popup_pos); popup->popup(); @@ -98,34 +96,33 @@ void TabContainer::gui_input(const Ref<InputEvent> &p_event) { if (pos.y > _get_top_margin()) { if (menu_hovered) { menu_hovered = false; - update(); + queue_redraw(); } return; } - Ref<Texture2D> menu = get_theme_icon(SNAME("menu")); if (popup) { if (is_layout_rtl()) { - if (pos.x <= menu->get_width()) { + if (pos.x <= theme_cache.menu_icon->get_width()) { if (!menu_hovered) { menu_hovered = true; - update(); + queue_redraw(); return; } } else if (menu_hovered) { menu_hovered = false; - update(); + queue_redraw(); } } else { - if (pos.x >= size.width - menu->get_width()) { + if (pos.x >= size.width - theme_cache.menu_icon->get_width()) { if (!menu_hovered) { menu_hovered = true; - update(); + queue_redraw(); return; } } else if (menu_hovered) { menu_hovered = false; - update(); + queue_redraw(); } } @@ -136,6 +133,41 @@ void TabContainer::gui_input(const Ref<InputEvent> &p_event) { } } +void TabContainer::_update_theme_item_cache() { + Container::_update_theme_item_cache(); + + theme_cache.side_margin = get_theme_constant(SNAME("side_margin")); + + theme_cache.panel_style = get_theme_stylebox(SNAME("panel")); + theme_cache.tabbar_style = get_theme_stylebox(SNAME("tabbar_background")); + + theme_cache.menu_icon = get_theme_icon(SNAME("menu")); + theme_cache.menu_hl_icon = get_theme_icon(SNAME("menu_highlight")); + + // TabBar overrides. + theme_cache.icon_separation = get_theme_constant(SNAME("icon_separation")); + theme_cache.outline_size = get_theme_constant(SNAME("outline_size")); + + theme_cache.tab_unselected_style = get_theme_stylebox(SNAME("tab_unselected")); + theme_cache.tab_selected_style = get_theme_stylebox(SNAME("tab_selected")); + theme_cache.tab_disabled_style = get_theme_stylebox(SNAME("tab_disabled")); + + theme_cache.increment_icon = get_theme_icon(SNAME("increment")); + theme_cache.increment_hl_icon = get_theme_icon(SNAME("increment_highlight")); + theme_cache.decrement_icon = get_theme_icon(SNAME("decrement")); + theme_cache.decrement_hl_icon = get_theme_icon(SNAME("decrement_highlight")); + theme_cache.drop_mark_icon = get_theme_icon(SNAME("drop_mark")); + theme_cache.drop_mark_color = get_theme_color(SNAME("drop_mark_color")); + + theme_cache.font_selected_color = get_theme_color(SNAME("font_selected_color")); + theme_cache.font_unselected_color = get_theme_color(SNAME("font_unselected_color")); + theme_cache.font_disabled_color = get_theme_color(SNAME("font_disabled_color")); + theme_cache.font_outline_color = get_theme_color(SNAME("font_outline_color")); + + theme_cache.tab_font = get_theme_font(SNAME("font")); + theme_cache.tab_font_size = get_theme_font_size(SNAME("font_size")); +} + void TabContainer::_notification(int p_what) { switch (p_what) { case NOTIFICATION_ENTER_TREE: { @@ -155,27 +187,26 @@ void TabContainer::_notification(int p_what) { Size2 size = get_size(); // Draw only the tab area if the header is hidden. - Ref<StyleBox> panel = get_theme_stylebox(SNAME("panel")); if (!tabs_visible) { - panel->draw(canvas, Rect2(0, 0, size.width, size.height)); + theme_cache.panel_style->draw(canvas, Rect2(0, 0, size.width, size.height)); return; } int header_height = _get_top_margin(); - panel->draw(canvas, Rect2(0, header_height, size.width, size.height - header_height)); + // Draw background for the tabbar. + theme_cache.tabbar_style->draw(canvas, Rect2(0, 0, size.width, header_height)); + // Draw the background for the tab's content. + theme_cache.panel_style->draw(canvas, Rect2(0, header_height, size.width, size.height - header_height)); // Draw the popup menu. if (get_popup()) { - Ref<Texture2D> menu = get_theme_icon(SNAME("menu")); - Ref<Texture2D> menu_hl = get_theme_icon(SNAME("menu_highlight")); - - int x = is_layout_rtl() ? 0 : get_size().width - menu->get_width(); + int x = is_layout_rtl() ? 0 : get_size().width - theme_cache.menu_icon->get_width(); if (menu_hovered) { - menu_hl->draw(get_canvas_item(), Point2(x, (header_height - menu_hl->get_height()) / 2)); + theme_cache.menu_hl_icon->draw(get_canvas_item(), Point2(x, (header_height - theme_cache.menu_hl_icon->get_height()) / 2)); } else { - menu->draw(get_canvas_item(), Point2(x, (header_height - menu->get_height()) / 2)); + theme_cache.menu_icon->draw(get_canvas_item(), Point2(x, (header_height - theme_cache.menu_icon->get_height()) / 2)); } } } break; @@ -194,23 +225,27 @@ void TabContainer::_on_theme_changed() { return; } - tab_bar->add_theme_style_override(SNAME("tab_unselected"), get_theme_stylebox(SNAME("tab_unselected"))); - tab_bar->add_theme_style_override(SNAME("tab_selected"), get_theme_stylebox(SNAME("tab_selected"))); - tab_bar->add_theme_style_override(SNAME("tab_disabled"), get_theme_stylebox(SNAME("tab_disabled"))); - tab_bar->add_theme_icon_override(SNAME("increment"), get_theme_icon(SNAME("increment"))); - tab_bar->add_theme_icon_override(SNAME("increment_highlight"), get_theme_icon(SNAME("increment_highlight"))); - tab_bar->add_theme_icon_override(SNAME("decrement"), get_theme_icon(SNAME("decrement"))); - tab_bar->add_theme_icon_override(SNAME("decrement_highlight"), get_theme_icon(SNAME("decrement_highlight"))); - tab_bar->add_theme_icon_override(SNAME("drop_mark"), get_theme_icon(SNAME("drop_mark"))); - tab_bar->add_theme_color_override(SNAME("drop_mark_color"), get_theme_color(SNAME("drop_mark_color"))); - tab_bar->add_theme_color_override(SNAME("font_selected_color"), get_theme_color(SNAME("font_selected_color"))); - tab_bar->add_theme_color_override(SNAME("font_unselected_color"), get_theme_color(SNAME("font_unselected_color"))); - tab_bar->add_theme_color_override(SNAME("font_disabled_color"), get_theme_color(SNAME("font_disabled_color"))); - tab_bar->add_theme_color_override(SNAME("font_outline_color"), get_theme_color(SNAME("font_outline_color"))); - tab_bar->add_theme_font_override(SNAME("font"), get_theme_font(SNAME("font"))); - tab_bar->add_theme_font_size_override(SNAME("font_size"), get_theme_font_size(SNAME("font_size"))); - tab_bar->add_theme_constant_override(SNAME("h_separation"), get_theme_constant(SNAME("icon_separation"))); - tab_bar->add_theme_constant_override(SNAME("outline_size"), get_theme_constant(SNAME("outline_size"))); + tab_bar->add_theme_style_override(SNAME("tab_unselected"), theme_cache.tab_unselected_style); + tab_bar->add_theme_style_override(SNAME("tab_selected"), theme_cache.tab_selected_style); + tab_bar->add_theme_style_override(SNAME("tab_disabled"), theme_cache.tab_disabled_style); + + tab_bar->add_theme_icon_override(SNAME("increment"), theme_cache.increment_icon); + tab_bar->add_theme_icon_override(SNAME("increment_highlight"), theme_cache.increment_hl_icon); + tab_bar->add_theme_icon_override(SNAME("decrement"), theme_cache.decrement_icon); + tab_bar->add_theme_icon_override(SNAME("decrement_highlight"), theme_cache.decrement_hl_icon); + tab_bar->add_theme_icon_override(SNAME("drop_mark"), theme_cache.drop_mark_icon); + tab_bar->add_theme_color_override(SNAME("drop_mark_color"), theme_cache.drop_mark_color); + + tab_bar->add_theme_color_override(SNAME("font_selected_color"), theme_cache.font_selected_color); + tab_bar->add_theme_color_override(SNAME("font_unselected_color"), theme_cache.font_unselected_color); + tab_bar->add_theme_color_override(SNAME("font_disabled_color"), theme_cache.font_disabled_color); + tab_bar->add_theme_color_override(SNAME("font_outline_color"), theme_cache.font_outline_color); + + tab_bar->add_theme_font_override(SNAME("font"), theme_cache.tab_font); + tab_bar->add_theme_font_size_override(SNAME("font_size"), theme_cache.tab_font_size); + + tab_bar->add_theme_constant_override(SNAME("h_separation"), theme_cache.icon_separation); + tab_bar->add_theme_constant_override(SNAME("outline_size"), theme_cache.outline_size); _update_margins(); if (get_tab_count() > 0) { @@ -218,13 +253,12 @@ void TabContainer::_on_theme_changed() { } else { update_minimum_size(); } - update(); + queue_redraw(); theme_changing = false; } void TabContainer::_repaint() { - Ref<StyleBox> sb = get_theme_stylebox(SNAME("panel")); Vector<Control *> controls = _get_tab_controls(); int current = get_current_tab(); @@ -239,10 +273,10 @@ void TabContainer::_repaint() { c->set_offset(SIDE_TOP, _get_top_margin()); } - c->set_offset(SIDE_TOP, c->get_offset(SIDE_TOP) + sb->get_margin(SIDE_TOP)); - c->set_offset(SIDE_LEFT, c->get_offset(SIDE_LEFT) + sb->get_margin(SIDE_LEFT)); - c->set_offset(SIDE_RIGHT, c->get_offset(SIDE_RIGHT) - sb->get_margin(SIDE_RIGHT)); - c->set_offset(SIDE_BOTTOM, c->get_offset(SIDE_BOTTOM) - sb->get_margin(SIDE_BOTTOM)); + c->set_offset(SIDE_TOP, c->get_offset(SIDE_TOP) + theme_cache.panel_style->get_margin(SIDE_TOP)); + c->set_offset(SIDE_LEFT, c->get_offset(SIDE_LEFT) + theme_cache.panel_style->get_margin(SIDE_LEFT)); + c->set_offset(SIDE_RIGHT, c->get_offset(SIDE_RIGHT) - theme_cache.panel_style->get_margin(SIDE_RIGHT)); + c->set_offset(SIDE_BOTTOM, c->get_offset(SIDE_BOTTOM) - theme_cache.panel_style->get_margin(SIDE_BOTTOM)); } else { c->hide(); } @@ -252,8 +286,7 @@ void TabContainer::_repaint() { } void TabContainer::_update_margins() { - int menu_width = get_theme_icon(SNAME("menu"))->get_width(); - int side_margin = get_theme_constant(SNAME("side_margin")); + int menu_width = theme_cache.menu_icon->get_width(); // Directly check for validity, to avoid errors when quitting. bool has_popup = popup_obj_id.is_valid(); @@ -267,7 +300,7 @@ void TabContainer::_update_margins() { switch (get_tab_alignment()) { case TabBar::ALIGNMENT_LEFT: { - tab_bar->set_offset(SIDE_LEFT, side_margin); + tab_bar->set_offset(SIDE_LEFT, theme_cache.side_margin); tab_bar->set_offset(SIDE_RIGHT, has_popup ? -menu_width : 0); } break; @@ -289,10 +322,10 @@ void TabContainer::_update_margins() { int total_tabs_width = last_tab_rect.position.x - first_tab_pos + last_tab_rect.size.width; // Calculate if all the tabs would still fit if the margin was present. - if (get_clip_tabs() && (tab_bar->get_offset_buttons_visible() || (get_tab_count() > 1 && (total_tabs_width + side_margin) > get_size().width))) { + if (get_clip_tabs() && (tab_bar->get_offset_buttons_visible() || (get_tab_count() > 1 && (total_tabs_width + theme_cache.side_margin) > get_size().width))) { tab_bar->set_offset(SIDE_RIGHT, has_popup ? -menu_width : 0); } else { - tab_bar->set_offset(SIDE_RIGHT, -side_margin); + tab_bar->set_offset(SIDE_RIGHT, -theme_cache.side_margin); } } break; @@ -304,7 +337,7 @@ void TabContainer::_update_margins() { void TabContainer::_on_mouse_exited() { if (menu_hovered) { menu_hovered = false; - update(); + queue_redraw(); } } @@ -502,7 +535,7 @@ void TabContainer::add_child_notify(Node *p_child) { _update_margins(); if (get_tab_count() == 1) { - update(); + queue_redraw(); } p_child->connect("renamed", callable_mp(this, &TabContainer::_refresh_tab_names)); @@ -558,7 +591,7 @@ void TabContainer::remove_child_notify(Node *p_child) { _update_margins(); if (get_tab_count() == 0) { - update(); + queue_redraw(); } p_child->remove_meta("_tab_name"); @@ -618,6 +651,10 @@ int TabContainer::get_tab_idx_from_control(Control *p_child) const { } void TabContainer::set_tab_alignment(TabBar::AlignmentMode p_alignment) { + if (tab_bar->get_tab_alignment() == p_alignment) { + return; + } + tab_bar->set_tab_alignment(p_alignment); _update_margins(); } @@ -652,7 +689,7 @@ void TabContainer::set_tabs_visible(bool p_visible) { } } - update(); + queue_redraw(); update_minimum_size(); } @@ -679,6 +716,10 @@ void TabContainer::set_tab_title(int p_tab, const String &p_title) { Control *child = get_tab_control(p_tab); ERR_FAIL_COND(!child); + if (tab_bar->get_tab_title(p_tab) == p_title) { + return; + } + tab_bar->set_tab_title(p_tab, p_title); if (p_title == child->get_name()) { @@ -698,6 +739,10 @@ String TabContainer::get_tab_title(int p_tab) const { } void TabContainer::set_tab_icon(int p_tab, const Ref<Texture2D> &p_icon) { + if (tab_bar->get_tab_icon(p_tab) == p_icon) { + return; + } + tab_bar->set_tab_icon(p_tab, p_icon); _update_margins(); @@ -709,6 +754,10 @@ Ref<Texture2D> TabContainer::get_tab_icon(int p_tab) const { } void TabContainer::set_tab_disabled(int p_tab, bool p_disabled) { + if (tab_bar->is_tab_disabled(p_tab) == p_disabled) { + return; + } + tab_bar->set_tab_disabled(p_tab, p_disabled); _update_margins(); @@ -725,6 +774,10 @@ void TabContainer::set_tab_hidden(int p_tab, bool p_hidden) { Control *child = get_tab_control(p_tab); ERR_FAIL_COND(!child); + if (tab_bar->is_tab_hidden(p_tab) == p_hidden) { + return; + } + tab_bar->set_tab_hidden(p_tab, p_hidden); child->hide(); @@ -774,13 +827,12 @@ Size2 TabContainer::get_minimum_size() const { if (!get_clip_tabs()) { if (get_popup()) { - ms.x += get_theme_icon(SNAME("menu"))->get_width(); + ms.x += theme_cache.menu_icon->get_width(); } - int side_margin = get_theme_constant(SNAME("side_margin")); - if (side_margin > 0 && get_tab_alignment() != TabBar::ALIGNMENT_CENTER && + if (theme_cache.side_margin > 0 && get_tab_alignment() != TabBar::ALIGNMENT_CENTER && (get_tab_alignment() != TabBar::ALIGNMENT_RIGHT || !get_popup())) { - ms.x += side_margin; + ms.x += theme_cache.side_margin; } } } @@ -800,7 +852,7 @@ Size2 TabContainer::get_minimum_size() const { } ms.y += max_control_height; - Size2 panel_ms = get_theme_stylebox(SNAME("panel"))->get_minimum_size(); + Size2 panel_ms = theme_cache.panel_style->get_minimum_size(); ms.x = MAX(ms.x, panel_ms.x); ms.y += panel_ms.y; @@ -811,10 +863,14 @@ void TabContainer::set_popup(Node *p_popup) { bool had_popup = get_popup(); Popup *popup = Object::cast_to<Popup>(p_popup); - popup_obj_id = popup ? popup->get_instance_id() : ObjectID(); + ObjectID popup_id = popup ? popup->get_instance_id() : ObjectID(); + if (popup_obj_id == popup_id) { + return; + } + popup_obj_id = popup_id; if (had_popup != bool(popup)) { - update(); + queue_redraw(); _update_margins(); if (!get_clip_tabs()) { update_minimum_size(); @@ -855,6 +911,10 @@ int TabContainer::get_tabs_rearrange_group() const { } void TabContainer::set_use_hidden_tabs_for_min_size(bool p_use_hidden_tabs) { + if (use_hidden_tabs_for_min_size == p_use_hidden_tabs) { + return; + } + use_hidden_tabs_for_min_size = p_use_hidden_tabs; update_minimum_size(); } diff --git a/scene/gui/tab_container.h b/scene/gui/tab_container.h index 60c8130939..b552aa459b 100644 --- a/scene/gui/tab_container.h +++ b/scene/gui/tab_container.h @@ -48,6 +48,39 @@ class TabContainer : public Container { bool theme_changing = false; Node *child_removing = nullptr; + struct ThemeCache { + int side_margin = 0; + + Ref<StyleBox> panel_style; + Ref<StyleBox> tabbar_style; + + Ref<Texture2D> menu_icon; + Ref<Texture2D> menu_hl_icon; + + // TabBar overrides. + int icon_separation = 0; + int outline_size = 0; + + Ref<StyleBox> tab_unselected_style; + Ref<StyleBox> tab_selected_style; + Ref<StyleBox> tab_disabled_style; + + Ref<Texture2D> increment_icon; + Ref<Texture2D> increment_hl_icon; + Ref<Texture2D> decrement_icon; + Ref<Texture2D> decrement_hl_icon; + Ref<Texture2D> drop_mark_icon; + Color drop_mark_color; + + Color font_selected_color; + Color font_unselected_color; + Color font_disabled_color; + Color font_outline_color; + + Ref<Font> tab_font; + int tab_font_size; + } theme_cache; + int _get_top_margin() const; Vector<Control *> _get_tab_controls() const; void _on_theme_changed(); @@ -65,6 +98,8 @@ class TabContainer : public Container { protected: virtual void gui_input(const Ref<InputEvent> &p_event) override; + virtual void _update_theme_item_cache() override; + void _notification(int p_what); virtual void add_child_notify(Node *p_child) override; virtual void move_child_notify(Node *p_child) override; diff --git a/scene/gui/text_edit.cpp b/scene/gui/text_edit.cpp index 3755a8fa34..318447ecd8 100644 --- a/scene/gui/text_edit.cpp +++ b/scene/gui/text_edit.cpp @@ -452,13 +452,13 @@ void TextEdit::_notification(int p_what) { case NOTIFICATION_WM_WINDOW_FOCUS_IN: { window_has_focus = true; draw_caret = true; - update(); + queue_redraw(); } break; case NOTIFICATION_WM_WINDOW_FOCUS_OUT: { window_has_focus = false; draw_caret = false; - update(); + queue_redraw(); } break; case NOTIFICATION_INTERNAL_PHYSICS_PROCESS: { @@ -1507,7 +1507,7 @@ void TextEdit::_notification(int p_what) { } text.invalidate_cache(caret.line, caret.column, true, t, structured_text_parser(st_parser, st_args, t)); - update(); + queue_redraw(); } } break; @@ -1614,7 +1614,7 @@ void TextEdit::gui_input(const Ref<InputEvent> &p_gui_input) { } if (mb->is_pressed()) { - if (mb->get_button_index() == MouseButton::WHEEL_UP && !mb->is_command_pressed()) { + if (mb->get_button_index() == MouseButton::WHEEL_UP && !mb->is_command_or_control_pressed()) { if (mb->is_shift_pressed()) { h_scroll->set_value(h_scroll->get_value() - (100 * mb->get_factor())); } else if (mb->is_alt_pressed()) { @@ -1625,7 +1625,7 @@ void TextEdit::gui_input(const Ref<InputEvent> &p_gui_input) { _scroll_up(3 * mb->get_factor()); } } - if (mb->get_button_index() == MouseButton::WHEEL_DOWN && !mb->is_command_pressed()) { + if (mb->get_button_index() == MouseButton::WHEEL_DOWN && !mb->is_command_or_control_pressed()) { if (mb->is_shift_pressed()) { h_scroll->set_value(h_scroll->get_value() + (100 * mb->get_factor())); } else if (mb->is_alt_pressed()) { @@ -1696,7 +1696,7 @@ void TextEdit::gui_input(const Ref<InputEvent> &p_gui_input) { } selection.selecting_line = prev_line; selection.selecting_column = prev_col; - update(); + queue_redraw(); } else { if (caret.line < selection.selecting_line || (caret.line == selection.selecting_line && caret.column < selection.selecting_column)) { if (selection.shiftclick_left) { @@ -1718,7 +1718,7 @@ void TextEdit::gui_input(const Ref<InputEvent> &p_gui_input) { selection.active = false; } - update(); + queue_redraw(); } } else if (drag_and_drop_selection_enabled && is_mouse_over_selection()) { selection.selecting_mode = SelectionMode::SELECTION_MODE_NONE; @@ -1746,7 +1746,7 @@ void TextEdit::gui_input(const Ref<InputEvent> &p_gui_input) { last_dblclk = OS::get_singleton()->get_ticks_msec(); last_dblclk_pos = mb->get_position(); } - update(); + queue_redraw(); } if (is_middle_mouse_paste_enabled() && mb->get_button_index() == MouseButton::MIDDLE && DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_CLIPBOARD_PRIMARY)) { @@ -1880,7 +1880,7 @@ void TextEdit::gui_input(const Ref<InputEvent> &p_gui_input) { if (current_hovered_gutter != hovered_gutter) { hovered_gutter = current_hovered_gutter; - update(); + queue_redraw(); } if (drag_action && can_drop_data(mpos, get_viewport()->gui_get_drag_data())) { @@ -1920,7 +1920,7 @@ void TextEdit::gui_input(const Ref<InputEvent> &p_gui_input) { // Allow unicode handling if: // * No Modifiers are pressed (except shift) - bool allow_unicode_handling = !(k->is_command_pressed() || k->is_ctrl_pressed() || k->is_alt_pressed() || k->is_meta_pressed()); + bool allow_unicode_handling = !(k->is_command_or_control_pressed() || k->is_ctrl_pressed() || k->is_alt_pressed() || k->is_meta_pressed()); selection.selecting_text = false; @@ -2146,7 +2146,7 @@ void TextEdit::_swap_current_input_direction() { input_direction = TEXT_DIRECTION_LTR; } set_caret_column(caret.column); - update(); + queue_redraw(); } void TextEdit::_new_line(bool p_split_current_line, bool p_above) { @@ -2527,7 +2527,7 @@ void TextEdit::_delete(bool p_word, bool p_all_to_right) { } _remove_text(caret.line, caret.column, next_line, next_column); - update(); + queue_redraw(); } void TextEdit::_move_caret_document_start(bool p_select) { @@ -2816,7 +2816,7 @@ void TextEdit::set_editable(const bool p_editable) { editable = p_editable; - update(); + queue_redraw(); } bool TextEdit::is_editable() const { @@ -2846,7 +2846,7 @@ void TextEdit::set_text_direction(Control::TextDirection p_text_direction) { menu_dir->set_item_checked(menu_dir->get_item_index(MENU_DIR_LTR), text_direction == TEXT_DIRECTION_LTR); menu_dir->set_item_checked(menu_dir->get_item_index(MENU_DIR_RTL), text_direction == TEXT_DIRECTION_RTL); } - update(); + queue_redraw(); } } @@ -2866,7 +2866,7 @@ void TextEdit::set_language(const String &p_language) { text.set_direction_and_language(dir, (!language.is_empty()) ? language : TranslationServer::get_singleton()->get_tool_locale()); text.invalidate_all(); _update_placeholder(); - update(); + queue_redraw(); } } @@ -2880,7 +2880,7 @@ void TextEdit::set_structured_text_bidi_override(TextServer::StructuredTextParse for (int i = 0; i < text.size(); i++) { text.set(i, text[i], structured_text_parser(st_parser, st_args, text[i])); } - update(); + queue_redraw(); } } @@ -2889,11 +2889,15 @@ TextServer::StructuredTextParser TextEdit::get_structured_text_bidi_override() c } void TextEdit::set_structured_text_bidi_override_options(Array p_args) { + if (st_args == p_args) { + return; + } + st_args = p_args; for (int i = 0; i < text.size(); i++) { text.set(i, text[i], structured_text_parser(st_parser, st_args, text[i])); } - update(); + queue_redraw(); } Array TextEdit::get_structured_text_bidi_override_options() const { @@ -2908,7 +2912,7 @@ void TextEdit::set_tab_size(const int p_size) { text.set_tab_size(p_size); text.invalidate_all_lines(); _update_placeholder(); - update(); + queue_redraw(); } int TextEdit::get_tab_size() const { @@ -2917,8 +2921,12 @@ int TextEdit::get_tab_size() const { // User controls void TextEdit::set_overtype_mode_enabled(const bool p_enabled) { + if (overtype_mode == p_enabled) { + return; + } + overtype_mode = p_enabled; - update(); + queue_redraw(); } bool TextEdit::is_overtype_mode_enabled() const { @@ -3014,7 +3022,7 @@ void TextEdit::set_text(const String &p_text) { set_caret_line(0); set_caret_column(0); - update(); + queue_redraw(); setting_text = false; emit_signal(SNAME("text_set")); } @@ -3036,9 +3044,13 @@ int TextEdit::get_line_count() const { } void TextEdit::set_placeholder(const String &p_text) { + if (placeholder_text == p_text) { + return; + } + placeholder_text = p_text; _update_placeholder(); - update(); + queue_redraw(); } String TextEdit::get_placeholder() const { @@ -3137,7 +3149,7 @@ void TextEdit::insert_line_at(int p_at, const String &p_text) { ++selection.to_line; } } - update(); + queue_redraw(); } void TextEdit::insert_text_at_caret(const String &p_text) { @@ -3154,7 +3166,7 @@ void TextEdit::insert_text_at_caret(const String &p_text) { set_caret_line(new_line, false); set_caret_column(new_column); - update(); + queue_redraw(); if (had_selection) { end_complex_operation(); @@ -3545,7 +3557,7 @@ void TextEdit::undo() { set_caret_line(undo_stack_pos->get().from_line, false); set_caret_column(undo_stack_pos->get().from_column); } - update(); + queue_redraw(); } void TextEdit::redo() { @@ -3580,7 +3592,7 @@ void TextEdit::redo() { set_caret_line(undo_stack_pos->get().to_line, false); set_caret_column(undo_stack_pos->get().to_column); undo_stack_pos = undo_stack_pos->next(); - update(); + queue_redraw(); } void TextEdit::clear_undo_history() { @@ -3945,8 +3957,12 @@ bool TextEdit::is_mouse_over_selection(bool p_edges) const { /* Caret */ void TextEdit::set_caret_type(CaretType p_type) { + if (caret_type == p_type) { + return; + } + caret_type = p_type; - update(); + queue_redraw(); } TextEdit::CaretType TextEdit::get_caret_type() const { @@ -3954,6 +3970,10 @@ TextEdit::CaretType TextEdit::get_caret_type() const { } void TextEdit::set_caret_blink_enabled(const bool p_enabled) { + if (caret_blink_enabled == p_enabled) { + return; + } + caret_blink_enabled = p_enabled; if (has_focus()) { @@ -3970,13 +3990,13 @@ bool TextEdit::is_caret_blink_enabled() const { return caret_blink_enabled; } -float TextEdit::get_caret_blink_speed() const { +float TextEdit::get_caret_blink_interval() const { return caret_blink_timer->get_wait_time(); } -void TextEdit::set_caret_blink_speed(const float p_speed) { - ERR_FAIL_COND(p_speed <= 0); - caret_blink_timer->set_wait_time(p_speed); +void TextEdit::set_caret_blink_interval(const float p_interval) { + ERR_FAIL_COND(p_interval <= 0); + caret_blink_timer->set_wait_time(p_interval); } void TextEdit::set_move_caret_on_right_click_enabled(const bool p_enabled) { @@ -4114,6 +4134,10 @@ String TextEdit::get_word_under_caret() const { /* Selection. */ void TextEdit::set_selecting_enabled(const bool p_enabled) { + if (selecting_enabled == p_enabled) { + return; + } + selecting_enabled = p_enabled; if (!selecting_enabled) { @@ -4126,6 +4150,10 @@ bool TextEdit::is_selecting_enabled() const { } void TextEdit::set_deselect_on_focus_loss_enabled(const bool p_enabled) { + if (deselect_on_focus_loss_enabled == p_enabled) { + return; + } + deselect_on_focus_loss_enabled = p_enabled; if (p_enabled && selection.active && !has_focus()) { deselect(); @@ -4189,7 +4217,7 @@ void TextEdit::select_all() { selection.shiftclick_left = true; set_caret_line(selection.to_line, false); set_caret_column(selection.to_column, false); - update(); + queue_redraw(); } void TextEdit::select_word_under_caret() { @@ -4284,7 +4312,7 @@ void TextEdit::select(int p_from_line, int p_from_column, int p_to_line, int p_t selection.shiftclick_left = true; } - update(); + queue_redraw(); } bool TextEdit::has_selection() const { @@ -4331,7 +4359,7 @@ int TextEdit::get_selection_to_column() const { void TextEdit::deselect() { selection.active = false; - update(); + queue_redraw(); } void TextEdit::delete_selection() { @@ -4344,7 +4372,7 @@ void TextEdit::delete_selection() { _remove_text(selection.from_line, selection.from_column, selection.to_line, selection.to_column); set_caret_line(selection.from_line, false, false); set_caret_column(selection.from_column); - update(); + queue_redraw(); } /* Line wrapping. */ @@ -4431,8 +4459,12 @@ bool TextEdit::is_smooth_scroll_enabled() const { } void TextEdit::set_scroll_past_end_of_file_enabled(const bool p_enabled) { + if (scroll_past_end_of_file_enabled == p_enabled) { + return; + } + scroll_past_end_of_file_enabled = p_enabled; - update(); + queue_redraw(); } bool TextEdit::is_scroll_past_end_of_file_enabled() const { @@ -4656,7 +4688,7 @@ void TextEdit::adjust_viewport_to_caret() { } h_scroll->set_value(caret.x_ofs); - update(); + queue_redraw(); } void TextEdit::center_viewport_to_caret() { @@ -4709,16 +4741,18 @@ void TextEdit::center_viewport_to_caret() { } h_scroll->set_value(caret.x_ofs); - update(); + queue_redraw(); } /* Minimap */ void TextEdit::set_draw_minimap(bool p_enabled) { - if (draw_minimap != p_enabled) { - draw_minimap = p_enabled; - _update_wrap_at_column(); + if (draw_minimap == p_enabled) { + return; } - update(); + + draw_minimap = p_enabled; + _update_wrap_at_column(); + queue_redraw(); } bool TextEdit::is_drawing_minimap() const { @@ -4726,11 +4760,13 @@ bool TextEdit::is_drawing_minimap() const { } void TextEdit::set_minimap_width(int p_minimap_width) { - if (minimap_width != p_minimap_width) { - minimap_width = p_minimap_width; - _update_wrap_at_column(); + if (minimap_width == p_minimap_width) { + return; } - update(); + + minimap_width = p_minimap_width; + _update_wrap_at_column(); + queue_redraw(); } int TextEdit::get_minimap_width() const { @@ -4750,8 +4786,11 @@ void TextEdit::add_gutter(int p_at) { } text.add_gutter(p_at); + + _update_gutter_width(); + emit_signal(SNAME("gutter_added")); - update(); + queue_redraw(); } void TextEdit::remove_gutter(int p_gutter) { @@ -4760,8 +4799,11 @@ void TextEdit::remove_gutter(int p_gutter) { gutters.remove_at(p_gutter); text.remove_gutter(p_gutter); + + _update_gutter_width(); + emit_signal(SNAME("gutter_removed")); - update(); + queue_redraw(); } int TextEdit::get_gutter_count() const { @@ -4780,8 +4822,13 @@ String TextEdit::get_gutter_name(int p_gutter) const { void TextEdit::set_gutter_type(int p_gutter, GutterType p_type) { ERR_FAIL_INDEX(p_gutter, gutters.size()); + + if (gutters[p_gutter].type == p_type) { + return; + } + gutters.write[p_gutter].type = p_type; - update(); + queue_redraw(); } TextEdit::GutterType TextEdit::get_gutter_type(int p_gutter) const { @@ -4823,8 +4870,13 @@ bool TextEdit::is_gutter_drawn(int p_gutter) const { void TextEdit::set_gutter_clickable(int p_gutter, bool p_clickable) { ERR_FAIL_INDEX(p_gutter, gutters.size()); + + if (gutters[p_gutter].clickable == p_clickable) { + return; + } + gutters.write[p_gutter].clickable = p_clickable; - update(); + queue_redraw(); } bool TextEdit::is_gutter_clickable(int p_gutter) const { @@ -4872,14 +4924,18 @@ void TextEdit::merge_gutters(int p_from_line, int p_to_line) { text.set_line_gutter_clickable(p_to_line, i, true); } } - update(); + queue_redraw(); } void TextEdit::set_gutter_custom_draw(int p_gutter, const Callable &p_draw_callback) { ERR_FAIL_INDEX(p_gutter, gutters.size()); + if (gutters[p_gutter].custom_draw_callback == p_draw_callback) { + return; + } + gutters.write[p_gutter].custom_draw_callback = p_draw_callback; - update(); + queue_redraw(); } // Line gutters. @@ -4898,8 +4954,13 @@ Variant TextEdit::get_line_gutter_metadata(int p_line, int p_gutter) const { void TextEdit::set_line_gutter_text(int p_line, int p_gutter, const String &p_text) { ERR_FAIL_INDEX(p_line, text.size()); ERR_FAIL_INDEX(p_gutter, gutters.size()); + + if (text.get_line_gutter_text(p_line, p_gutter) == p_text) { + return; + } + text.set_line_gutter_text(p_line, p_gutter, p_text); - update(); + queue_redraw(); } String TextEdit::get_line_gutter_text(int p_line, int p_gutter) const { @@ -4911,8 +4972,13 @@ String TextEdit::get_line_gutter_text(int p_line, int p_gutter) const { void TextEdit::set_line_gutter_icon(int p_line, int p_gutter, const Ref<Texture2D> &p_icon) { ERR_FAIL_INDEX(p_line, text.size()); ERR_FAIL_INDEX(p_gutter, gutters.size()); + + if (text.get_line_gutter_icon(p_line, p_gutter) == p_icon) { + return; + } + text.set_line_gutter_icon(p_line, p_gutter, p_icon); - update(); + queue_redraw(); } Ref<Texture2D> TextEdit::get_line_gutter_icon(int p_line, int p_gutter) const { @@ -4924,8 +4990,13 @@ Ref<Texture2D> TextEdit::get_line_gutter_icon(int p_line, int p_gutter) const { void TextEdit::set_line_gutter_item_color(int p_line, int p_gutter, const Color &p_color) { ERR_FAIL_INDEX(p_line, text.size()); ERR_FAIL_INDEX(p_gutter, gutters.size()); + + if (text.get_line_gutter_item_color(p_line, p_gutter) == p_color) { + return; + } + text.set_line_gutter_item_color(p_line, p_gutter, p_color); - update(); + queue_redraw(); } Color TextEdit::get_line_gutter_item_color(int p_line, int p_gutter) const { @@ -4949,8 +5020,13 @@ bool TextEdit::is_line_gutter_clickable(int p_line, int p_gutter) const { // Line style void TextEdit::set_line_background_color(int p_line, const Color &p_color) { ERR_FAIL_INDEX(p_line, text.size()); + + if (text.get_line_background_color(p_line) == p_color) { + return; + } + text.set_line_background_color(p_line, p_color); - update(); + queue_redraw(); } Color TextEdit::get_line_background_color(int p_line) const { @@ -4960,11 +5036,15 @@ Color TextEdit::get_line_background_color(int p_line) const { /* Syntax Highlighting. */ void TextEdit::set_syntax_highlighter(Ref<SyntaxHighlighter> p_syntax_highlighter) { + if (syntax_highlighter == p_syntax_highlighter && syntax_highlighter.is_valid() == p_syntax_highlighter.is_valid()) { + return; + } + syntax_highlighter = p_syntax_highlighter; if (syntax_highlighter.is_valid()) { syntax_highlighter->set_text_edit(this); } - update(); + queue_redraw(); } Ref<SyntaxHighlighter> TextEdit::get_syntax_highlighter() const { @@ -4973,8 +5053,12 @@ Ref<SyntaxHighlighter> TextEdit::get_syntax_highlighter() const { /* Visual. */ void TextEdit::set_highlight_current_line(bool p_enabled) { + if (highlight_current_line == p_enabled) { + return; + } + highlight_current_line = p_enabled; - update(); + queue_redraw(); } bool TextEdit::is_highlight_current_line_enabled() const { @@ -4982,8 +5066,12 @@ bool TextEdit::is_highlight_current_line_enabled() const { } void TextEdit::set_highlight_all_occurrences(const bool p_enabled) { + if (highlight_all_occurrences == p_enabled) { + return; + } + highlight_all_occurrences = p_enabled; - update(); + queue_redraw(); } bool TextEdit::is_highlight_all_occurrences_enabled() const { @@ -4999,7 +5087,7 @@ void TextEdit::set_draw_control_chars(bool p_enabled) { text.set_draw_control_chars(draw_control_chars); text.invalidate_font(); _update_placeholder(); - update(); + queue_redraw(); } } @@ -5008,8 +5096,12 @@ bool TextEdit::get_draw_control_chars() const { } void TextEdit::set_draw_tabs(bool p_enabled) { + if (draw_tabs == p_enabled) { + return; + } + draw_tabs = p_enabled; - update(); + queue_redraw(); } bool TextEdit::is_drawing_tabs() const { @@ -5017,8 +5109,12 @@ bool TextEdit::is_drawing_tabs() const { } void TextEdit::set_draw_spaces(bool p_enabled) { + if (draw_spaces == p_enabled) { + return; + } + draw_spaces = p_enabled; - update(); + queue_redraw(); } bool TextEdit::is_drawing_spaces() const { @@ -5198,8 +5294,8 @@ void TextEdit::_bind_methods() { ClassDB::bind_method(D_METHOD("set_caret_blink_enabled", "enable"), &TextEdit::set_caret_blink_enabled); ClassDB::bind_method(D_METHOD("is_caret_blink_enabled"), &TextEdit::is_caret_blink_enabled); - ClassDB::bind_method(D_METHOD("set_caret_blink_speed", "blink_speed"), &TextEdit::set_caret_blink_speed); - ClassDB::bind_method(D_METHOD("get_caret_blink_speed"), &TextEdit::get_caret_blink_speed); + ClassDB::bind_method(D_METHOD("set_caret_blink_interval", "interval"), &TextEdit::set_caret_blink_interval); + ClassDB::bind_method(D_METHOD("get_caret_blink_interval"), &TextEdit::get_caret_blink_interval); ClassDB::bind_method(D_METHOD("set_move_caret_on_right_click_enabled", "enable"), &TextEdit::set_move_caret_on_right_click_enabled); ClassDB::bind_method(D_METHOD("is_move_caret_on_right_click_enabled"), &TextEdit::is_move_caret_on_right_click_enabled); @@ -5428,7 +5524,7 @@ void TextEdit::_bind_methods() { ADD_GROUP("Caret", "caret_"); ADD_PROPERTY(PropertyInfo(Variant::INT, "caret_type", PROPERTY_HINT_ENUM, "Line,Block"), "set_caret_type", "get_caret_type"); 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,suffix:s"), "set_caret_blink_speed", "get_caret_blink_speed"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "caret_blink_interval", PROPERTY_HINT_RANGE, "0.1,10,0.01,suffix:s"), "set_caret_blink_interval", "get_caret_blink_interval"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "caret_move_on_right_click"), "set_move_caret_on_right_click_enabled", "is_move_caret_on_right_click_enabled"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "caret_mid_grapheme"), "set_caret_mid_grapheme_enabled", "is_caret_mid_grapheme_enabled"); @@ -5462,11 +5558,15 @@ void TextEdit::_bind_methods() { /* Internal API for CodeEdit. */ // Line hiding. void TextEdit::_set_hiding_enabled(bool p_enabled) { + if (hiding_enabled == p_enabled) { + return; + } + if (!p_enabled) { _unhide_all_lines(); } hiding_enabled = p_enabled; - update(); + queue_redraw(); } bool TextEdit::_is_hiding_enabled() const { @@ -5483,21 +5583,30 @@ void TextEdit::_unhide_all_lines() { text.set_hidden(i, false); } _update_scrollbars(); - update(); + queue_redraw(); } void TextEdit::_set_line_as_hidden(int p_line, bool p_hidden) { ERR_FAIL_INDEX(p_line, text.size()); + + if (text.is_hidden(p_line) == p_hidden) { + return; + } + if (_is_hiding_enabled() || !p_hidden) { text.set_hidden(p_line, p_hidden); } - update(); + queue_redraw(); } // Symbol lookup. void TextEdit::_set_symbol_lookup_word(const String &p_symbol) { + if (lookup_symbol_word == p_symbol) { + return; + } + lookup_symbol_word = p_symbol; - update(); + queue_redraw(); } /* Text manipulation */ @@ -5882,14 +5991,14 @@ void TextEdit::_reset_caret_blink_timer() { if (has_focus()) { caret_blink_timer->stop(); caret_blink_timer->start(); - update(); + queue_redraw(); } } void TextEdit::_toggle_draw_caret() { draw_caret = !draw_caret; if (is_visible_in_tree() && has_focus() && window_has_focus) { - update(); + queue_redraw(); } } @@ -5951,7 +6060,7 @@ void TextEdit::_update_selection_mode_pointer() { set_caret_line(line, false); set_caret_column(col); - update(); + queue_redraw(); click_select_held->start(); } @@ -6003,7 +6112,7 @@ void TextEdit::_update_selection_mode_word() { DisplayServer::get_singleton()->clipboard_set_primary(get_selected_text()); } - update(); + queue_redraw(); click_select_held->start(); } @@ -6034,7 +6143,7 @@ void TextEdit::_update_selection_mode_line() { DisplayServer::get_singleton()->clipboard_set_primary(get_selected_text()); } - update(); + queue_redraw(); click_select_held->start(); } @@ -6060,7 +6169,7 @@ void TextEdit::_post_shift_selection() { if (selection.active && selection.selecting_mode == SelectionMode::SELECTION_MODE_SHIFT) { select(selection.selecting_line, selection.selecting_column, caret.line, caret.column); - update(); + queue_redraw(); } selection.selecting_text = true; @@ -6222,7 +6331,7 @@ void TextEdit::_scroll_moved(double p_to_val) { caret.line_ofs = n_line; caret.wrap_ofs = wi; } - update(); + queue_redraw(); } double TextEdit::_get_visible_lines_offset() const { @@ -6344,7 +6453,7 @@ void TextEdit::_update_minimap_hover() { if (hovering_minimap) { // Only redraw if the hovering status changed. hovering_minimap = false; - update(); + queue_redraw(); } // Return early to avoid running the operations below when not needed. @@ -6357,7 +6466,7 @@ void TextEdit::_update_minimap_hover() { if (new_hovering_minimap != hovering_minimap) { // Only redraw if the hovering status changed. hovering_minimap = new_hovering_minimap; - update(); + queue_redraw(); } } @@ -6419,7 +6528,7 @@ void TextEdit::_update_gutter_width() { if (gutters_width > 0) { gutter_padding = 2; } - update(); + queue_redraw(); } /* Syntax highlighting. */ diff --git a/scene/gui/text_edit.h b/scene/gui/text_edit.h index 6711cf8c7f..a8da878ede 100644 --- a/scene/gui/text_edit.h +++ b/scene/gui/text_edit.h @@ -195,6 +195,9 @@ private: void set(int p_line, const String &p_text, const Array &p_bidi_override); void set_hidden(int p_line, bool p_hidden) { + if (text[p_line].hidden == p_hidden) { + return; + } text.write[p_line].hidden = p_hidden; if (!p_hidden && text[p_line].width > max_width) { max_width = text[p_line].width; @@ -759,8 +762,8 @@ public: void set_caret_blink_enabled(const bool p_enabled); bool is_caret_blink_enabled() const; - void set_caret_blink_speed(const float p_speed); - float get_caret_blink_speed() const; + void set_caret_blink_interval(const float p_interval); + float get_caret_blink_interval() const; void set_move_caret_on_right_click_enabled(const bool p_enabled); bool is_move_caret_on_right_click_enabled() const; diff --git a/scene/gui/texture_button.cpp b/scene/gui/texture_button.cpp index 26acfaaa70..d9ab1c2c55 100644 --- a/scene/gui/texture_button.cpp +++ b/scene/gui/texture_button.cpp @@ -67,7 +67,7 @@ bool TextureButton::has_point(const Point2 &p_point) const { Rect2 rect = Rect2(); Size2 mask_size = click_mask->get_size(); - if (_position_rect.has_no_area()) { + if (!_position_rect.has_area()) { rect.size = mask_size; } else if (_tile) { // if the stretch mode is tile we offset the point to keep it inside the mask size @@ -112,7 +112,7 @@ bool TextureButton::has_point(const Point2 &p_point) const { } Point2i p = point; - return click_mask->get_bit(p); + return click_mask->get_bitv(p); } return Control::has_point(p_point); @@ -294,31 +294,50 @@ void TextureButton::_bind_methods() { } void TextureButton::set_normal_texture(const Ref<Texture2D> &p_normal) { + if (normal == p_normal) { + return; + } + normal = p_normal; - update(); + queue_redraw(); update_minimum_size(); } void TextureButton::set_pressed_texture(const Ref<Texture2D> &p_pressed) { + if (pressed == p_pressed) { + return; + } + pressed = p_pressed; - update(); + queue_redraw(); update_minimum_size(); } void TextureButton::set_hover_texture(const Ref<Texture2D> &p_hover) { + if (hover == p_hover) { + return; + } + hover = p_hover; - update(); + queue_redraw(); update_minimum_size(); } void TextureButton::set_disabled_texture(const Ref<Texture2D> &p_disabled) { + if (disabled == p_disabled) { + return; + } + disabled = p_disabled; - update(); + queue_redraw(); } void TextureButton::set_click_mask(const Ref<BitMap> &p_click_mask) { + if (click_mask == p_click_mask) { + return; + } click_mask = p_click_mask; - update(); + queue_redraw(); update_minimum_size(); } @@ -355,14 +374,22 @@ bool TextureButton::get_ignore_texture_size() const { } void TextureButton::set_ignore_texture_size(bool p_ignore) { + if (ignore_texture_size == p_ignore) { + return; + } + ignore_texture_size = p_ignore; update_minimum_size(); - update(); + queue_redraw(); } void TextureButton::set_stretch_mode(StretchMode p_stretch_mode) { + if (stretch_mode == p_stretch_mode) { + return; + } + stretch_mode = p_stretch_mode; - update(); + queue_redraw(); } TextureButton::StretchMode TextureButton::get_stretch_mode() const { @@ -370,8 +397,12 @@ TextureButton::StretchMode TextureButton::get_stretch_mode() const { } void TextureButton::set_flip_h(bool p_flip) { + if (hflip == p_flip) { + return; + } + hflip = p_flip; - update(); + queue_redraw(); } bool TextureButton::is_flipped_h() const { @@ -379,8 +410,12 @@ bool TextureButton::is_flipped_h() const { } void TextureButton::set_flip_v(bool p_flip) { + if (vflip == p_flip) { + return; + } + vflip = p_flip; - update(); + queue_redraw(); } bool TextureButton::is_flipped_v() const { diff --git a/scene/gui/texture_progress_bar.cpp b/scene/gui/texture_progress_bar.cpp index 94e0a6f226..a9982b3ece 100644 --- a/scene/gui/texture_progress_bar.cpp +++ b/scene/gui/texture_progress_bar.cpp @@ -33,8 +33,12 @@ #include "core/config/engine.h" void TextureProgressBar::set_under_texture(const Ref<Texture2D> &p_texture) { + if (under == p_texture) { + return; + } + under = p_texture; - update(); + queue_redraw(); update_minimum_size(); } @@ -43,8 +47,12 @@ Ref<Texture2D> TextureProgressBar::get_under_texture() const { } void TextureProgressBar::set_over_texture(const Ref<Texture2D> &p_texture) { + if (over == p_texture) { + return; + } + over = p_texture; - update(); + queue_redraw(); if (under.is_null()) { update_minimum_size(); } @@ -56,8 +64,13 @@ Ref<Texture2D> TextureProgressBar::get_over_texture() const { void TextureProgressBar::set_stretch_margin(Side p_side, int p_size) { ERR_FAIL_INDEX((int)p_side, 4); + + if (stretch_margin[p_side] == p_size) { + return; + } + stretch_margin[p_side] = p_size; - update(); + queue_redraw(); update_minimum_size(); } @@ -67,8 +80,12 @@ int TextureProgressBar::get_stretch_margin(Side p_side) const { } void TextureProgressBar::set_nine_patch_stretch(bool p_stretch) { + if (nine_patch_stretch == p_stretch) { + return; + } + nine_patch_stretch = p_stretch; - update(); + queue_redraw(); update_minimum_size(); } @@ -91,8 +108,12 @@ Size2 TextureProgressBar::get_minimum_size() const { } void TextureProgressBar::set_progress_texture(const Ref<Texture2D> &p_texture) { + if (progress == p_texture) { + return; + } + progress = p_texture; - update(); + queue_redraw(); update_minimum_size(); } @@ -101,8 +122,12 @@ Ref<Texture2D> TextureProgressBar::get_progress_texture() const { } void TextureProgressBar::set_progress_offset(Point2 p_offset) { + if (progress_offset == p_offset) { + return; + } + progress_offset = p_offset; - update(); + queue_redraw(); } Point2 TextureProgressBar::get_progress_offset() const { @@ -110,8 +135,12 @@ Point2 TextureProgressBar::get_progress_offset() const { } void TextureProgressBar::set_tint_under(const Color &p_tint) { + if (tint_under == p_tint) { + return; + } + tint_under = p_tint; - update(); + queue_redraw(); } Color TextureProgressBar::get_tint_under() const { @@ -119,8 +148,12 @@ Color TextureProgressBar::get_tint_under() const { } void TextureProgressBar::set_tint_progress(const Color &p_tint) { + if (tint_progress == p_tint) { + return; + } + tint_progress = p_tint; - update(); + queue_redraw(); } Color TextureProgressBar::get_tint_progress() const { @@ -128,8 +161,12 @@ Color TextureProgressBar::get_tint_progress() const { } void TextureProgressBar::set_tint_over(const Color &p_tint) { + if (tint_over == p_tint) { + return; + } + tint_over = p_tint; - update(); + queue_redraw(); } Color TextureProgressBar::get_tint_over() const { @@ -548,8 +585,13 @@ void TextureProgressBar::_notification(int p_what) { void TextureProgressBar::set_fill_mode(int p_fill) { ERR_FAIL_INDEX(p_fill, FILL_MODE_MAX); + + if (mode == (FillMode)p_fill) { + return; + } + mode = (FillMode)p_fill; - update(); + queue_redraw(); } int TextureProgressBar::get_fill_mode() { @@ -563,8 +605,13 @@ void TextureProgressBar::set_radial_initial_angle(float p_angle) { while (p_angle < 0) { p_angle += 360; } + + if (rad_init_angle == p_angle) { + return; + } + rad_init_angle = p_angle; - update(); + queue_redraw(); } float TextureProgressBar::get_radial_initial_angle() { @@ -572,8 +619,14 @@ float TextureProgressBar::get_radial_initial_angle() { } void TextureProgressBar::set_fill_degrees(float p_angle) { - rad_max_degrees = CLAMP(p_angle, 0, 360); - update(); + float angle_clamped = CLAMP(p_angle, 0, 360); + + if (rad_max_degrees == angle_clamped) { + return; + } + + rad_max_degrees = angle_clamped; + queue_redraw(); } float TextureProgressBar::get_fill_degrees() { @@ -581,8 +634,12 @@ float TextureProgressBar::get_fill_degrees() { } void TextureProgressBar::set_radial_center_offset(const Point2 &p_off) { + if (rad_center_off == p_off) { + return; + } + rad_center_off = p_off; - update(); + queue_redraw(); } Point2 TextureProgressBar::get_radial_center_offset() { diff --git a/scene/gui/texture_rect.cpp b/scene/gui/texture_rect.cpp index ecdf55caf0..459e67091d 100644 --- a/scene/gui/texture_rect.cpp +++ b/scene/gui/texture_rect.cpp @@ -94,7 +94,7 @@ void TextureRect::_notification(int p_what) { Ref<AtlasTexture> p_atlas = texture; - if (p_atlas.is_valid() && region.has_no_area()) { + if (p_atlas.is_valid() && !region.has_area()) { Size2 scale_size(size.width / texture->get_width(), size.height / texture->get_height()); offset.width += hflip ? p_atlas->get_margin().get_position().width * scale_size.width * 2 : 0; @@ -104,10 +104,10 @@ void TextureRect::_notification(int p_what) { size.width *= hflip ? -1.0f : 1.0f; size.height *= vflip ? -1.0f : 1.0f; - if (region.has_no_area()) { - draw_texture_rect(texture, Rect2(offset, size), tile); - } else { + if (region.has_area()) { draw_texture_rect_region(texture, Rect2(offset, size), region); + } else { + draw_texture_rect(texture, Rect2(offset, size), tile); } } break; } @@ -150,7 +150,7 @@ void TextureRect::_bind_methods() { void TextureRect::_texture_changed() { if (texture.is_valid()) { - update(); + queue_redraw(); update_minimum_size(); } } @@ -170,7 +170,7 @@ void TextureRect::set_texture(const Ref<Texture2D> &p_tex) { texture->connect(CoreStringNames::get_singleton()->changed, callable_mp(this, &TextureRect::_texture_changed)); } - update(); + queue_redraw(); update_minimum_size(); } @@ -179,8 +179,12 @@ Ref<Texture2D> TextureRect::get_texture() const { } void TextureRect::set_ignore_texture_size(bool p_ignore) { + if (ignore_texture_size == p_ignore) { + return; + } + ignore_texture_size = p_ignore; - update(); + queue_redraw(); update_minimum_size(); } @@ -189,8 +193,12 @@ bool TextureRect::get_ignore_texture_size() const { } void TextureRect::set_stretch_mode(StretchMode p_mode) { + if (stretch_mode == p_mode) { + return; + } + stretch_mode = p_mode; - update(); + queue_redraw(); } TextureRect::StretchMode TextureRect::get_stretch_mode() const { @@ -198,8 +206,12 @@ TextureRect::StretchMode TextureRect::get_stretch_mode() const { } void TextureRect::set_flip_h(bool p_flip) { + if (hflip == p_flip) { + return; + } + hflip = p_flip; - update(); + queue_redraw(); } bool TextureRect::is_flipped_h() const { @@ -207,8 +219,12 @@ bool TextureRect::is_flipped_h() const { } void TextureRect::set_flip_v(bool p_flip) { + if (vflip == p_flip) { + return; + } + vflip = p_flip; - update(); + queue_redraw(); } bool TextureRect::is_flipped_v() const { diff --git a/scene/gui/tree.cpp b/scene/gui/tree.cpp index 1eb6c5a554..237c78407b 100644 --- a/scene/gui/tree.cpp +++ b/scene/gui/tree.cpp @@ -126,13 +126,13 @@ void TreeItem::_change_tree(Tree *p_tree) { tree->pressing_for_editor = false; } - tree->update(); + tree->queue_redraw(); } tree = p_tree; if (tree) { - tree->update(); + tree->queue_redraw(); cells.resize(tree->columns.size()); } } @@ -141,6 +141,10 @@ void TreeItem::_change_tree(Tree *p_tree) { void TreeItem::set_cell_mode(int p_column, TreeCellMode p_mode) { ERR_FAIL_INDEX(p_column, cells.size()); + if (cells[p_column].mode == p_mode) { + return; + } + Cell &c = cells.write[p_column]; c.mode = p_mode; c.min = 0; @@ -166,6 +170,10 @@ TreeItem::TreeCellMode TreeItem::get_cell_mode(int p_column) const { void TreeItem::set_checked(int p_column, bool p_checked) { ERR_FAIL_INDEX(p_column, cells.size()); + if (cells[p_column].checked == p_checked) { + return; + } + cells.write[p_column].checked = p_checked; cells.write[p_column].indeterminate = false; cells.write[p_column].cached_minimum_size_dirty = true; @@ -259,6 +267,11 @@ void TreeItem::_propagate_check_through_parents(int p_column, bool p_emit_signal void TreeItem::set_text(int p_column, String p_text) { ERR_FAIL_INDEX(p_column, cells.size()); + + if (cells[p_column].text == p_text) { + return; + } + cells.write[p_column].text = p_text; cells.write[p_column].dirty = true; @@ -290,11 +303,14 @@ String TreeItem::get_text(int p_column) const { void TreeItem::set_text_direction(int p_column, Control::TextDirection p_text_direction) { ERR_FAIL_INDEX(p_column, cells.size()); ERR_FAIL_COND((int)p_text_direction < -1 || (int)p_text_direction > 3); - if (cells[p_column].text_direction != p_text_direction) { - cells.write[p_column].text_direction = p_text_direction; - cells.write[p_column].dirty = true; - _changed_notify(p_column); + + if (cells[p_column].text_direction == p_text_direction) { + return; } + + cells.write[p_column].text_direction = p_text_direction; + cells.write[p_column].dirty = true; + _changed_notify(p_column); cells.write[p_column].cached_minimum_size_dirty = true; } @@ -323,6 +339,10 @@ TextServer::StructuredTextParser TreeItem::get_structured_text_bidi_override(int void TreeItem::set_structured_text_bidi_override_options(int p_column, Array p_args) { ERR_FAIL_INDEX(p_column, cells.size()); + if (cells[p_column].st_args == p_args) { + return; + } + cells.write[p_column].st_args = p_args; cells.write[p_column].dirty = true; cells.write[p_column].cached_minimum_size_dirty = true; @@ -355,6 +375,10 @@ String TreeItem::get_language(int p_column) const { void TreeItem::set_suffix(int p_column, String p_suffix) { ERR_FAIL_INDEX(p_column, cells.size()); + if (cells[p_column].suffix == p_suffix) { + return; + } + cells.write[p_column].suffix = p_suffix; cells.write[p_column].cached_minimum_size_dirty = true; @@ -369,6 +393,10 @@ String TreeItem::get_suffix(int p_column) const { void TreeItem::set_icon(int p_column, const Ref<Texture2D> &p_icon) { ERR_FAIL_INDEX(p_column, cells.size()); + if (cells[p_column].icon == p_icon) { + return; + } + cells.write[p_column].icon = p_icon; cells.write[p_column].cached_minimum_size_dirty = true; @@ -383,6 +411,10 @@ Ref<Texture2D> TreeItem::get_icon(int p_column) const { void TreeItem::set_icon_region(int p_column, const Rect2 &p_icon_region) { ERR_FAIL_INDEX(p_column, cells.size()); + if (cells[p_column].icon_region == p_icon_region) { + return; + } + cells.write[p_column].icon_region = p_icon_region; cells.write[p_column].cached_minimum_size_dirty = true; @@ -396,6 +428,11 @@ Rect2 TreeItem::get_icon_region(int p_column) const { void TreeItem::set_icon_modulate(int p_column, const Color &p_modulate) { ERR_FAIL_INDEX(p_column, cells.size()); + + if (cells[p_column].icon_color == p_modulate) { + return; + } + cells.write[p_column].icon_color = p_modulate; _changed_notify(p_column); } @@ -408,6 +445,10 @@ Color TreeItem::get_icon_modulate(int p_column) const { void TreeItem::set_icon_max_width(int p_column, int p_max) { ERR_FAIL_INDEX(p_column, cells.size()); + if (cells[p_column].icon_max_w == p_max) { + return; + } + cells.write[p_column].icon_max_w = p_max; cells.write[p_column].cached_minimum_size_dirty = true; @@ -432,6 +473,10 @@ void TreeItem::set_range(int p_column, double p_value) { p_value = cells[p_column].max; } + if (cells[p_column].val == p_value) { + return; + } + cells.write[p_column].val = p_value; cells.write[p_column].dirty = true; _changed_notify(p_column); @@ -449,6 +494,11 @@ bool TreeItem::is_range_exponential(int p_column) const { void TreeItem::set_range_config(int p_column, double p_min, double p_max, double p_step, bool p_exp) { ERR_FAIL_INDEX(p_column, cells.size()); + + if (cells[p_column].min == p_min && cells[p_column].max == p_max && cells[p_column].step == p_step && cells[p_column].expr == p_exp) { + return; + } + cells.write[p_column].min = p_min; cells.write[p_column].max = p_max; cells.write[p_column].step = p_step; @@ -501,7 +551,7 @@ void TreeItem::set_collapsed(bool p_collapsed) { select(tree->selected_col); } - tree->update(); + tree->queue_redraw(); } } @@ -519,7 +569,7 @@ void TreeItem::set_visible(bool p_visible) { } visible = p_visible; if (tree) { - tree->update(); + tree->queue_redraw(); _changed_notify(); } } @@ -537,6 +587,10 @@ void TreeItem::uncollapse_tree() { } void TreeItem::set_custom_minimum_height(int p_height) { + if (custom_min_height == p_height) { + return; + } + custom_min_height = p_height; for (Cell &c : cells) { @@ -556,7 +610,7 @@ TreeItem *TreeItem::create_child(int p_idx) { TreeItem *ti = memnew(TreeItem(tree)); if (tree) { ti->cells.resize(tree->columns.size()); - tree->update(); + tree->queue_redraw(); } TreeItem *l_prev = nullptr; @@ -748,9 +802,10 @@ int TreeItem::get_child_count() { return children_cache.size(); } -Array TreeItem::get_children() { +TypedArray<TreeItem> TreeItem::get_children() { + // Don't need to explicitly create children cache, because get_child_count creates it. int size = get_child_count(); - Array arr; + TypedArray<TreeItem> arr; arr.resize(size); for (int i = 0; i < size; i++) { arr[i] = children_cache[i]; @@ -770,6 +825,22 @@ int TreeItem::get_index() { return idx - 1; } +#ifdef DEV_ENABLED +void TreeItem::validate_cache() const { + if (!parent || parent->children_cache.is_empty()) { + return; + } + TreeItem *scan = parent->first_child; + int index = 0; + while (scan) { + DEV_ASSERT(parent->children_cache[index] == scan); + ++index; + scan = scan->get_next(); + } + DEV_ASSERT(index == parent->children_cache.size()); +} +#endif + void TreeItem::move_before(TreeItem *p_item) { ERR_FAIL_NULL(p_item); ERR_FAIL_COND(is_root); @@ -797,7 +868,11 @@ void TreeItem::move_before(TreeItem *p_item) { parent->children_cache.clear(); } else { parent->first_child = this; - parent->children_cache.insert(0, this); + // If the cache is empty, it has not been built but there + // are items in the tree (note p_item != nullptr,) so we cannot update it. + if (!parent->children_cache.is_empty()) { + parent->children_cache.insert(0, this); + } } prev = item_prev; @@ -805,8 +880,10 @@ void TreeItem::move_before(TreeItem *p_item) { p_item->prev = this; if (tree && old_tree == tree) { - tree->update(); + tree->queue_redraw(); } + + validate_cache(); } void TreeItem::move_after(TreeItem *p_item) { @@ -839,12 +916,17 @@ void TreeItem::move_after(TreeItem *p_item) { if (next) { parent->children_cache.clear(); } else { - parent->children_cache.append(this); + // If the cache is empty, it has not been built but there + // are items in the tree (note p_item != nullptr,) so we cannot update it. + if (!parent->children_cache.is_empty()) { + parent->children_cache.append(this); + } } if (tree && old_tree == tree) { - tree->update(); + tree->queue_redraw(); } + validate_cache(); } void TreeItem::remove_child(TreeItem *p_item) { @@ -857,8 +939,9 @@ void TreeItem::remove_child(TreeItem *p_item) { p_item->parent = nullptr; if (tree) { - tree->update(); + tree->queue_redraw(); } + validate_cache(); } void TreeItem::set_selectable(int p_column, bool p_selectable) { @@ -884,9 +967,12 @@ void TreeItem::set_as_cursor(int p_column) { if (tree->select_mode != Tree::SELECT_MULTI) { return; } + if (tree->selected_col == p_column) { + return; + } tree->selected_item = this; tree->selected_col = p_column; - tree->update(); + tree->queue_redraw(); } void TreeItem::select(int p_column) { @@ -927,7 +1013,7 @@ Ref<Texture2D> TreeItem::get_button(int p_column, int p_idx) const { return cells[p_column].buttons[p_idx].texture; } -String TreeItem::get_button_tooltip(int p_column, int p_idx) const { +String TreeItem::get_button_tooltip_text(int p_column, int p_idx) const { ERR_FAIL_INDEX_V(p_column, cells.size(), String()); ERR_FAIL_INDEX_V(p_idx, cells[p_column].buttons.size(), String()); return cells[p_column].buttons[p_idx].tooltip; @@ -961,6 +1047,11 @@ void TreeItem::set_button(int p_column, int p_idx, const Ref<Texture2D> &p_butto ERR_FAIL_COND(p_button.is_null()); ERR_FAIL_INDEX(p_column, cells.size()); ERR_FAIL_INDEX(p_idx, cells[p_column].buttons.size()); + + if (cells[p_column].buttons[p_idx].texture == p_button) { + return; + } + cells.write[p_column].buttons.write[p_idx].texture = p_button; cells.write[p_column].cached_minimum_size_dirty = true; @@ -970,6 +1061,11 @@ void TreeItem::set_button(int p_column, int p_idx, const Ref<Texture2D> &p_butto void TreeItem::set_button_color(int p_column, int p_idx, const Color &p_color) { ERR_FAIL_INDEX(p_column, cells.size()); ERR_FAIL_INDEX(p_idx, cells[p_column].buttons.size()); + + if (cells[p_column].buttons[p_idx].color == p_color) { + return; + } + cells.write[p_column].buttons.write[p_idx].color = p_color; _changed_notify(p_column); } @@ -978,6 +1074,10 @@ void TreeItem::set_button_disabled(int p_column, int p_idx, bool p_disabled) { ERR_FAIL_INDEX(p_column, cells.size()); ERR_FAIL_INDEX(p_idx, cells[p_column].buttons.size()); + if (cells[p_column].buttons[p_idx].disabled == p_disabled) { + return; + } + cells.write[p_column].buttons.write[p_idx].disabled = p_disabled; cells.write[p_column].cached_minimum_size_dirty = true; @@ -994,6 +1094,10 @@ bool TreeItem::is_button_disabled(int p_column, int p_idx) const { void TreeItem::set_editable(int p_column, bool p_editable) { ERR_FAIL_INDEX(p_column, cells.size()); + if (cells[p_column].editable == p_editable) { + return; + } + cells.write[p_column].editable = p_editable; cells.write[p_column].cached_minimum_size_dirty = true; @@ -1007,6 +1111,11 @@ bool TreeItem::is_editable(int p_column) { void TreeItem::set_custom_color(int p_column, const Color &p_color) { ERR_FAIL_INDEX(p_column, cells.size()); + + if (cells[p_column].custom_color == true) { + return; + } + cells.write[p_column].custom_color = true; cells.write[p_column].color = p_color; _changed_notify(p_column); @@ -1051,18 +1160,23 @@ int TreeItem::get_custom_font_size(int p_column) const { return cells[p_column].custom_font_size; } -void TreeItem::set_tooltip(int p_column, const String &p_tooltip) { +void TreeItem::set_tooltip_text(int p_column, const String &p_tooltip) { ERR_FAIL_INDEX(p_column, cells.size()); cells.write[p_column].tooltip = p_tooltip; } -String TreeItem::get_tooltip(int p_column) const { +String TreeItem::get_tooltip_text(int p_column) const { ERR_FAIL_INDEX_V(p_column, cells.size(), ""); return cells[p_column].tooltip; } void TreeItem::set_custom_bg_color(int p_column, const Color &p_color, bool p_bg_outline) { ERR_FAIL_INDEX(p_column, cells.size()); + + if (cells[p_column].custom_bg_color && cells[p_column].custom_bg_outline == p_bg_outline && cells[p_column].bg_color == p_color) { + return; + } + cells.write[p_column].custom_bg_color = true; cells.write[p_column].custom_bg_outline = p_bg_outline; cells.write[p_column].bg_color = p_color; @@ -1099,6 +1213,10 @@ bool TreeItem::is_custom_set_as_button(int p_column) const { void TreeItem::set_text_alignment(int p_column, HorizontalAlignment p_alignment) { ERR_FAIL_INDEX(p_column, cells.size()); + if (cells[p_column].text_alignment == p_alignment) { + return; + } + cells.write[p_column].text_alignment = p_alignment; cells.write[p_column].cached_minimum_size_dirty = true; @@ -1113,6 +1231,10 @@ HorizontalAlignment TreeItem::get_text_alignment(int p_column) const { void TreeItem::set_expand_right(int p_column, bool p_enable) { ERR_FAIL_INDEX(p_column, cells.size()); + if (cells[p_column].expand_right == p_enable) { + return; + } + cells.write[p_column].expand_right = p_enable; cells.write[p_column].cached_minimum_size_dirty = true; @@ -1125,6 +1247,10 @@ bool TreeItem::get_expand_right(int p_column) const { } void TreeItem::set_disable_folding(bool p_disable) { + if (disable_folding == p_disable) { + return; + } + disable_folding = p_disable; for (Cell &c : cells) { @@ -1160,14 +1286,14 @@ Size2 TreeItem::get_minimum_size(int p_column) { // Icon. if (cell.mode == CELL_MODE_CHECK) { - size.width += tree->cache.checked->get_width() + tree->cache.hseparation; + size.width += tree->theme_cache.checked->get_width() + tree->theme_cache.hseparation; } if (cell.icon.is_valid()) { Size2i icon_size = cell.get_icon_size(); if (cell.icon_max_w > 0 && icon_size.width > cell.icon_max_w) { icon_size.width = cell.icon_max_w; } - size.width += icon_size.width + tree->cache.hseparation; + size.width += icon_size.width + tree->theme_cache.hseparation; size.height = MAX(size.height, icon_size.height); } @@ -1175,13 +1301,13 @@ Size2 TreeItem::get_minimum_size(int p_column) { for (int i = 0; i < cell.buttons.size(); i++) { Ref<Texture2D> texture = cell.buttons[i].texture; if (texture.is_valid()) { - Size2 button_size = texture->get_size() + tree->cache.button_pressed->get_minimum_size(); + Size2 button_size = texture->get_size() + tree->theme_cache.button_pressed->get_minimum_size(); size.width += button_size.width; size.height = MAX(size.height, button_size.height); } } if (cell.buttons.size() >= 2) { - size.width += (cell.buttons.size() - 1) * tree->cache.button_margin; + size.width += (cell.buttons.size() - 1) * tree->theme_cache.button_margin; } cells.write[p_column].cached_minimum_size = size; @@ -1315,9 +1441,9 @@ void TreeItem::_bind_methods() { ClassDB::bind_method(D_METHOD("set_custom_as_button", "column", "enable"), &TreeItem::set_custom_as_button); ClassDB::bind_method(D_METHOD("is_custom_set_as_button", "column"), &TreeItem::is_custom_set_as_button); - ClassDB::bind_method(D_METHOD("add_button", "column", "button", "id", "disabled", "tooltip"), &TreeItem::add_button, DEFVAL(-1), DEFVAL(false), DEFVAL("")); + ClassDB::bind_method(D_METHOD("add_button", "column", "button", "id", "disabled", "tooltip_text"), &TreeItem::add_button, DEFVAL(-1), DEFVAL(false), DEFVAL("")); ClassDB::bind_method(D_METHOD("get_button_count", "column"), &TreeItem::get_button_count); - ClassDB::bind_method(D_METHOD("get_button_tooltip", "column", "button_idx"), &TreeItem::get_button_tooltip); + ClassDB::bind_method(D_METHOD("get_button_tooltip_text", "column", "button_idx"), &TreeItem::get_button_tooltip_text); ClassDB::bind_method(D_METHOD("get_button_id", "column", "button_idx"), &TreeItem::get_button_id); ClassDB::bind_method(D_METHOD("get_button_by_id", "column", "id"), &TreeItem::get_button_by_id); ClassDB::bind_method(D_METHOD("get_button", "column", "button_idx"), &TreeItem::get_button); @@ -1326,8 +1452,8 @@ void TreeItem::_bind_methods() { ClassDB::bind_method(D_METHOD("set_button_disabled", "column", "button_idx", "disabled"), &TreeItem::set_button_disabled); ClassDB::bind_method(D_METHOD("is_button_disabled", "column", "button_idx"), &TreeItem::is_button_disabled); - ClassDB::bind_method(D_METHOD("set_tooltip", "column", "tooltip"), &TreeItem::set_tooltip); - ClassDB::bind_method(D_METHOD("get_tooltip", "column"), &TreeItem::get_tooltip); + ClassDB::bind_method(D_METHOD("set_tooltip_text", "column", "tooltip"), &TreeItem::set_tooltip_text); + ClassDB::bind_method(D_METHOD("get_tooltip_text", "column"), &TreeItem::get_tooltip_text); ClassDB::bind_method(D_METHOD("set_text_alignment", "column", "text_alignment"), &TreeItem::set_text_alignment); ClassDB::bind_method(D_METHOD("get_text_alignment", "column"), &TreeItem::get_text_alignment); @@ -1396,6 +1522,7 @@ TreeItem::TreeItem(Tree *p_tree) { TreeItem::~TreeItem() { _unlink_from_tree(); + validate_cache(); prev = nullptr; clear_children(); _change_tree(nullptr); @@ -1408,68 +1535,68 @@ TreeItem::~TreeItem() { /**********************************************/ /**********************************************/ -void Tree::update_cache() { - cache.font = get_theme_font(SNAME("font")); - cache.font_size = get_theme_font_size(SNAME("font_size")); - cache.tb_font = get_theme_font(SNAME("title_button_font")); - cache.tb_font_size = get_theme_font_size(SNAME("title_button_font_size")); - cache.bg = get_theme_stylebox(SNAME("bg")); - cache.selected = get_theme_stylebox(SNAME("selected")); - cache.selected_focus = get_theme_stylebox(SNAME("selected_focus")); - cache.cursor = get_theme_stylebox(SNAME("cursor")); - cache.cursor_unfocus = get_theme_stylebox(SNAME("cursor_unfocused")); - cache.button_pressed = get_theme_stylebox(SNAME("button_pressed")); - - cache.checked = get_theme_icon(SNAME("checked")); - cache.unchecked = get_theme_icon(SNAME("unchecked")); - cache.indeterminate = get_theme_icon(SNAME("indeterminate")); - if (is_layout_rtl()) { - cache.arrow_collapsed = get_theme_icon(SNAME("arrow_collapsed_mirrored")); - } else { - cache.arrow_collapsed = get_theme_icon(SNAME("arrow_collapsed")); - } - cache.arrow = get_theme_icon(SNAME("arrow")); - cache.select_arrow = get_theme_icon(SNAME("select_arrow")); - cache.updown = get_theme_icon(SNAME("updown")); - - cache.custom_button = get_theme_stylebox(SNAME("custom_button")); - cache.custom_button_hover = get_theme_stylebox(SNAME("custom_button_hover")); - cache.custom_button_pressed = get_theme_stylebox(SNAME("custom_button_pressed")); - cache.custom_button_font_highlight = get_theme_color(SNAME("custom_button_font_highlight")); - - cache.font_color = get_theme_color(SNAME("font_color")); - cache.font_selected_color = get_theme_color(SNAME("font_selected_color")); - cache.drop_position_color = get_theme_color(SNAME("drop_position_color")); - cache.hseparation = get_theme_constant(SNAME("h_separation")); - cache.vseparation = get_theme_constant(SNAME("v_separation")); - cache.item_margin = get_theme_constant(SNAME("item_margin")); - cache.button_margin = get_theme_constant(SNAME("button_margin")); - - cache.font_outline_color = get_theme_color(SNAME("font_outline_color")); - cache.font_outline_size = get_theme_constant(SNAME("outline_size")); - - cache.draw_guides = get_theme_constant(SNAME("draw_guides")); - cache.guide_color = get_theme_color(SNAME("guide_color")); - cache.draw_relationship_lines = get_theme_constant(SNAME("draw_relationship_lines")); - cache.relationship_line_width = get_theme_constant(SNAME("relationship_line_width")); - cache.parent_hl_line_width = get_theme_constant(SNAME("parent_hl_line_width")); - cache.children_hl_line_width = get_theme_constant(SNAME("children_hl_line_width")); - cache.parent_hl_line_margin = get_theme_constant(SNAME("parent_hl_line_margin")); - cache.relationship_line_color = get_theme_color(SNAME("relationship_line_color")); - cache.parent_hl_line_color = get_theme_color(SNAME("parent_hl_line_color")); - cache.children_hl_line_color = get_theme_color(SNAME("children_hl_line_color")); - - cache.scroll_border = get_theme_constant(SNAME("scroll_border")); - cache.scroll_speed = get_theme_constant(SNAME("scroll_speed")); - - cache.title_button = get_theme_stylebox(SNAME("title_button_normal")); - cache.title_button_pressed = get_theme_stylebox(SNAME("title_button_pressed")); - cache.title_button_hover = get_theme_stylebox(SNAME("title_button_hover")); - cache.title_button_color = get_theme_color(SNAME("title_button_color")); - - cache.base_scale = get_theme_default_base_scale(); - - v_scroll->set_custom_step(cache.font->get_height(cache.font_size)); +void Tree::_update_theme_item_cache() { + Control::_update_theme_item_cache(); + + theme_cache.panel_style = get_theme_stylebox(SNAME("panel")); + theme_cache.focus_style = get_theme_stylebox(SNAME("focus")); + + theme_cache.font = get_theme_font(SNAME("font")); + theme_cache.font_size = get_theme_font_size(SNAME("font_size")); + theme_cache.tb_font = get_theme_font(SNAME("title_button_font")); + theme_cache.tb_font_size = get_theme_font_size(SNAME("title_button_font_size")); + + theme_cache.selected = get_theme_stylebox(SNAME("selected")); + theme_cache.selected_focus = get_theme_stylebox(SNAME("selected_focus")); + theme_cache.cursor = get_theme_stylebox(SNAME("cursor")); + theme_cache.cursor_unfocus = get_theme_stylebox(SNAME("cursor_unfocused")); + theme_cache.button_pressed = get_theme_stylebox(SNAME("button_pressed")); + + theme_cache.checked = get_theme_icon(SNAME("checked")); + theme_cache.unchecked = get_theme_icon(SNAME("unchecked")); + theme_cache.indeterminate = get_theme_icon(SNAME("indeterminate")); + theme_cache.arrow = get_theme_icon(SNAME("arrow")); + theme_cache.arrow_collapsed = get_theme_icon(SNAME("arrow_collapsed")); + theme_cache.arrow_collapsed_mirrored = get_theme_icon(SNAME("arrow_collapsed_mirrored")); + theme_cache.select_arrow = get_theme_icon(SNAME("select_arrow")); + theme_cache.updown = get_theme_icon(SNAME("updown")); + + theme_cache.custom_button = get_theme_stylebox(SNAME("custom_button")); + theme_cache.custom_button_hover = get_theme_stylebox(SNAME("custom_button_hover")); + theme_cache.custom_button_pressed = get_theme_stylebox(SNAME("custom_button_pressed")); + theme_cache.custom_button_font_highlight = get_theme_color(SNAME("custom_button_font_highlight")); + + theme_cache.font_color = get_theme_color(SNAME("font_color")); + theme_cache.font_selected_color = get_theme_color(SNAME("font_selected_color")); + theme_cache.drop_position_color = get_theme_color(SNAME("drop_position_color")); + theme_cache.hseparation = get_theme_constant(SNAME("h_separation")); + theme_cache.vseparation = get_theme_constant(SNAME("v_separation")); + theme_cache.item_margin = get_theme_constant(SNAME("item_margin")); + theme_cache.button_margin = get_theme_constant(SNAME("button_margin")); + + theme_cache.font_outline_color = get_theme_color(SNAME("font_outline_color")); + theme_cache.font_outline_size = get_theme_constant(SNAME("outline_size")); + + theme_cache.draw_guides = get_theme_constant(SNAME("draw_guides")); + theme_cache.guide_color = get_theme_color(SNAME("guide_color")); + theme_cache.draw_relationship_lines = get_theme_constant(SNAME("draw_relationship_lines")); + theme_cache.relationship_line_width = get_theme_constant(SNAME("relationship_line_width")); + theme_cache.parent_hl_line_width = get_theme_constant(SNAME("parent_hl_line_width")); + theme_cache.children_hl_line_width = get_theme_constant(SNAME("children_hl_line_width")); + theme_cache.parent_hl_line_margin = get_theme_constant(SNAME("parent_hl_line_margin")); + theme_cache.relationship_line_color = get_theme_color(SNAME("relationship_line_color")); + theme_cache.parent_hl_line_color = get_theme_color(SNAME("parent_hl_line_color")); + theme_cache.children_hl_line_color = get_theme_color(SNAME("children_hl_line_color")); + + theme_cache.scroll_border = get_theme_constant(SNAME("scroll_border")); + theme_cache.scroll_speed = get_theme_constant(SNAME("scroll_speed")); + + theme_cache.title_button = get_theme_stylebox(SNAME("title_button_normal")); + theme_cache.title_button_pressed = get_theme_stylebox(SNAME("title_button_pressed")); + theme_cache.title_button_hover = get_theme_stylebox(SNAME("title_button_hover")); + theme_cache.title_button_color = get_theme_color(SNAME("title_button_color")); + + theme_cache.base_scale = get_theme_default_base_scale(); } int Tree::compute_item_height(TreeItem *p_item) const { @@ -1477,7 +1604,7 @@ int Tree::compute_item_height(TreeItem *p_item) const { return 0; } - ERR_FAIL_COND_V(cache.font.is_null(), 0); + ERR_FAIL_COND_V(theme_cache.font.is_null(), 0); int height = 0; for (int i = 0; i < columns.size(); i++) { @@ -1495,7 +1622,7 @@ int Tree::compute_item_height(TreeItem *p_item) const { switch (p_item->cells[i].mode) { case TreeItem::CELL_MODE_CHECK: { - int check_icon_h = cache.checked->get_height(); + int check_icon_h = theme_cache.checked->get_height(); if (height < check_icon_h) { height = check_icon_h; } @@ -1515,7 +1642,7 @@ int Tree::compute_item_height(TreeItem *p_item) const { } } if (p_item->cells[i].mode == TreeItem::CELL_MODE_CUSTOM && p_item->cells[i].custom_button) { - height += cache.custom_button->get_minimum_size().height; + height += theme_cache.custom_button->get_minimum_size().height; } } break; @@ -1528,7 +1655,7 @@ int Tree::compute_item_height(TreeItem *p_item) const { height = item_min_height; } - height += cache.vseparation; + height += theme_cache.vseparation; return height; } @@ -1538,7 +1665,7 @@ int Tree::get_item_height(TreeItem *p_item) const { return 0; } int height = compute_item_height(p_item); - height += cache.vseparation; + height += theme_cache.vseparation; if (!p_item->collapsed) { /* if not collapsed, check the children */ @@ -1555,7 +1682,7 @@ int Tree::get_item_height(TreeItem *p_item) const { } void Tree::draw_item_rect(TreeItem::Cell &p_cell, const Rect2i &p_rect, const Color &p_color, const Color &p_icon_color, int p_ol_size, const Color &p_ol_color) { - ERR_FAIL_COND(cache.font.is_null()); + ERR_FAIL_COND(theme_cache.font.is_null()); Rect2i rect = p_rect; Size2 ts = p_cell.text_buf->get_size(); @@ -1567,7 +1694,7 @@ void Tree::draw_item_rect(TreeItem::Cell &p_cell, const Rect2i &p_rect, const Co if (p_cell.icon_max_w > 0 && bmsize.width > p_cell.icon_max_w) { bmsize.width = p_cell.icon_max_w; } - w += bmsize.width + cache.hseparation; + w += bmsize.width + theme_cache.hseparation; if (rect.size.width > 0 && (w + ts.width) > rect.size.width) { ts.width = rect.size.width - w; } @@ -1601,8 +1728,8 @@ void Tree::draw_item_rect(TreeItem::Cell &p_cell, const Rect2i &p_rect, const Co p_cell.text_buf->draw_outline(ci, draw_pos, p_ol_size, p_ol_color); } p_cell.text_buf->draw(ci, draw_pos, p_color); - rect.position.x += ts.width + cache.hseparation; - rect.size.x -= ts.width + cache.hseparation; + rect.position.x += ts.width + theme_cache.hseparation; + rect.size.x -= ts.width + theme_cache.hseparation; } if (!p_cell.icon.is_null()) { @@ -1614,8 +1741,8 @@ void Tree::draw_item_rect(TreeItem::Cell &p_cell, const Rect2i &p_rect, const Co } p_cell.draw_icon(ci, rect.position + Size2i(0, Math::floor((real_t)(rect.size.y - bmsize.y) / 2)), bmsize, p_icon_color); - rect.position.x += bmsize.x + cache.hseparation; - rect.size.x -= bmsize.x + cache.hseparation; + rect.position.x += bmsize.x + theme_cache.hseparation; + rect.size.x -= bmsize.x + theme_cache.hseparation; } if (!rtl) { @@ -1637,7 +1764,7 @@ void Tree::update_column(int p_col) { columns.write[p_col].text_buf->set_direction((TextServer::Direction)columns[p_col].text_direction); } - columns.write[p_col].text_buf->add_string(columns[p_col].title, cache.font, cache.font_size, columns[p_col].language); + columns.write[p_col].text_buf->add_string(columns[p_col].title, theme_cache.font, theme_cache.font_size, columns[p_col].language); } void Tree::update_item_cell(TreeItem *p_item, int p_col) { @@ -1686,14 +1813,14 @@ void Tree::update_item_cell(TreeItem *p_item, int p_col) { if (p_item->cells[p_col].custom_font.is_valid()) { font = p_item->cells[p_col].custom_font; } else { - font = cache.font; + font = theme_cache.font; } int font_size; if (p_item->cells[p_col].custom_font_size > 0) { font_size = p_item->cells[p_col].custom_font_size; } else { - font_size = cache.font_size; + font_size = theme_cache.font_size; } p_item->cells.write[p_col].text_buf->add_string(valtext, font, font_size, p_item->cells[p_col].language); TS->shaped_text_set_bidi_override(p_item->cells[p_col].text_buf->get_rid(), structured_text_parser(p_item->cells[p_col].st_parser, p_item->cells[p_col].st_args, valtext)); @@ -1713,7 +1840,7 @@ void Tree::update_item_cache(TreeItem *p_item) { } int Tree::draw_item(const Point2i &p_pos, const Point2 &p_draw_ofs, const Size2 &p_draw_size, TreeItem *p_item) { - if (p_pos.y - cache.offset.y > (p_draw_size.height)) { + if (p_pos.y - theme_cache.offset.y > (p_draw_size.height)) { return -1; //draw no more! } @@ -1729,18 +1856,18 @@ int Tree::draw_item(const Point2i &p_pos, const Point2 &p_draw_ofs, const Size2 bool rtl = cache.rtl; /* Calculate height of the label part */ - label_h += cache.vseparation; + label_h += theme_cache.vseparation; /* Draw label, if height fits */ bool skip = (p_item == root && hide_root); - if (!skip && (p_pos.y + label_h - cache.offset.y) > 0) { + if (!skip && (p_pos.y + label_h - theme_cache.offset.y) > 0) { // Draw separation. - ERR_FAIL_COND_V(cache.font.is_null(), -1); + ERR_FAIL_COND_V(theme_cache.font.is_null(), -1); - int ofs = p_pos.x + ((p_item->disable_folding || hide_folding) ? cache.hseparation : cache.item_margin); + int ofs = p_pos.x + ((p_item->disable_folding || hide_folding) ? theme_cache.hseparation : theme_cache.item_margin); int skip2 = 0; for (int i = 0; i < columns.size(); i++) { if (skip2) { @@ -1758,8 +1885,8 @@ int Tree::draw_item(const Point2i &p_pos, const Point2 &p_draw_ofs, const Size2 continue; } } else { - ofs += cache.hseparation; - w -= cache.hseparation; + ofs += theme_cache.hseparation; + w -= theme_cache.hseparation; } if (p_item->cells[i].expand_right) { @@ -1775,10 +1902,10 @@ int Tree::draw_item(const Point2i &p_pos, const Point2 &p_draw_ofs, const Size2 int button_w = 0; for (int j = p_item->cells[i].buttons.size() - 1; j >= 0; j--) { Ref<Texture2D> b = p_item->cells[i].buttons[j].texture; - button_w += b->get_size().width + cache.button_pressed->get_minimum_size().width + cache.button_margin; + button_w += b->get_size().width + theme_cache.button_pressed->get_minimum_size().width + theme_cache.button_margin; } - int total_ofs = ofs - cache.offset.x; + int total_ofs = ofs - theme_cache.offset.x; if (total_ofs + w > p_draw_size.width) { w = MAX(button_w, p_draw_size.width - total_ofs); @@ -1788,9 +1915,9 @@ int Tree::draw_item(const Point2i &p_pos, const Point2 &p_draw_ofs, const Size2 int bw = 0; for (int j = p_item->cells[i].buttons.size() - 1; j >= 0; j--) { Ref<Texture2D> b = p_item->cells[i].buttons[j].texture; - Size2 s = b->get_size() + cache.button_pressed->get_minimum_size(); + Size2 s = b->get_size() + theme_cache.button_pressed->get_minimum_size(); - Point2i o = Point2i(ofs + w - s.width, p_pos.y) - cache.offset + p_draw_ofs; + Point2i o = Point2i(ofs + w - s.width, p_pos.y) - theme_cache.offset + p_draw_ofs; if (cache.click_type == Cache::CLICK_BUTTON && cache.click_item == p_item && cache.click_column == i && cache.click_index == j && !p_item->cells[i].buttons[j].disabled) { // Being pressed. @@ -1798,48 +1925,48 @@ int Tree::draw_item(const Point2i &p_pos, const Point2 &p_draw_ofs, const Size2 if (rtl) { od.x = get_size().width - od.x - s.x; } - cache.button_pressed->draw(get_canvas_item(), Rect2(od.x, od.y, s.width, MAX(s.height, label_h))); + theme_cache.button_pressed->draw(get_canvas_item(), Rect2(od.x, od.y, s.width, MAX(s.height, label_h))); } o.y += (label_h - s.height) / 2; - o += cache.button_pressed->get_offset(); + o += theme_cache.button_pressed->get_offset(); if (rtl) { o.x = get_size().width - o.x - b->get_width(); } b->draw(ci, o, p_item->cells[i].buttons[j].disabled ? Color(1, 1, 1, 0.5) : p_item->cells[i].buttons[j].color); - w -= s.width + cache.button_margin; - bw += s.width + cache.button_margin; + w -= s.width + theme_cache.button_margin; + bw += s.width + theme_cache.button_margin; } - Rect2i item_rect = Rect2i(Point2i(ofs, p_pos.y) - cache.offset + p_draw_ofs, Size2i(w, label_h)); + Rect2i item_rect = Rect2i(Point2i(ofs, p_pos.y) - theme_cache.offset + p_draw_ofs, Size2i(w, label_h)); Rect2i cell_rect = item_rect; if (i != 0) { - cell_rect.position.x -= cache.hseparation; - cell_rect.size.x += cache.hseparation; + cell_rect.position.x -= theme_cache.hseparation; + cell_rect.size.x += theme_cache.hseparation; } - if (cache.draw_guides) { + if (theme_cache.draw_guides) { Rect2 r = cell_rect; if (rtl) { r.position.x = get_size().width - r.position.x - r.size.x; } - RenderingServer::get_singleton()->canvas_item_add_line(ci, Point2i(r.position.x, r.position.y + r.size.height), r.position + r.size, cache.guide_color, 1); + RenderingServer::get_singleton()->canvas_item_add_line(ci, Point2i(r.position.x, r.position.y + r.size.height), r.position + r.size, theme_cache.guide_color, 1); } if (i == 0) { if (p_item->cells[0].selected && select_mode == SELECT_ROW) { - Rect2i row_rect = Rect2i(Point2i(cache.bg->get_margin(SIDE_LEFT), item_rect.position.y), Size2i(get_size().width - cache.bg->get_minimum_size().width, item_rect.size.y)); + Rect2i row_rect = Rect2i(Point2i(theme_cache.panel_style->get_margin(SIDE_LEFT), item_rect.position.y), Size2i(get_size().width - theme_cache.panel_style->get_minimum_size().width, item_rect.size.y)); //Rect2 r = Rect2i(row_rect.pos,row_rect.size); //r.grow(cache.selected->get_margin(SIDE_LEFT)); if (rtl) { row_rect.position.x = get_size().width - row_rect.position.x - row_rect.size.x; } if (has_focus()) { - cache.selected_focus->draw(ci, row_rect); + theme_cache.selected_focus->draw(ci, row_rect); } else { - cache.selected->draw(ci, row_rect); + theme_cache.selected->draw(ci, row_rect); } } } @@ -1855,9 +1982,9 @@ int Tree::draw_item(const Point2i &p_pos, const Point2 &p_draw_ofs, const Size2 } if (p_item->cells[i].selected) { if (has_focus()) { - cache.selected_focus->draw(ci, r); + theme_cache.selected_focus->draw(ci, r); } else { - cache.selected->draw(ci, r); + theme_cache.selected->draw(ci, r); } } } @@ -1869,8 +1996,8 @@ int Tree::draw_item(const Point2i &p_pos, const Point2 &p_draw_ofs, const Size2 r.position.x = p_draw_ofs.x; r.size.x = w + ofs; } else { - r.position.x -= cache.hseparation; - r.size.x += cache.hseparation; + r.position.x -= theme_cache.hseparation; + r.size.x += theme_cache.hseparation; } if (rtl) { r.position.x = get_size().width - r.position.x - r.size.x; @@ -1893,28 +2020,34 @@ int Tree::draw_item(const Point2i &p_pos, const Point2 &p_draw_ofs, const Size2 if (drop_mode_over == p_item) { if (drop_mode_section == 0 || drop_mode_section == -1) { // Line above. - RenderingServer::get_singleton()->canvas_item_add_rect(ci, Rect2(r.position.x, r.position.y, r.size.x, 1), cache.drop_position_color); + RenderingServer::get_singleton()->canvas_item_add_rect(ci, Rect2(r.position.x, r.position.y, r.size.x, 1), theme_cache.drop_position_color); } if (drop_mode_section == 0) { // Side lines. - RenderingServer::get_singleton()->canvas_item_add_rect(ci, Rect2(r.position.x, r.position.y, 1, r.size.y), cache.drop_position_color); - RenderingServer::get_singleton()->canvas_item_add_rect(ci, Rect2(r.position.x + r.size.x - 1, r.position.y, 1, r.size.y), cache.drop_position_color); + RenderingServer::get_singleton()->canvas_item_add_rect(ci, Rect2(r.position.x, r.position.y, 1, r.size.y), theme_cache.drop_position_color); + RenderingServer::get_singleton()->canvas_item_add_rect(ci, Rect2(r.position.x + r.size.x - 1, r.position.y, 1, r.size.y), theme_cache.drop_position_color); } if (drop_mode_section == 0 || (drop_mode_section == 1 && (!p_item->get_first_child() || p_item->is_collapsed()))) { // Line below. - RenderingServer::get_singleton()->canvas_item_add_rect(ci, Rect2(r.position.x, r.position.y + r.size.y, r.size.x, 1), cache.drop_position_color); + RenderingServer::get_singleton()->canvas_item_add_rect(ci, Rect2(r.position.x, r.position.y + r.size.y, r.size.x, 1), theme_cache.drop_position_color); } } else if (drop_mode_over == p_item->get_parent()) { if (drop_mode_section == 1 && !p_item->get_prev() /* && !drop_mode_over->is_collapsed() */) { // The drop_mode_over shouldn't ever be collapsed in here, otherwise we would be drawing a child of a collapsed item. // Line above. - RenderingServer::get_singleton()->canvas_item_add_rect(ci, Rect2(r.position.x, r.position.y, r.size.x, 1), cache.drop_position_color); + RenderingServer::get_singleton()->canvas_item_add_rect(ci, Rect2(r.position.x, r.position.y, r.size.x, 1), theme_cache.drop_position_color); } } } - Color col = p_item->cells[i].custom_color ? p_item->cells[i].color : get_theme_color(p_item->cells[i].selected ? "font_selected_color" : "font_color"); - Color font_outline_color = cache.font_outline_color; - int outline_size = cache.font_outline_size; + Color col; + if (p_item->cells[i].custom_color) { + col = p_item->cells[i].color; + } else { + col = p_item->cells[i].selected ? theme_cache.font_selected_color : theme_cache.font_color; + } + + Color font_outline_color = theme_cache.font_outline_color; + int outline_size = theme_cache.font_outline_size; Color icon_col = p_item->cells[i].icon_color; if (p_item->cells[i].dirty) { @@ -1934,9 +2067,9 @@ int Tree::draw_item(const Point2i &p_pos, const Point2 &p_draw_ofs, const Size2 draw_item_rect(p_item->cells.write[i], item_rect, col, icon_col, outline_size, font_outline_color); } break; case TreeItem::CELL_MODE_CHECK: { - Ref<Texture2D> checked = cache.checked; - Ref<Texture2D> unchecked = cache.unchecked; - Ref<Texture2D> indeterminate = cache.indeterminate; + Ref<Texture2D> checked = theme_cache.checked; + Ref<Texture2D> unchecked = theme_cache.unchecked; + Ref<Texture2D> indeterminate = theme_cache.indeterminate; Point2i check_ofs = item_rect.position; check_ofs.y += Math::floor((real_t)(item_rect.size.y - checked->get_height()) / 2); @@ -1948,7 +2081,7 @@ int Tree::draw_item(const Point2i &p_pos, const Point2 &p_draw_ofs, const Size2 unchecked->draw(ci, check_ofs); } - int check_w = checked->get_width() + cache.hseparation; + int check_w = checked->get_width() + theme_cache.hseparation; text_pos.x += check_w; @@ -1964,7 +2097,7 @@ int Tree::draw_item(const Point2i &p_pos, const Point2 &p_draw_ofs, const Size2 break; } - Ref<Texture2D> downarrow = cache.select_arrow; + Ref<Texture2D> downarrow = theme_cache.select_arrow; int cell_width = item_rect.size.x - downarrow->get_width(); p_item->cells.write[i].text_buf->set_width(cell_width); @@ -1986,7 +2119,7 @@ int Tree::draw_item(const Point2i &p_pos, const Point2 &p_draw_ofs, const Size2 downarrow->draw(ci, arrow_pos); } else { - Ref<Texture2D> updown = cache.updown; + Ref<Texture2D> updown = theme_cache.updown; int cell_width = item_rect.size.x - updown->get_width(); @@ -2043,7 +2176,7 @@ int Tree::draw_item(const Point2i &p_pos, const Point2 &p_draw_ofs, const Size2 break; } - Ref<Texture2D> downarrow = cache.select_arrow; + Ref<Texture2D> downarrow = theme_cache.select_arrow; Rect2i ir = item_rect; @@ -2055,16 +2188,16 @@ int Tree::draw_item(const Point2i &p_pos, const Point2 &p_draw_ofs, const Size2 if (p_item->cells[i].custom_button) { if (cache.hover_item == p_item && cache.hover_cell == i) { if (Input::get_singleton()->is_mouse_button_pressed(MouseButton::LEFT)) { - draw_style_box(cache.custom_button_pressed, ir); + draw_style_box(theme_cache.custom_button_pressed, ir); } else { - draw_style_box(cache.custom_button_hover, ir); - col = cache.custom_button_font_highlight; + draw_style_box(theme_cache.custom_button_hover, ir); + col = theme_cache.custom_button_font_highlight; } } else { - draw_style_box(cache.custom_button, ir); + draw_style_box(theme_cache.custom_button, ir); } - ir.size -= cache.custom_button->get_minimum_size(); - ir.position += cache.custom_button->get_offset(); + ir.size -= theme_cache.custom_button->get_minimum_size(); + ir.position += theme_cache.custom_button->get_offset(); } draw_item_rect(p_item->cells.write[i], ir, col, icon_col, outline_size, font_outline_color); @@ -2085,9 +2218,9 @@ int Tree::draw_item(const Point2i &p_pos, const Point2 &p_draw_ofs, const Size2 cell_rect.position.x = get_size().width - cell_rect.position.x - cell_rect.size.x; } if (has_focus()) { - cache.cursor->draw(ci, cell_rect); + theme_cache.cursor->draw(ci, cell_rect); } else { - cache.cursor_unfocus->draw(ci, cell_rect); + theme_cache.cursor_unfocus->draw(ci, cell_rect); } } } @@ -2097,13 +2230,17 @@ int Tree::draw_item(const Point2i &p_pos, const Point2 &p_draw_ofs, const Size2 Ref<Texture2D> arrow; if (p_item->collapsed) { - arrow = cache.arrow_collapsed; + if (is_layout_rtl()) { + arrow = theme_cache.arrow_collapsed_mirrored; + } else { + arrow = theme_cache.arrow_collapsed; + } } else { - arrow = cache.arrow; + arrow = theme_cache.arrow; } - Point2 apos = p_pos + Point2i(0, (label_h - arrow->get_height()) / 2) - cache.offset + p_draw_ofs; - apos.x += cache.item_margin - arrow->get_width(); + Point2 apos = p_pos + Point2i(0, (label_h - arrow->get_height()) / 2) - theme_cache.offset + p_draw_ofs; + apos.x += theme_cache.item_margin - arrow->get_width(); if (rtl) { apos.x = get_size().width - apos.x - arrow->get_width(); @@ -2116,7 +2253,7 @@ int Tree::draw_item(const Point2i &p_pos, const Point2 &p_draw_ofs, const Size2 Point2 children_pos = p_pos; if (!skip) { - children_pos.x += cache.item_margin; + children_pos.x += theme_cache.item_margin; htotal += label_h; children_pos.y += htotal; } @@ -2124,7 +2261,7 @@ int Tree::draw_item(const Point2i &p_pos, const Point2 &p_draw_ofs, const Size2 if (!p_item->collapsed) { /* if not collapsed, check the children */ TreeItem *c = p_item->first_child; - int base_ofs = children_pos.y - cache.offset.y + p_draw_ofs.y; + int base_ofs = children_pos.y - theme_cache.offset.y + p_draw_ofs.y; int prev_ofs = base_ofs; int prev_hl_ofs = base_ofs; @@ -2135,20 +2272,20 @@ 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) && 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 (theme_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) ? theme_cache.hseparation : theme_cache.item_margin); + int parent_ofs = p_pos.x + theme_cache.item_margin; + Point2i root_pos = Point2i(root_ofs, children_pos.y + label_h / 2) - theme_cache.offset + p_draw_ofs; if (c->get_visible_child_count() > 0) { - root_pos -= Point2i(cache.arrow->get_width(), 0); + root_pos -= Point2i(theme_cache.arrow->get_width(), 0); } - float line_width = cache.relationship_line_width * Math::round(cache.base_scale); - float parent_line_width = cache.parent_hl_line_width * Math::round(cache.base_scale); - float children_line_width = cache.children_hl_line_width * Math::round(cache.base_scale); + float line_width = theme_cache.relationship_line_width * Math::round(theme_cache.base_scale); + float parent_line_width = theme_cache.parent_hl_line_width * Math::round(theme_cache.base_scale); + float children_line_width = theme_cache.children_hl_line_width * Math::round(theme_cache.base_scale); - Point2i parent_pos = Point2i(parent_ofs - cache.arrow->get_width() / 2, p_pos.y + label_h / 2 + cache.arrow->get_height() / 2) - cache.offset + p_draw_ofs; + Point2i parent_pos = Point2i(parent_ofs - theme_cache.arrow->get_width() / 2, p_pos.y + label_h / 2 + theme_cache.arrow->get_height() / 2) - theme_cache.offset + p_draw_ofs; int more_prev_ofs = 0; @@ -2162,43 +2299,43 @@ int Tree::draw_item(const Point2i &p_pos, const Point2 &p_draw_ofs, const Size2 if (_is_branch_selected(c)) { // If this item or one of its children is selected, we draw the line using parent highlight style. if (htotal >= 0) { - RenderingServer::get_singleton()->canvas_item_add_line(ci, root_pos, Point2i(parent_pos.x + Math::floor(parent_line_width / 2), root_pos.y), cache.parent_hl_line_color, parent_line_width); + RenderingServer::get_singleton()->canvas_item_add_line(ci, root_pos, Point2i(parent_pos.x + Math::floor(parent_line_width / 2), root_pos.y), theme_cache.parent_hl_line_color, parent_line_width); } - RenderingServer::get_singleton()->canvas_item_add_line(ci, Point2i(parent_pos.x, root_pos.y + Math::floor(parent_line_width / 2)), Point2i(parent_pos.x, prev_hl_ofs), cache.parent_hl_line_color, parent_line_width); + RenderingServer::get_singleton()->canvas_item_add_line(ci, Point2i(parent_pos.x, root_pos.y + Math::floor(parent_line_width / 2)), Point2i(parent_pos.x, prev_hl_ofs), theme_cache.parent_hl_line_color, parent_line_width); - more_prev_ofs = cache.parent_hl_line_margin; + more_prev_ofs = theme_cache.parent_hl_line_margin; prev_hl_ofs = root_pos.y + Math::floor(parent_line_width / 2); } else if (p_item->is_selected(0)) { // If parent item is selected (but this item is not), we draw the line using children highlight style. // Siblings of the selected branch can be drawn with a slight offset and their vertical line must appear as highlighted. if (_is_sibling_branch_selected(c)) { if (htotal >= 0) { - RenderingServer::get_singleton()->canvas_item_add_line(ci, root_pos, Point2i(parent_pos.x + Math::floor(parent_line_width / 2), root_pos.y), cache.children_hl_line_color, children_line_width); + RenderingServer::get_singleton()->canvas_item_add_line(ci, root_pos, Point2i(parent_pos.x + Math::floor(parent_line_width / 2), root_pos.y), theme_cache.children_hl_line_color, children_line_width); } - RenderingServer::get_singleton()->canvas_item_add_line(ci, Point2i(parent_pos.x, root_pos.y + Math::floor(parent_line_width / 2)), Point2i(parent_pos.x, prev_hl_ofs), cache.parent_hl_line_color, parent_line_width); + RenderingServer::get_singleton()->canvas_item_add_line(ci, Point2i(parent_pos.x, root_pos.y + Math::floor(parent_line_width / 2)), Point2i(parent_pos.x, prev_hl_ofs), theme_cache.parent_hl_line_color, parent_line_width); prev_hl_ofs = root_pos.y + Math::floor(parent_line_width / 2); } else { if (htotal >= 0) { - RenderingServer::get_singleton()->canvas_item_add_line(ci, root_pos, Point2i(parent_pos.x + Math::floor(children_line_width / 2), root_pos.y), cache.children_hl_line_color, children_line_width); + RenderingServer::get_singleton()->canvas_item_add_line(ci, root_pos, Point2i(parent_pos.x + Math::floor(children_line_width / 2), root_pos.y), theme_cache.children_hl_line_color, children_line_width); } - RenderingServer::get_singleton()->canvas_item_add_line(ci, Point2i(parent_pos.x, root_pos.y + Math::floor(children_line_width / 2)), Point2i(parent_pos.x, prev_ofs + Math::floor(children_line_width / 2)), cache.children_hl_line_color, children_line_width); + RenderingServer::get_singleton()->canvas_item_add_line(ci, Point2i(parent_pos.x, root_pos.y + Math::floor(children_line_width / 2)), Point2i(parent_pos.x, prev_ofs + Math::floor(children_line_width / 2)), theme_cache.children_hl_line_color, children_line_width); } } else { // If nothing of the above is true, we draw the line using normal style. // Siblings of the selected branch can be drawn with a slight offset and their vertical line must appear as highlighted. if (_is_sibling_branch_selected(c)) { if (htotal >= 0) { - RenderingServer::get_singleton()->canvas_item_add_line(ci, root_pos, Point2i(parent_pos.x + cache.parent_hl_line_margin, root_pos.y), cache.relationship_line_color, line_width); + RenderingServer::get_singleton()->canvas_item_add_line(ci, root_pos, Point2i(parent_pos.x + theme_cache.parent_hl_line_margin, root_pos.y), theme_cache.relationship_line_color, line_width); } - RenderingServer::get_singleton()->canvas_item_add_line(ci, Point2i(parent_pos.x, root_pos.y + Math::floor(parent_line_width / 2)), Point2i(parent_pos.x, prev_hl_ofs), cache.parent_hl_line_color, parent_line_width); + RenderingServer::get_singleton()->canvas_item_add_line(ci, Point2i(parent_pos.x, root_pos.y + Math::floor(parent_line_width / 2)), Point2i(parent_pos.x, prev_hl_ofs), theme_cache.parent_hl_line_color, parent_line_width); prev_hl_ofs = root_pos.y + Math::floor(parent_line_width / 2); } else { if (htotal >= 0) { - RenderingServer::get_singleton()->canvas_item_add_line(ci, root_pos, Point2i(parent_pos.x + Math::floor(line_width / 2), root_pos.y), cache.relationship_line_color, line_width); + RenderingServer::get_singleton()->canvas_item_add_line(ci, root_pos, Point2i(parent_pos.x + Math::floor(line_width / 2), root_pos.y), theme_cache.relationship_line_color, line_width); } - RenderingServer::get_singleton()->canvas_item_add_line(ci, Point2i(parent_pos.x, root_pos.y + Math::floor(line_width / 2)), Point2i(parent_pos.x, prev_ofs + Math::floor(line_width / 2)), cache.relationship_line_color, line_width); + RenderingServer::get_singleton()->canvas_item_add_line(ci, Point2i(parent_pos.x, root_pos.y + Math::floor(line_width / 2)), Point2i(parent_pos.x, prev_ofs + Math::floor(line_width / 2)), theme_cache.relationship_line_color, line_width); } } } @@ -2211,12 +2348,12 @@ int Tree::draw_item(const Point2i &p_pos, const Point2 &p_draw_ofs, const Size2 break; // Last loop done, stop. } - if (cache.draw_relationship_lines == 0) { + if (theme_cache.draw_relationship_lines == 0) { return -1; // No need to draw anymore, full stop. } htotal = -1; - children_pos.y = cache.offset.y + p_draw_size.height; + children_pos.y = theme_cache.offset.y + p_draw_size.height; } else { htotal += child_h; children_pos.y += child_h; @@ -2367,7 +2504,7 @@ Rect2 Tree::search_item_rect(TreeItem *p_from, TreeItem *p_item) { void Tree::_range_click_timeout() { if (range_item_last && !range_drag_enabled && Input::get_singleton()->is_mouse_button_pressed(MouseButton::LEFT)) { - Point2 pos = get_local_mouse_position() - cache.bg->get_offset(); + Point2 pos = get_local_mouse_position() - theme_cache.panel_style->get_offset(); if (show_column_titles) { pos.y -= _get_title_button_height(); @@ -2385,7 +2522,7 @@ void Tree::_range_click_timeout() { Ref<InputEventMouseButton> mb; mb.instantiate(); - int x_limit = get_size().width - cache.bg->get_minimum_size().width; + int x_limit = get_size().width - theme_cache.panel_style->get_minimum_size().width; if (h_scroll->is_visible()) { x_limit -= h_scroll->get_minimum_size().width; } @@ -2394,7 +2531,7 @@ void Tree::_range_click_timeout() { propagate_mouse_activated = false; // done from outside, so signal handler can't clear the tree in the middle of emit (which is a common case) blocked++; - propagate_mouse_event(pos + cache.offset, 0, 0, x_limit + cache.offset.width, false, root, MouseButton::LEFT, mb); + propagate_mouse_event(pos + theme_cache.offset, 0, 0, x_limit + theme_cache.offset.width, false, root, MouseButton::LEFT, mb); blocked--; if (range_click_timer->is_one_shot()) { @@ -2423,7 +2560,7 @@ int Tree::propagate_mouse_event(const Point2i &p_pos, int x_ofs, int y_ofs, int return 0; } - int item_h = compute_item_height(p_item) + cache.vseparation; + int item_h = compute_item_height(p_item) + theme_cache.vseparation; bool skip = (p_item == root && hide_root); @@ -2434,7 +2571,7 @@ int Tree::propagate_mouse_event(const Point2i &p_pos, int x_ofs, int y_ofs, int return -1; } - if (!p_item->disable_folding && !hide_folding && p_item->first_child && (p_pos.x >= x_ofs && p_pos.x < (x_ofs + cache.item_margin))) { + if (!p_item->disable_folding && !hide_folding && p_item->first_child && (p_pos.x >= x_ofs && p_pos.x < (x_ofs + theme_cache.item_margin))) { p_item->set_collapsed(!p_item->is_collapsed()); return -1; } @@ -2453,7 +2590,7 @@ int Tree::propagate_mouse_event(const Point2i &p_pos, int x_ofs, int y_ofs, int if (p_item->cells[i].expand_right) { int plus = 1; while (i + plus < columns.size() && !p_item->cells[i + plus].editable && p_item->cells[i + plus].mode == TreeItem::CELL_MODE_STRING && p_item->cells[i + plus].text.is_empty() && p_item->cells[i + plus].icon.is_null()) { - col_width += cache.hseparation; + col_width += theme_cache.hseparation; col_width += get_column_width(i + plus); plus++; } @@ -2473,16 +2610,16 @@ int Tree::propagate_mouse_event(const Point2i &p_pos, int x_ofs, int y_ofs, int if (col == -1) { return -1; } else if (col == 0) { - int margin = x_ofs + cache.item_margin; //-cache.hseparation; - //int lm = cache.bg->get_margin(SIDE_LEFT); + int margin = x_ofs + theme_cache.item_margin; //-theme_cache.hseparation; + //int lm = theme_cache.panel_style->get_margin(SIDE_LEFT); col_width -= margin; limit_w -= margin; col_ofs += margin; x -= margin; } else { - col_width -= cache.hseparation; - limit_w -= cache.hseparation; - x -= cache.hseparation; + col_width -= theme_cache.hseparation; + limit_w -= theme_cache.hseparation; + x -= theme_cache.hseparation; } if (!p_item->disable_folding && !hide_folding && !p_item->cells[col].editable && !p_item->cells[col].selectable && p_item->get_first_child()) { @@ -2499,7 +2636,7 @@ int Tree::propagate_mouse_event(const Point2i &p_pos, int x_ofs, int y_ofs, int int button_w = 0; for (int j = p_item->cells[col].buttons.size() - 1; j >= 0; j--) { Ref<Texture2D> b = p_item->cells[col].buttons[j].texture; - button_w += b->get_size().width + cache.button_pressed->get_minimum_size().width + cache.button_margin; + button_w += b->get_size().width + theme_cache.button_pressed->get_minimum_size().width + theme_cache.button_margin; } col_width = MAX(button_w, MIN(limit_w, col_width)); @@ -2507,7 +2644,7 @@ int Tree::propagate_mouse_event(const Point2i &p_pos, int x_ofs, int y_ofs, int for (int j = c.buttons.size() - 1; j >= 0; j--) { Ref<Texture2D> b = c.buttons[j].texture; - int w = b->get_size().width + cache.button_pressed->get_minimum_size().width; + int w = b->get_size().width + theme_cache.button_pressed->get_minimum_size().width; if (x > col_width - w) { if (c.buttons[j].disabled) { @@ -2531,11 +2668,11 @@ int Tree::propagate_mouse_event(const Point2i &p_pos, int x_ofs, int y_ofs, int cache.click_item = p_item; cache.click_column = col; cache.click_pos = click_pos; - update(); + queue_redraw(); return -1; } - col_width -= w + cache.button_margin; + col_width -= w + theme_cache.button_margin; } if (p_button == MouseButton::LEFT || (p_button == MouseButton::RIGHT && allow_rmb_select)) { @@ -2549,7 +2686,7 @@ int Tree::propagate_mouse_event(const Point2i &p_pos, int x_ofs, int y_ofs, int return -1; } - if (select_mode == SELECT_MULTI && p_mod->is_command_pressed() && c.selectable) { + if (select_mode == SELECT_MULTI && p_mod->is_command_or_control_pressed() && c.selectable) { if (!c.selected || p_button == MouseButton::RIGHT) { p_item->select(col); emit_signal(SNAME("multi_selected"), p_item, col, true); @@ -2589,7 +2726,7 @@ int Tree::propagate_mouse_event(const Point2i &p_pos, int x_ofs, int y_ofs, int emit_signal(SNAME("multi_selected"),p_item,col,true); } */ - update(); + queue_redraw(); } } } @@ -2615,7 +2752,7 @@ int Tree::propagate_mouse_event(const Point2i &p_pos, int x_ofs, int y_ofs, int case TreeItem::CELL_MODE_CHECK: { bring_up_editor = false; //checkboxes are not edited with editor if (force_edit_checkbox_only_on_checkbox) { - if (x < cache.checked->get_width()) { + if (x < theme_cache.checked->get_width()) { p_item->set_checked(col, !c.checked); item_edited(col, p_item, p_button); } @@ -2637,7 +2774,7 @@ int Tree::propagate_mouse_event(const Point2i &p_pos, int x_ofs, int y_ofs, int } popup_menu->set_size(Size2(col_width, 0)); - popup_menu->set_position(get_screen_position() + Point2i(col_ofs, _get_title_button_height() + y_ofs + item_h) - cache.offset); + popup_menu->set_position(get_screen_position() + Point2i(col_ofs, _get_title_button_height() + y_ofs + item_h) - theme_cache.offset); popup_menu->popup(); popup_edited_item = p_item; popup_edited_item_col = col; @@ -2695,9 +2832,9 @@ int Tree::propagate_mouse_event(const Point2i &p_pos, int x_ofs, int y_ofs, int case TreeItem::CELL_MODE_CUSTOM: { edited_item = p_item; edited_col = col; - bool on_arrow = x > col_width - cache.select_arrow->get_width(); + bool on_arrow = x > col_width - theme_cache.select_arrow->get_width(); - custom_popup_rect = Rect2i(get_global_position() + Point2i(col_ofs, _get_title_button_height() + y_ofs + item_h - cache.offset.y), Size2(get_column_width(col), item_h)); + custom_popup_rect = Rect2i(get_global_position() + Point2i(col_ofs, _get_title_button_height() + y_ofs + item_h - theme_cache.offset.y), Size2(get_column_width(col), item_h)); if (on_arrow || !p_item->cells[col].custom_button) { emit_signal(SNAME("custom_popup_edited"), ((bool)(x >= (col_width - item_h / 2)))); @@ -2719,7 +2856,7 @@ int Tree::propagate_mouse_event(const Point2i &p_pos, int x_ofs, int y_ofs, int popup_pressing_edited_item = p_item; popup_pressing_edited_item_column = col; - pressing_item_rect = Rect2(get_global_position() + Point2i(col_ofs, _get_title_button_height() + y_ofs) - cache.offset, Size2(col_width, item_h)); + pressing_item_rect = Rect2(get_global_position() + Point2i(col_ofs, _get_title_button_height() + y_ofs) - theme_cache.offset, Size2(col_width, item_h)); pressing_for_editor_text = editor_text; pressing_for_editor = true; @@ -2728,8 +2865,8 @@ int Tree::propagate_mouse_event(const Point2i &p_pos, int x_ofs, int y_ofs, int Point2i new_pos = p_pos; if (!skip) { - x_ofs += cache.item_margin; - //new_pos.x-=cache.item_margin; + x_ofs += theme_cache.item_margin; + //new_pos.x-=theme_cache.item_margin; y_ofs += item_h; new_pos.y -= item_h; } @@ -2808,7 +2945,7 @@ void Tree::_text_editor_submit(String p_text) { } item_edited(popup_edited_item_col, popup_edited_item); - update(); + queue_redraw(); } void Tree::value_editor_changed(double p_value) { @@ -2825,7 +2962,7 @@ void Tree::value_editor_changed(double p_value) { text_editor->set_text(String::num(c.val, Math::range_step_decimals(c.step))); item_edited(popup_edited_item_col, popup_edited_item); - update(); + queue_redraw(); } void Tree::popup_select(int p_option) { @@ -2839,7 +2976,7 @@ void Tree::popup_select(int p_option) { popup_edited_item->cells.write[popup_edited_item_col].val = p_option; //popup_edited_item->edited_signal.call( popup_edited_item_col ); - update(); + queue_redraw(); item_edited(popup_edited_item_col, popup_edited_item); } @@ -2866,7 +3003,7 @@ void Tree::_go_left() { selected_item->select(selected_col - 1); } } - update(); + queue_redraw(); accept_event(); ensure_cursor_is_visible(); } @@ -2887,7 +3024,7 @@ void Tree::_go_right() { selected_item->select(selected_col + 1); } } - update(); + queue_redraw(); ensure_cursor_is_visible(); accept_event(); } @@ -2916,7 +3053,7 @@ void Tree::_go_up() { } selected_item = prev; emit_signal(SNAME("cell_selected")); - update(); + queue_redraw(); } else { int col = selected_col < 0 ? 0 : selected_col; while (prev && !prev->cells[col].selectable) { @@ -2959,7 +3096,7 @@ void Tree::_go_down() { selected_item = next; emit_signal(SNAME("cell_selected")); - update(); + queue_redraw(); } else { int col = selected_col < 0 ? 0 : selected_col; @@ -2990,7 +3127,7 @@ void Tree::gui_input(const Ref<InputEvent> &p_event) { Ref<InputEventKey> k = p_event; - bool is_command = k.is_valid() && k->is_command_pressed(); + bool is_command = k.is_valid() && k->is_command_or_control_pressed(); if (p_event->is_action("ui_right") && p_event->is_pressed()) { if (!cursor_can_exit_tree) { accept_event(); @@ -3069,7 +3206,7 @@ void Tree::gui_input(const Ref<InputEvent> &p_event) { if (select_mode == SELECT_MULTI) { selected_item = next; emit_signal(SNAME("cell_selected")); - update(); + queue_redraw(); } else { while (next && !next->cells[selected_col].selectable) { next = next->get_next_visible(); @@ -3107,7 +3244,7 @@ void Tree::gui_input(const Ref<InputEvent> &p_event) { if (select_mode == SELECT_MULTI) { selected_item = prev; emit_signal(SNAME("cell_selected")); - update(); + queue_redraw(); } else { while (prev && !prev->cells[selected_col].selectable) { prev = prev->get_prev_visible(); @@ -3148,7 +3285,7 @@ void Tree::gui_input(const Ref<InputEvent> &p_event) { if (!k->is_pressed()) { return; } - if (k->is_command_pressed() || (k->is_shift_pressed() && k->get_unicode() == 0) || k->is_meta_pressed()) { + if (k->is_command_or_control_pressed() || (k->is_shift_pressed() && k->get_unicode() == 0) || k->is_meta_pressed()) { return; } if (!root) { @@ -3173,18 +3310,14 @@ 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(); - } - - Ref<StyleBox> bg = cache.bg; + Ref<StyleBox> bg = theme_cache.panel_style; bool rtl = is_layout_rtl(); Point2 pos = mm->get_position(); if (rtl) { pos.x = get_size().width - pos.x; } - pos -= cache.bg->get_offset(); + pos -= theme_cache.panel_style->get_offset(); Cache::ClickType old_hover = cache.hover_type; int old_index = cache.hover_index; @@ -3194,7 +3327,7 @@ void Tree::gui_input(const Ref<InputEvent> &p_event) { if (show_column_titles) { pos.y -= _get_title_button_height(); if (pos.y < 0) { - pos.x += cache.offset.x; + pos.x += theme_cache.offset.x; int len = 0; for (int i = 0; i < columns.size(); i++) { len += get_column_width(i); @@ -3212,7 +3345,7 @@ void Tree::gui_input(const Ref<InputEvent> &p_event) { if (rtl) { mpos.x = get_size().width - mpos.x; } - mpos -= cache.bg->get_offset(); + mpos -= theme_cache.panel_style->get_offset(); mpos.y -= _get_title_button_height(); if (mpos.y >= 0) { if (h_scroll->is_visible_in_tree()) { @@ -3231,11 +3364,11 @@ void Tree::gui_input(const Ref<InputEvent> &p_event) { if (drop_mode_flags) { if (it != drop_mode_over) { drop_mode_over = it; - update(); + queue_redraw(); } if (it && section != drop_mode_section) { drop_mode_section = section; - update(); + queue_redraw(); } } @@ -3244,14 +3377,14 @@ void Tree::gui_input(const Ref<InputEvent> &p_event) { if (it != old_it || col != old_col) { if (old_it && old_col >= old_it->cells.size()) { - // Columns may have changed since last update(). - update(); + // Columns may have changed since last redraw(). + queue_redraw(); } else { // Only need to update if mouse enters/exits a button bool was_over_button = old_it && old_it->cells[old_col].custom_button; bool is_over_button = it && it->cells[col].custom_button; if (was_over_button || is_over_button) { - update(); + queue_redraw(); } } } @@ -3260,7 +3393,7 @@ void Tree::gui_input(const Ref<InputEvent> &p_event) { // Update if mouse enters/exits columns if (cache.hover_type != old_hover || cache.hover_index != old_index) { - update(); + queue_redraw(); } if (pressing_for_editor && popup_pressing_edited_item && (popup_pressing_edited_item->get_cell_mode(popup_pressing_edited_item_column) == TreeItem::CELL_MODE_RANGE)) { @@ -3303,35 +3436,34 @@ void Tree::gui_input(const Ref<InputEvent> &p_event) { 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 (!mb->is_pressed()) { - if (mb->get_button_index() == MouseButton::LEFT) { + if (mb->get_button_index() == MouseButton::LEFT || + mb->get_button_index() == MouseButton::RIGHT) { Point2 pos = mb->get_position(); if (rtl) { pos.x = get_size().width - pos.x; } - pos -= cache.bg->get_offset(); + pos -= theme_cache.panel_style->get_offset(); if (show_column_titles) { pos.y -= _get_title_button_height(); if (pos.y < 0) { - pos.x += cache.offset.x; + pos.x += theme_cache.offset.x; int len = 0; for (int i = 0; i < columns.size(); i++) { len += get_column_width(i); - if (pos.x < len) { - emit_signal(SNAME("column_title_pressed"), i); + if (pos.x < static_cast<real_t>(len)) { + emit_signal(SNAME("column_title_clicked"), i, mb->get_button_index()); break; } } } } + } + if (mb->get_button_index() == MouseButton::LEFT) { if (single_select_defer) { select_single_item(single_select_defer, root, single_select_defer_column); single_select_defer = nullptr; @@ -3396,7 +3528,7 @@ void Tree::gui_input(const Ref<InputEvent> &p_event) { cache.click_id = -1; cache.click_item = nullptr; cache.click_column = 0; - update(); + queue_redraw(); return; } @@ -3407,7 +3539,7 @@ void Tree::gui_input(const Ref<InputEvent> &p_event) { switch (mb->get_button_index()) { case MouseButton::RIGHT: case MouseButton::LEFT: { - Ref<StyleBox> bg = cache.bg; + Ref<StyleBox> bg = theme_cache.panel_style; Point2 pos = mb->get_position(); if (rtl) { @@ -3419,18 +3551,15 @@ void Tree::gui_input(const Ref<InputEvent> &p_event) { pos.y -= _get_title_button_height(); if (pos.y < 0) { - if (mb->get_button_index() == MouseButton::LEFT) { - pos.x += cache.offset.x; - int len = 0; - for (int i = 0; i < columns.size(); i++) { - len += get_column_width(i); - if (pos.x < len) { - cache.click_type = Cache::CLICK_TITLE; - cache.click_index = i; - //cache.click_id=; - update(); - break; - } + pos.x += theme_cache.offset.x; + int len = 0; + for (int i = 0; i < columns.size(); i++) { + len += get_column_width(i); + if (pos.x < static_cast<real_t>(len)) { + cache.click_type = Cache::CLICK_TITLE; + cache.click_index = i; + queue_redraw(); + break; } } break; @@ -3445,14 +3574,14 @@ void Tree::gui_input(const Ref<InputEvent> &p_event) { pressing_for_editor = false; propagate_mouse_activated = false; - int x_limit = get_size().width - cache.bg->get_minimum_size().width; + int x_limit = get_size().width - theme_cache.panel_style->get_minimum_size().width; if (h_scroll->is_visible()) { x_limit -= h_scroll->get_minimum_size().width; } cache.rtl = is_layout_rtl(); blocked++; - propagate_mouse_event(pos + cache.offset, 0, 0, x_limit + cache.offset.width, mb->is_double_click(), root, mb->get_button_index(), mb); + propagate_mouse_event(pos + theme_cache.offset, 0, 0, x_limit + theme_cache.offset.width, mb->is_double_click(), root, mb->get_button_index(), mb); blocked--; if (pressing_for_editor) { @@ -3486,7 +3615,7 @@ void Tree::gui_input(const Ref<InputEvent> &p_event) { } 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()) { + if (get_item_at_position(mb->get_position()) == nullptr && !mb->is_shift_pressed() && !mb->is_ctrl_pressed() && !mb->is_command_or_control_pressed()) { emit_signal(SNAME("nothing_selected")); } } @@ -3591,12 +3720,17 @@ bool Tree::edit_selected() { } else if (c.mode == TreeItem::CELL_MODE_STRING || c.mode == TreeItem::CELL_MODE_RANGE) { Rect2 popup_rect; - Vector2 ofs(0, (text_editor->get_size().height - rect.size.height) / 2); + Vector2 ofs(0, Math::floor((text_editor->get_size().height - rect.size.height) / 2)); // "floor()" centers vertically. Point2i textedpos = get_screen_position() + rect.position - ofs; cache.text_editor_position = textedpos; popup_rect.position = textedpos; popup_rect.size = rect.size; + + // Account for icon. + popup_rect.position.x += c.get_icon_size().x; + popup_rect.size.x -= c.get_icon_size().x; + text_editor->clear(); text_editor->set_text(c.mode == TreeItem::CELL_MODE_STRING ? c.text : String::num(c.val, Math::range_step_decimals(c.step))); text_editor->select_all(); @@ -3634,7 +3768,7 @@ bool Tree::is_editing() { } Size2 Tree::get_internal_min_size() const { - Size2i size = cache.bg->get_offset(); + Size2i size = theme_cache.panel_style->get_offset(); if (root) { size.height += get_item_height(root); } @@ -3657,23 +3791,23 @@ void Tree::update_scrollbars() { Size2 hmin = h_scroll->get_combined_minimum_size(); Size2 vmin = v_scroll->get_combined_minimum_size(); - v_scroll->set_begin(Point2(size.width - vmin.width, cache.bg->get_margin(SIDE_TOP))); - v_scroll->set_end(Point2(size.width, size.height - cache.bg->get_margin(SIDE_TOP) - cache.bg->get_margin(SIDE_BOTTOM))); + v_scroll->set_begin(Point2(size.width - vmin.width, theme_cache.panel_style->get_margin(SIDE_TOP))); + v_scroll->set_end(Point2(size.width, size.height - theme_cache.panel_style->get_margin(SIDE_TOP) - theme_cache.panel_style->get_margin(SIDE_BOTTOM))); h_scroll->set_begin(Point2(0, size.height - hmin.height)); h_scroll->set_end(Point2(size.width - vmin.width, size.height)); Size2 internal_min_size = get_internal_min_size(); - bool display_vscroll = internal_min_size.height + cache.bg->get_margin(SIDE_TOP) > size.height; - bool display_hscroll = internal_min_size.width + cache.bg->get_margin(SIDE_LEFT) > size.width; + bool display_vscroll = internal_min_size.height + theme_cache.panel_style->get_margin(SIDE_TOP) > size.height; + bool display_hscroll = internal_min_size.width + theme_cache.panel_style->get_margin(SIDE_LEFT) > size.width; for (int i = 0; i < 2; i++) { // Check twice, as both values are dependent on each other. if (display_hscroll) { - display_vscroll = internal_min_size.height + cache.bg->get_margin(SIDE_TOP) + hmin.height > size.height; + display_vscroll = internal_min_size.height + theme_cache.panel_style->get_margin(SIDE_TOP) + hmin.height > size.height; } if (display_vscroll) { - display_hscroll = internal_min_size.width + cache.bg->get_margin(SIDE_LEFT) + vmin.width > size.width; + display_hscroll = internal_min_size.width + theme_cache.panel_style->get_margin(SIDE_LEFT) + vmin.width > size.width; } } @@ -3681,29 +3815,29 @@ void Tree::update_scrollbars() { v_scroll->show(); v_scroll->set_max(internal_min_size.height); v_scroll->set_page(size.height - hmin.height - tbh); - cache.offset.y = v_scroll->get_value(); + theme_cache.offset.y = v_scroll->get_value(); } else { v_scroll->hide(); - cache.offset.y = 0; + theme_cache.offset.y = 0; } if (display_hscroll) { h_scroll->show(); h_scroll->set_max(internal_min_size.width); h_scroll->set_page(size.width - vmin.width); - cache.offset.x = h_scroll->get_value(); + theme_cache.offset.x = h_scroll->get_value(); } else { h_scroll->hide(); - cache.offset.x = 0; + theme_cache.offset.x = 0; } } int Tree::_get_title_button_height() const { - ERR_FAIL_COND_V(cache.font.is_null() || cache.title_button.is_null(), 0); + ERR_FAIL_COND_V(theme_cache.font.is_null() || theme_cache.title_button.is_null(), 0); int h = 0; if (show_column_titles) { for (int i = 0; i < columns.size(); i++) { - h = MAX(h, columns[i].text_buf->get_size().y + cache.title_button->get_minimum_size().height); + h = MAX(h, columns[i].text_buf->get_size().y + theme_cache.title_button->get_minimum_size().height); } } return h; @@ -3720,7 +3854,7 @@ void Tree::_notification(int p_what) { case NOTIFICATION_MOUSE_EXIT: { if (cache.hover_type != Cache::CLICK_NONE) { cache.hover_type = Cache::CLICK_NONE; - update(); + queue_redraw(); } } break; @@ -3728,20 +3862,16 @@ void Tree::_notification(int p_what) { drag_touching = false; } break; - case NOTIFICATION_ENTER_TREE: { - update_cache(); - } break; - case NOTIFICATION_DRAG_END: { drop_mode_flags = 0; scrolling = false; set_physics_process_internal(false); - update(); + queue_redraw(); } break; case NOTIFICATION_DRAG_BEGIN: { single_select_defer = nullptr; - if (cache.scroll_speed > 0) { + if (theme_cache.scroll_speed > 0) { scrolling = true; set_physics_process_internal(true); } @@ -3785,22 +3915,22 @@ void Tree::_notification(int p_what) { } Point2 mouse_position = get_viewport()->get_mouse_position() - get_global_position(); - if (scrolling && get_rect().grow(cache.scroll_border).has_point(mouse_position)) { + if (scrolling && get_rect().grow(theme_cache.scroll_border).has_point(mouse_position)) { Point2 point; - if ((ABS(mouse_position.x) < ABS(mouse_position.x - get_size().width)) && (ABS(mouse_position.x) < cache.scroll_border)) { - point.x = mouse_position.x - cache.scroll_border; - } else if (ABS(mouse_position.x - get_size().width) < cache.scroll_border) { - point.x = mouse_position.x - (get_size().width - cache.scroll_border); + if ((ABS(mouse_position.x) < ABS(mouse_position.x - get_size().width)) && (ABS(mouse_position.x) < theme_cache.scroll_border)) { + point.x = mouse_position.x - theme_cache.scroll_border; + } else if (ABS(mouse_position.x - get_size().width) < theme_cache.scroll_border) { + point.x = mouse_position.x - (get_size().width - theme_cache.scroll_border); } - if ((ABS(mouse_position.y) < ABS(mouse_position.y - get_size().height)) && (ABS(mouse_position.y) < cache.scroll_border)) { - point.y = mouse_position.y - cache.scroll_border; - } else if (ABS(mouse_position.y - get_size().height) < cache.scroll_border) { - point.y = mouse_position.y - (get_size().height - cache.scroll_border); + if ((ABS(mouse_position.y) < ABS(mouse_position.y - get_size().height)) && (ABS(mouse_position.y) < theme_cache.scroll_border)) { + point.y = mouse_position.y - theme_cache.scroll_border; + } else if (ABS(mouse_position.y - get_size().height) < theme_cache.scroll_border) { + point.y = mouse_position.y - (get_size().height - theme_cache.scroll_border); } - point *= cache.scroll_speed * get_physics_process_delta_time(); + point *= theme_cache.scroll_speed * get_physics_process_delta_time(); point += get_scroll(); h_scroll->set_value(point.x); v_scroll->set_value(point.y); @@ -3808,13 +3938,12 @@ void Tree::_notification(int p_what) { } break; case NOTIFICATION_DRAW: { - update_cache(); + v_scroll->set_custom_step(theme_cache.font->get_height(theme_cache.font_size)); + update_scrollbars(); RID ci = get_canvas_item(); - Ref<StyleBox> bg = cache.bg; - Color font_outline_color = get_theme_color(SNAME("font_outline_color")); - int outline_size = get_theme_constant(SNAME("outline_size")); + Ref<StyleBox> bg = theme_cache.panel_style; Point2 draw_ofs; draw_ofs += bg->get_offset(); @@ -3838,11 +3967,11 @@ void Tree::_notification(int p_what) { if (show_column_titles) { //title buttons - int ofs2 = cache.bg->get_margin(SIDE_LEFT); + int ofs2 = theme_cache.panel_style->get_margin(SIDE_LEFT); for (int i = 0; i < columns.size(); i++) { - Ref<StyleBox> sb = (cache.click_type == Cache::CLICK_TITLE && cache.click_index == i) ? cache.title_button_pressed : ((cache.hover_type == Cache::CLICK_TITLE && cache.hover_index == i) ? cache.title_button_hover : cache.title_button); - Ref<Font> f = cache.tb_font; - Rect2 tbrect = Rect2(ofs2 - cache.offset.x, bg->get_margin(SIDE_TOP), get_column_width(i), tbh); + Ref<StyleBox> sb = (cache.click_type == Cache::CLICK_TITLE && cache.click_index == i) ? theme_cache.title_button_pressed : ((cache.hover_type == Cache::CLICK_TITLE && cache.hover_index == i) ? theme_cache.title_button_hover : theme_cache.title_button); + Ref<Font> f = theme_cache.tb_font; + Rect2 tbrect = Rect2(ofs2 - theme_cache.offset.x, bg->get_margin(SIDE_TOP), get_column_width(i), tbh); if (cache.rtl) { tbrect.position.x = get_size().width - tbrect.size.x - tbrect.position.x; } @@ -3853,19 +3982,18 @@ void Tree::_notification(int p_what) { columns.write[i].text_buf->set_width(clip_w); Vector2 text_pos = tbrect.position + Point2i(sb->get_offset().x + (tbrect.size.width - columns[i].text_buf->get_size().x) / 2, (tbrect.size.height - columns[i].text_buf->get_size().y) / 2); - if (outline_size > 0 && font_outline_color.a > 0) { - columns[i].text_buf->draw_outline(ci, text_pos, outline_size, font_outline_color); + if (theme_cache.font_outline_size > 0 && theme_cache.font_outline_color.a > 0) { + columns[i].text_buf->draw_outline(ci, text_pos, theme_cache.font_outline_size, theme_cache.font_outline_color); } - columns[i].text_buf->draw(ci, text_pos, cache.title_button_color); + columns[i].text_buf->draw(ci, text_pos, theme_cache.title_button_color); } } - // Draw the background focus outline last, so that it is drawn in front of the section headings. + // Draw the focus outline last, so that it is drawn in front of the section headings. // Otherwise, section heading backgrounds can appear to be in front of the focus outline when scrolling. if (has_focus()) { RenderingServer::get_singleton()->canvas_item_add_clip_ignore(ci, true); - const Ref<StyleBox> bg_focus = get_theme_stylebox(SNAME("bg_focus")); - bg_focus->draw(ci, Rect2(Point2(), get_size())); + theme_cache.focus_style->draw(ci, Rect2(Point2(), get_size())); RenderingServer::get_singleton()->canvas_item_add_clip_ignore(ci, false); } } break; @@ -3873,7 +4001,6 @@ void Tree::_notification(int p_what) { case NOTIFICATION_THEME_CHANGED: case NOTIFICATION_LAYOUT_DIRECTION_CHANGED: case NOTIFICATION_TRANSLATION_CHANGED: { - update_cache(); _update_all(); } break; @@ -3908,7 +4035,7 @@ Size2 Tree::get_minimum_size() const { return Size2(); } else { Vector2 min_size = get_internal_min_size(); - Ref<StyleBox> bg = cache.bg; + Ref<StyleBox> bg = theme_cache.panel_style; if (bg.is_valid()) { min_size.x += bg->get_margin(SIDE_LEFT) + bg->get_margin(SIDE_RIGHT); min_size.y += bg->get_margin(SIDE_TOP) + bg->get_margin(SIDE_BOTTOM); @@ -3978,7 +4105,7 @@ void Tree::item_changed(int p_column, TreeItem *p_item) { if (p_item != nullptr && p_column >= 0 && p_column < p_item->cells.size()) { p_item->cells.write[p_column].dirty = true; } - update(); + queue_redraw(); } void Tree::item_selected(int p_column, TreeItem *p_item) { @@ -3997,7 +4124,7 @@ void Tree::item_selected(int p_column, TreeItem *p_item) { } else { select_single_item(p_item, root, p_column); } - update(); + queue_redraw(); } void Tree::item_deselected(int p_column, TreeItem *p_item) { @@ -4012,7 +4139,7 @@ void Tree::item_deselected(int p_column, TreeItem *p_item) { if (select_mode == SELECT_MULTI || select_mode == SELECT_SINGLE) { p_item->cells.write[p_column].selected = false; } - update(); + queue_redraw(); } void Tree::set_select_mode(SelectMode p_mode) { @@ -4035,7 +4162,7 @@ void Tree::deselect_all() { selected_item = nullptr; selected_col = -1; - update(); + queue_redraw(); } bool Tree::is_anything_selected() { @@ -4064,12 +4191,16 @@ void Tree::clear() { popup_edited_item = nullptr; popup_pressing_edited_item = nullptr; - update(); + queue_redraw(); }; void Tree::set_hide_root(bool p_enabled) { + if (hide_root == p_enabled) { + return; + } + hide_root = p_enabled; - update(); + queue_redraw(); } bool Tree::is_root_hidden() const { @@ -4079,31 +4210,48 @@ bool Tree::is_root_hidden() const { void Tree::set_column_custom_minimum_width(int p_column, int p_min_width) { ERR_FAIL_INDEX(p_column, columns.size()); + if (columns[p_column].custom_min_width == p_min_width) { + return; + } + if (p_min_width < 0) { return; } columns.write[p_column].custom_min_width = p_min_width; - update(); + queue_redraw(); } void Tree::set_column_expand(int p_column, bool p_expand) { ERR_FAIL_INDEX(p_column, columns.size()); + if (columns[p_column].expand == p_expand) { + return; + } + columns.write[p_column].expand = p_expand; - update(); + queue_redraw(); } void Tree::set_column_expand_ratio(int p_column, int p_ratio) { ERR_FAIL_INDEX(p_column, columns.size()); + + if (columns[p_column].expand_ratio == p_ratio) { + return; + } + columns.write[p_column].expand_ratio = p_ratio; - update(); + queue_redraw(); } void Tree::set_column_clip_content(int p_column, bool p_fit) { ERR_FAIL_INDEX(p_column, columns.size()); + if (columns[p_column].clip_content == p_fit) { + return; + } + columns.write[p_column].clip_content = p_fit; - update(); + queue_redraw(); } bool Tree::is_column_expanding(int p_column) const { @@ -4183,7 +4331,7 @@ int Tree::get_column_minimum_width(int p_column) const { // Check if the visible title of the column is wider. if (show_column_titles) { - min_width = MAX(cache.font->get_string_size(columns[p_column].title, HORIZONTAL_ALIGNMENT_LEFT, -1, cache.font_size).width + cache.bg->get_margin(SIDE_LEFT) + cache.bg->get_margin(SIDE_RIGHT), min_width); + min_width = MAX(theme_cache.font->get_string_size(columns[p_column].title, HORIZONTAL_ALIGNMENT_LEFT, -1, theme_cache.font_size).width + theme_cache.panel_style->get_margin(SIDE_LEFT) + theme_cache.panel_style->get_margin(SIDE_RIGHT), min_width); } if (!columns[p_column].clip_content) { @@ -4207,9 +4355,9 @@ int Tree::get_column_minimum_width(int p_column) const { // Get the item minimum size. Size2 item_size = item->get_minimum_size(p_column); if (p_column == 0) { - item_size.width += cache.item_margin * depth; + item_size.width += theme_cache.item_margin * depth; } else { - item_size.width += cache.hseparation; + item_size.width += theme_cache.hseparation; } // Check if the item is wider. @@ -4228,7 +4376,7 @@ int Tree::get_column_width(int p_column) const { if (columns[p_column].expand) { int expand_area = get_size().width; - Ref<StyleBox> bg = cache.bg; + Ref<StyleBox> bg = theme_cache.panel_style; if (bg.is_valid()) { expand_area -= bg->get_margin(SIDE_LEFT) + bg->get_margin(SIDE_RIGHT); @@ -4276,7 +4424,7 @@ void Tree::set_columns(int p_columns) { if (selected_col >= p_columns) { selected_col = p_columns - 1; } - update(); + queue_redraw(); } int Tree::get_columns() const { @@ -4284,7 +4432,7 @@ int Tree::get_columns() const { } void Tree::_scroll_moved(float) { - update(); + queue_redraw(); } Rect2 Tree::get_custom_popup_rect() const { @@ -4305,7 +4453,7 @@ int Tree::get_item_offset(TreeItem *p_item) const { ofs += compute_item_height(it); if (it != root || !hide_root) { - ofs += cache.vseparation; + ofs += theme_cache.vseparation; } if (it->first_child && !it->collapsed) { @@ -4336,14 +4484,14 @@ void Tree::ensure_cursor_is_visible() { return; // Nothing under cursor. } - const Size2 area_size = get_size() - cache.bg->get_minimum_size(); + const Size2 area_size = get_size() - theme_cache.panel_style->get_minimum_size(); int y_offset = get_item_offset(selected_item); if (y_offset != -1) { const int tbh = _get_title_button_height(); y_offset -= tbh; - const int cell_h = compute_item_height(selected_item) + cache.vseparation; + const int cell_h = compute_item_height(selected_item) + theme_cache.vseparation; const int screen_h = area_size.height - h_scroll->get_combined_minimum_size().height - tbh; if (cell_h > screen_h) { // Screen size is too small, maybe it was not resized yet. @@ -4410,7 +4558,7 @@ Rect2 Tree::get_item_rect(TreeItem *p_item, int p_column, int p_button) const { Vector2 ofst = Vector2(r.position.x + r.size.x, r.position.y); for (int j = c.buttons.size() - 1; j >= 0; j--) { Ref<Texture2D> b = c.buttons[j].texture; - Size2 size = b->get_size() + cache.button_pressed->get_minimum_size(); + Size2 size = b->get_size() + theme_cache.button_pressed->get_minimum_size(); ofst.x -= size.x; if (j == p_button) { @@ -4424,8 +4572,12 @@ Rect2 Tree::get_item_rect(TreeItem *p_item, int p_column, int p_button) const { } void Tree::set_column_titles_visible(bool p_show) { + if (show_column_titles == p_show) { + return; + } + show_column_titles = p_show; - update(); + queue_redraw(); } bool Tree::are_column_titles_visible() const { @@ -4434,12 +4586,14 @@ bool Tree::are_column_titles_visible() const { void Tree::set_column_title(int p_column, const String &p_title) { ERR_FAIL_INDEX(p_column, columns.size()); - if (cache.font.is_null()) { // avoid a strange case that may corrupt stuff - update_cache(); + + if (columns[p_column].title == p_title) { + return; } + columns.write[p_column].title = p_title; update_column(p_column); - update(); + queue_redraw(); } String Tree::get_column_title(int p_column) const { @@ -4453,7 +4607,7 @@ void Tree::set_column_title_direction(int p_column, Control::TextDirection p_tex if (columns[p_column].text_direction != p_text_direction) { columns.write[p_column].text_direction = p_text_direction; update_column(p_column); - update(); + queue_redraw(); } } @@ -4467,7 +4621,7 @@ void Tree::set_column_title_language(int p_column, const String &p_language) { if (columns[p_column].language != p_language) { columns.write[p_column].language = p_language; update_column(p_column); - update(); + queue_redraw(); } } @@ -4498,7 +4652,7 @@ void Tree::scroll_to_item(TreeItem *p_item, bool p_center_on_item) { const real_t tree_height = get_size().y; const Rect2 item_rect = get_item_rect(p_item); const real_t item_y = item_rect.position.y; - const real_t item_height = item_rect.size.y + cache.vseparation; + const real_t item_height = item_rect.size.y + theme_cache.vseparation; if (p_center_on_item) { v_scroll->set_value(item_y - (tree_height - item_height) / 2.0f); @@ -4515,6 +4669,10 @@ void Tree::scroll_to_item(TreeItem *p_item, bool p_center_on_item) { } void Tree::set_h_scroll_enabled(bool p_enable) { + if (h_scroll_enabled == p_enable) { + return; + } + h_scroll_enabled = p_enable; update_minimum_size(); } @@ -4524,6 +4682,10 @@ bool Tree::is_h_scroll_enabled() const { } void Tree::set_v_scroll_enabled(bool p_enable) { + if (v_scroll_enabled == p_enable) { + return; + } + v_scroll_enabled = p_enable; update_minimum_size(); } @@ -4614,7 +4776,7 @@ TreeItem *Tree::_find_item_at_pos(TreeItem *p_item, const Point2 &p_pos, int &r_ Point2 pos = p_pos; if ((root != p_item || !hide_root) && p_item->is_visible()) { - h = compute_item_height(p_item) + cache.vseparation; + h = compute_item_height(p_item) + theme_cache.vseparation; if (pos.y < h) { if (drop_mode_flags == DROP_MODE_ON_ITEM) { section = 0; @@ -4671,7 +4833,7 @@ int Tree::get_column_at_position(const Point2 &p_pos) const { if (is_layout_rtl()) { pos.x = get_size().width - pos.x; } - pos -= cache.bg->get_offset(); + pos -= theme_cache.panel_style->get_offset(); pos.y -= _get_title_button_height(); if (pos.y < 0) { return -1; @@ -4701,7 +4863,7 @@ int Tree::get_drop_section_at_position(const Point2 &p_pos) const { if (is_layout_rtl()) { pos.x = get_size().width - pos.x; } - pos -= cache.bg->get_offset(); + pos -= theme_cache.panel_style->get_offset(); pos.y -= _get_title_button_height(); if (pos.y < 0) { return -100; @@ -4731,7 +4893,7 @@ TreeItem *Tree::get_item_at_position(const Point2 &p_pos) const { if (is_layout_rtl()) { pos.x = get_size().width - pos.x; } - pos -= cache.bg->get_offset(); + pos -= theme_cache.panel_style->get_offset(); pos.y -= _get_title_button_height(); if (pos.y < 0) { return nullptr; @@ -4758,7 +4920,7 @@ TreeItem *Tree::get_item_at_position(const Point2 &p_pos) const { int Tree::get_button_id_at_position(const Point2 &p_pos) const { if (root) { Point2 pos = p_pos; - pos -= cache.bg->get_offset(); + pos -= theme_cache.panel_style->get_offset(); pos.y -= _get_title_button_height(); if (pos.y < 0) { return -1; @@ -4784,7 +4946,7 @@ int Tree::get_button_id_at_position(const Point2 &p_pos) const { for (int j = c.buttons.size() - 1; j >= 0; j--) { Ref<Texture2D> b = c.buttons[j].texture; - Size2 size = b->get_size() + cache.button_pressed->get_minimum_size(); + Size2 size = b->get_size() + theme_cache.button_pressed->get_minimum_size(); if (pos.x > col_width - size.width) { return c.buttons[j].id; } @@ -4799,7 +4961,7 @@ int Tree::get_button_id_at_position(const Point2 &p_pos) const { String Tree::get_tooltip(const Point2 &p_pos) const { if (root) { Point2 pos = p_pos; - pos -= cache.bg->get_offset(); + pos -= theme_cache.panel_style->get_offset(); pos.y -= _get_title_button_height(); if (pos.y < 0) { return Control::get_tooltip(p_pos); @@ -4825,7 +4987,7 @@ String Tree::get_tooltip(const Point2 &p_pos) const { for (int j = c.buttons.size() - 1; j >= 0; j--) { Ref<Texture2D> b = c.buttons[j].texture; - Size2 size = b->get_size() + cache.button_pressed->get_minimum_size(); + Size2 size = b->get_size() + theme_cache.button_pressed->get_minimum_size(); if (pos.x > col_width - size.width) { String tooltip = c.buttons[j].tooltip; if (!tooltip.is_empty()) { @@ -4835,10 +4997,10 @@ String Tree::get_tooltip(const Point2 &p_pos) const { col_width -= size.width; } String ret; - if (it->get_tooltip(col) == "") { + if (it->get_tooltip_text(col) == "") { ret = it->get_text(col); } else { - ret = it->get_tooltip(col); + ret = it->get_tooltip_text(col); } return ret; } @@ -4852,8 +5014,12 @@ void Tree::set_cursor_can_exit_tree(bool p_enable) { } void Tree::set_hide_folding(bool p_hide) { + if (hide_folding == p_hide) { + return; + } + hide_folding = p_hide; - update(); + queue_redraw(); } bool Tree::is_folding_hidden() const { @@ -4869,7 +5035,7 @@ void Tree::set_drop_mode_flags(int p_flags) { drop_mode_over = nullptr; } - update(); + queue_redraw(); } int Tree::get_drop_mode_flags() const { @@ -4997,7 +5163,7 @@ void Tree::_bind_methods() { 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"))); + ADD_SIGNAL(MethodInfo("column_title_clicked", PropertyInfo(Variant::INT, "column"), PropertyInfo(Variant::INT, "mouse_button_index"))); ADD_SIGNAL(MethodInfo("nothing_selected")); BIND_ENUM_CONSTANT(SELECT_SINGLE); @@ -5057,8 +5223,6 @@ Tree::Tree() { set_mouse_filter(MOUSE_FILTER_STOP); set_clip_contents(true); - - update_cache(); } Tree::~Tree() { diff --git a/scene/gui/tree.h b/scene/gui/tree.h index f0819e2980..450943c048 100644 --- a/scene/gui/tree.h +++ b/scene/gui/tree.h @@ -245,7 +245,7 @@ public: void add_button(int p_column, const Ref<Texture2D> &p_button, int p_id = -1, bool p_disabled = false, const String &p_tooltip = ""); int get_button_count(int p_column) const; - String get_button_tooltip(int p_column, int p_idx) const; + String get_button_tooltip_text(int p_column, int p_idx) const; Ref<Texture2D> get_button(int p_column, int p_idx) const; int get_button_id(int p_column, int p_idx) const; void erase_button(int p_column, int p_idx); @@ -308,8 +308,8 @@ public: void set_custom_as_button(int p_column, bool p_button); bool is_custom_set_as_button(int p_column) const; - void set_tooltip(int p_column, const String &p_tooltip); - String get_tooltip(int p_column) const; + void set_tooltip_text(int p_column, const String &p_tooltip); + String get_tooltip_text(int p_column) const; void set_text_alignment(int p_column, HorizontalAlignment p_alignment); HorizontalAlignment get_text_alignment(int p_column) const; @@ -339,9 +339,16 @@ public: TreeItem *get_child(int p_idx); int get_visible_child_count(); int get_child_count(); - Array get_children(); + TypedArray<TreeItem> get_children(); int get_index(); +#ifdef DEV_ENABLED + // This debugging code can be removed once the current refactoring of this class is complete. + void validate_cache() const; +#else + void validate_cache() const {} +#endif + void move_before(TreeItem *p_item); void move_after(TreeItem *p_item); @@ -475,12 +482,15 @@ private: void propagate_set_columns(TreeItem *p_item); - struct Cache { + struct ThemeCache { + Ref<StyleBox> panel_style; + Ref<StyleBox> focus_style; + Ref<Font> font; Ref<Font> tb_font; int font_size = 0; int tb_font_size = 0; - Ref<StyleBox> bg; + Ref<StyleBox> selected; Ref<StyleBox> selected_focus; Ref<StyleBox> cursor; @@ -498,8 +508,9 @@ private: Ref<Texture2D> checked; Ref<Texture2D> unchecked; Ref<Texture2D> indeterminate; - Ref<Texture2D> arrow_collapsed; Ref<Texture2D> arrow; + Ref<Texture2D> arrow_collapsed; + Ref<Texture2D> arrow_collapsed_mirrored; Ref<Texture2D> select_arrow; Ref<Texture2D> updown; @@ -529,7 +540,9 @@ private: int scroll_border = 0; int scroll_speed = 0; int font_outline_size = 0; + } theme_cache; + struct Cache { enum ClickType { CLICK_NONE, CLICK_TITLE, @@ -552,7 +565,6 @@ private: Point2i text_editor_position; bool rtl = false; - } cache; int _get_title_button_height() const; @@ -565,7 +577,6 @@ private: bool v_scroll_enabled = true; Size2 get_internal_min_size() const; - void update_cache(); void update_scrollbars(); Rect2 search_item_rect(TreeItem *p_from, TreeItem *p_item); @@ -613,6 +624,8 @@ private: bool _scroll(bool p_horizontal, float p_pages); protected: + virtual void _update_theme_item_cache() override; + static void _bind_methods(); public: diff --git a/scene/gui/video_stream_player.cpp b/scene/gui/video_stream_player.cpp index f20a2ad67b..1e03ed6e76 100644 --- a/scene/gui/video_stream_player.cpp +++ b/scene/gui/video_stream_player.cpp @@ -208,8 +208,12 @@ Size2 VideoStreamPlayer::get_minimum_size() const { } void VideoStreamPlayer::set_expand(bool p_expand) { + if (expand == p_expand) { + return; + } + expand = p_expand; - update(); + queue_redraw(); update_minimum_size(); } @@ -257,7 +261,7 @@ void VideoStreamPlayer::set_stream(const Ref<VideoStream> &p_stream) { AudioServer::get_singleton()->unlock(); } - update(); + queue_redraw(); if (!expand) { update_minimum_size(); @@ -306,6 +310,10 @@ bool VideoStreamPlayer::is_playing() const { } void VideoStreamPlayer::set_paused(bool p_paused) { + if (paused == p_paused) { + return; + } + paused = p_paused; if (!p_paused && !can_process()) { paused_from_tree = true; @@ -354,7 +362,7 @@ void VideoStreamPlayer::set_volume_db(float p_db) { if (p_db < -79) { set_volume(0); } else { - set_volume(Math::db2linear(p_db)); + set_volume(Math::db_to_linear(p_db)); } } @@ -362,7 +370,7 @@ float VideoStreamPlayer::get_volume_db() const { if (volume == 0) { return -80; } else { - return Math::linear2db(volume); + return Math::linear_to_db(volume); } } diff --git a/scene/gui/video_stream_player.h b/scene/gui/video_stream_player.h index 913e7905b6..9974eb8488 100644 --- a/scene/gui/video_stream_player.h +++ b/scene/gui/video_stream_player.h @@ -79,7 +79,7 @@ class VideoStreamPlayer : public Control { protected: static void _bind_methods(); void _notification(int p_notification); - void _validate_property(PropertyInfo &p_property) const override; + void _validate_property(PropertyInfo &p_property) const; public: Size2 get_minimum_size() const override; |