diff options
Diffstat (limited to 'scene/gui')
104 files changed, 7784 insertions, 4945 deletions
diff --git a/scene/gui/aspect_ratio_container.cpp b/scene/gui/aspect_ratio_container.cpp index fb6fa9dec9..b59eda465e 100644 --- a/scene/gui/aspect_ratio_container.cpp +++ b/scene/gui/aspect_ratio_container.cpp @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ @@ -60,16 +60,34 @@ void AspectRatioContainer::set_stretch_mode(StretchMode p_mode) { queue_sort(); } -void AspectRatioContainer::set_alignment_horizontal(AlignMode p_alignment_horizontal) { +void AspectRatioContainer::set_alignment_horizontal(AlignmentMode p_alignment_horizontal) { alignment_horizontal = p_alignment_horizontal; queue_sort(); } -void AspectRatioContainer::set_alignment_vertical(AlignMode p_alignment_vertical) { +void AspectRatioContainer::set_alignment_vertical(AlignmentMode p_alignment_vertical) { alignment_vertical = p_alignment_vertical; queue_sort(); } +Vector<int> AspectRatioContainer::get_allowed_size_flags_horizontal() const { + Vector<int> flags; + flags.append(SIZE_FILL); + flags.append(SIZE_SHRINK_BEGIN); + flags.append(SIZE_SHRINK_CENTER); + flags.append(SIZE_SHRINK_END); + return flags; +} + +Vector<int> AspectRatioContainer::get_allowed_size_flags_vertical() const { + Vector<int> flags; + flags.append(SIZE_FILL); + flags.append(SIZE_SHRINK_BEGIN); + flags.append(SIZE_SHRINK_CENTER); + flags.append(SIZE_SHRINK_END); + return flags; +} + void AspectRatioContainer::_notification(int p_what) { switch (p_what) { case NOTIFICATION_SORT_CHILDREN: { @@ -107,25 +125,25 @@ void AspectRatioContainer::_notification(int p_what) { float align_x = 0.5; switch (alignment_horizontal) { - case ALIGN_BEGIN: { + case ALIGNMENT_BEGIN: { align_x = 0.0; } break; - case ALIGN_CENTER: { + case ALIGNMENT_CENTER: { align_x = 0.5; } break; - case ALIGN_END: { + case ALIGNMENT_END: { align_x = 1.0; } break; } float align_y = 0.5; switch (alignment_vertical) { - case ALIGN_BEGIN: { + case ALIGNMENT_BEGIN: { align_y = 0.0; } break; - case ALIGN_CENTER: { + case ALIGNMENT_CENTER: { align_y = 0.5; } break; - case ALIGN_END: { + case ALIGNMENT_END: { align_y = 1.0; } break; } @@ -166,7 +184,7 @@ void AspectRatioContainer::_bind_methods() { BIND_ENUM_CONSTANT(STRETCH_FIT); BIND_ENUM_CONSTANT(STRETCH_COVER); - BIND_ENUM_CONSTANT(ALIGN_BEGIN); - BIND_ENUM_CONSTANT(ALIGN_CENTER); - BIND_ENUM_CONSTANT(ALIGN_END); + BIND_ENUM_CONSTANT(ALIGNMENT_BEGIN); + BIND_ENUM_CONSTANT(ALIGNMENT_CENTER); + BIND_ENUM_CONSTANT(ALIGNMENT_END); } diff --git a/scene/gui/aspect_ratio_container.h b/scene/gui/aspect_ratio_container.h index c95c6a7274..6740e2f329 100644 --- a/scene/gui/aspect_ratio_container.h +++ b/scene/gui/aspect_ratio_container.h @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ @@ -48,17 +48,17 @@ public: STRETCH_FIT, STRETCH_COVER, }; - enum AlignMode { - ALIGN_BEGIN, - ALIGN_CENTER, - ALIGN_END, + enum AlignmentMode { + ALIGNMENT_BEGIN, + ALIGNMENT_CENTER, + ALIGNMENT_END, }; private: float ratio = 1.0; StretchMode stretch_mode = STRETCH_FIT; - AlignMode alignment_horizontal = ALIGN_CENTER; - AlignMode alignment_vertical = ALIGN_CENTER; + AlignmentMode alignment_horizontal = ALIGNMENT_CENTER; + AlignmentMode alignment_vertical = ALIGNMENT_CENTER; public: void set_ratio(float p_ratio); @@ -67,14 +67,17 @@ public: void set_stretch_mode(StretchMode p_mode); StretchMode get_stretch_mode() const { return stretch_mode; } - void set_alignment_horizontal(AlignMode p_alignment_horizontal); - AlignMode get_alignment_horizontal() const { return alignment_horizontal; } + void set_alignment_horizontal(AlignmentMode p_alignment_horizontal); + AlignmentMode get_alignment_horizontal() const { return alignment_horizontal; } - void set_alignment_vertical(AlignMode p_alignment_vertical); - AlignMode get_alignment_vertical() const { return alignment_vertical; } + void set_alignment_vertical(AlignmentMode p_alignment_vertical); + AlignmentMode get_alignment_vertical() const { return alignment_vertical; } + + virtual Vector<int> get_allowed_size_flags_horizontal() const override; + virtual Vector<int> get_allowed_size_flags_vertical() const override; }; VARIANT_ENUM_CAST(AspectRatioContainer::StretchMode); -VARIANT_ENUM_CAST(AspectRatioContainer::AlignMode); +VARIANT_ENUM_CAST(AspectRatioContainer::AlignmentMode); #endif // ASPECT_RATIO_CONTAINER_H diff --git a/scene/gui/base_button.cpp b/scene/gui/base_button.cpp index bef1011364..ab86face7e 100644 --- a/scene/gui/base_button.cpp +++ b/scene/gui/base_button.cpp @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ @@ -62,7 +62,7 @@ void BaseButton::gui_input(const Ref<InputEvent> &p_event) { Ref<InputEventMouseButton> mouse_button = p_event; bool ui_accept = p_event->is_action("ui_accept") && !p_event->is_echo(); - bool button_masked = mouse_button.is_valid() && ((1 << (mouse_button->get_button_index() - 1)) & button_mask) != 0; + 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) { on_action_event(p_event); return; @@ -81,42 +81,50 @@ void BaseButton::gui_input(const Ref<InputEvent> &p_event) { } void BaseButton::_notification(int p_what) { - if (p_what == NOTIFICATION_MOUSE_ENTER) { - status.hovering = true; - update(); - } + switch (p_what) { + case NOTIFICATION_MOUSE_ENTER: { + status.hovering = true; + update(); + } break; - if (p_what == NOTIFICATION_MOUSE_EXIT) { - status.hovering = false; - update(); - } - if (p_what == NOTIFICATION_DRAG_BEGIN || p_what == NOTIFICATION_SCROLL_BEGIN) { - if (status.press_attempt) { - status.press_attempt = false; + case NOTIFICATION_MOUSE_EXIT: { + status.hovering = false; update(); - } - } + } break; - if (p_what == NOTIFICATION_FOCUS_ENTER) { - update(); - } + case NOTIFICATION_DRAG_BEGIN: + case NOTIFICATION_SCROLL_BEGIN: { + if (status.press_attempt) { + status.press_attempt = false; + update(); + } + } break; - if (p_what == NOTIFICATION_FOCUS_EXIT) { - if (status.press_attempt) { - status.press_attempt = false; + case NOTIFICATION_FOCUS_ENTER: { update(); - } else if (status.hovering) { - update(); - } - } + } break; - if (p_what == NOTIFICATION_EXIT_TREE || (p_what == NOTIFICATION_VISIBILITY_CHANGED && !is_visible_in_tree())) { - if (!toggle_mode) { - status.pressed = false; - } - status.hovering = false; - status.press_attempt = false; - status.pressing_inside = false; + case NOTIFICATION_FOCUS_EXIT: { + if (status.press_attempt) { + status.press_attempt = false; + update(); + } else if (status.hovering) { + update(); + } + } break; + + case NOTIFICATION_VISIBILITY_CHANGED: + case NOTIFICATION_EXIT_TREE: { + if (p_what == NOTIFICATION_VISIBILITY_CHANGED && is_visible_in_tree()) { + break; + } + if (!toggle_mode) { + status.pressed = false; + } + status.hovering = false; + status.press_attempt = false; + status.pressing_inside = false; + } break; } } @@ -313,11 +321,11 @@ BaseButton::ActionMode BaseButton::get_action_mode() const { return action_mode; } -void BaseButton::set_button_mask(int p_mask) { +void BaseButton::set_button_mask(MouseButton p_mask) { button_mask = p_mask; } -int BaseButton::get_button_mask() const { +MouseButton BaseButton::get_button_mask() const { return button_mask; } @@ -341,7 +349,7 @@ Ref<Shortcut> BaseButton::get_shortcut() const { void BaseButton::unhandled_key_input(const Ref<InputEvent> &p_event) { ERR_FAIL_COND(p_event.is_null()); - if (!_is_focus_owner_in_shorcut_context()) { + if (!_is_focus_owner_in_shortcut_context()) { return; } @@ -355,8 +363,8 @@ String BaseButton::get_tooltip(const Point2 &p_pos) const { String tooltip = Control::get_tooltip(p_pos); if (shortcut_in_tooltip && shortcut.is_valid() && shortcut->has_valid_event()) { String text = shortcut->get_name() + " (" + shortcut->get_as_text() + ")"; - if (tooltip != String() && shortcut->get_name().nocasecmp_to(tooltip) != 0) { - text += "\n" + tooltip; + if (!tooltip.is_empty() && shortcut->get_name().nocasecmp_to(tooltip) != 0) { + text += "\n" + atr(tooltip); } tooltip = text; } @@ -382,8 +390,11 @@ Ref<ButtonGroup> BaseButton::get_button_group() const { } void BaseButton::set_shortcut_context(Node *p_node) { - ERR_FAIL_NULL_MSG(p_node, "Shortcut context node can't be null."); - shortcut_context = p_node->get_instance_id(); + if (p_node != nullptr) { + shortcut_context = p_node->get_instance_id(); + } else { + shortcut_context = ObjectID(); + } } Node *BaseButton::get_shortcut_context() const { @@ -393,14 +404,14 @@ Node *BaseButton::get_shortcut_context() const { return ctx_node; } -bool BaseButton::_is_focus_owner_in_shorcut_context() const { +bool BaseButton::_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_focus_owner(); + 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)); @@ -441,10 +452,11 @@ void BaseButton::_bind_methods() { ADD_SIGNAL(MethodInfo("button_up")); ADD_SIGNAL(MethodInfo("button_down")); ADD_SIGNAL(MethodInfo("toggled", PropertyInfo(Variant::BOOL, "button_pressed"))); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "disabled"), "set_disabled", "is_disabled"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "toggle_mode"), "set_toggle_mode", "is_toggle_mode"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "shortcut_in_tooltip"), "set_shortcut_in_tooltip", "is_shortcut_in_tooltip_enabled"); - ADD_PROPERTY(PropertyInfo(Variant::BOOL, "pressed"), "set_pressed", "is_pressed"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "button_pressed"), "set_pressed", "is_pressed"); ADD_PROPERTY(PropertyInfo(Variant::INT, "action_mode", PROPERTY_HINT_ENUM, "Button Press,Button Release"), "set_action_mode", "get_action_mode"); ADD_PROPERTY(PropertyInfo(Variant::INT, "button_mask", PROPERTY_HINT_FLAGS, "Mouse Left, Mouse Right, Mouse Middle"), "set_button_mask", "get_button_mask"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "keep_pressed_outside"), "set_keep_pressed_outside", "is_keep_pressed_outside"); @@ -500,7 +512,8 @@ BaseButton *ButtonGroup::get_pressed_button() { void ButtonGroup::_bind_methods() { ClassDB::bind_method(D_METHOD("get_pressed_button"), &ButtonGroup::get_pressed_button); ClassDB::bind_method(D_METHOD("get_buttons"), &ButtonGroup::_get_buttons); - ADD_SIGNAL(MethodInfo("pressed", PropertyInfo(Variant::OBJECT, "button"))); + + ADD_SIGNAL(MethodInfo("pressed", PropertyInfo(Variant::OBJECT, "button", PROPERTY_HINT_RESOURCE_TYPE, "BaseButton"))); } ButtonGroup::ButtonGroup() { diff --git a/scene/gui/base_button.h b/scene/gui/base_button.h index cd6e78464f..a2b6ee0845 100644 --- a/scene/gui/base_button.h +++ b/scene/gui/base_button.h @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ @@ -31,6 +31,7 @@ #ifndef BASE_BUTTON_H #define BASE_BUTTON_H +#include "core/input/shortcut.h" #include "scene/gui/control.h" class ButtonGroup; @@ -45,7 +46,7 @@ public: }; private: - int button_mask = MOUSE_BUTTON_MASK_LEFT; + MouseButton button_mask = MouseButton::MASK_LEFT; bool toggle_mode = false; bool shortcut_in_tooltip = true; bool keep_pressed_outside = false; @@ -79,7 +80,7 @@ protected: virtual void unhandled_key_input(const Ref<InputEvent> &p_event) override; void _notification(int p_what); - bool _is_focus_owner_in_shorcut_context() const; + bool _is_focus_owner_in_shortcut_context() const; GDVIRTUAL0(_pressed) GDVIRTUAL1(_toggled, bool) @@ -118,8 +119,8 @@ public: void set_keep_pressed_outside(bool p_on); bool is_keep_pressed_outside() const; - void set_button_mask(int p_mask); - int get_button_mask() const; + void set_button_mask(MouseButton p_mask); + MouseButton get_button_mask() const; void set_shortcut(const Ref<Shortcut> &p_shortcut); Ref<Shortcut> get_shortcut() const; diff --git a/scene/gui/box_container.cpp b/scene/gui/box_container.cpp index cf2df4e1a2..251648da69 100644 --- a/scene/gui/box_container.cpp +++ b/scene/gui/box_container.cpp @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ @@ -29,6 +29,7 @@ /*************************************************************************/ #include "box_container.h" + #include "label.h" #include "margin_container.h" @@ -154,29 +155,29 @@ void BoxContainer::_resort() { int ofs = 0; if (!has_stretched) { if (!vertical) { - switch (align) { - case ALIGN_BEGIN: + switch (alignment) { + case ALIGNMENT_BEGIN: if (rtl) { ofs = stretch_diff; } break; - case ALIGN_CENTER: + case ALIGNMENT_CENTER: ofs = stretch_diff / 2; break; - case ALIGN_END: + case ALIGNMENT_END: if (!rtl) { ofs = stretch_diff; } break; } } else { - switch (align) { - case ALIGN_BEGIN: + switch (alignment) { + case ALIGNMENT_BEGIN: break; - case ALIGN_CENTER: + case ALIGNMENT_CENTER: ofs = stretch_diff / 2; break; - case ALIGN_END: + case ALIGNMENT_END: ofs = stretch_diff; break; } @@ -294,9 +295,11 @@ void BoxContainer::_notification(int p_what) { case NOTIFICATION_SORT_CHILDREN: { _resort(); } break; + case NOTIFICATION_THEME_CHANGED: { - minimum_size_changed(); + update_minimum_size(); } break; + case NOTIFICATION_TRANSLATION_CHANGED: case NOTIFICATION_LAYOUT_DIRECTION_CHANGED: { queue_sort(); @@ -304,13 +307,13 @@ void BoxContainer::_notification(int p_what) { } } -void BoxContainer::set_alignment(AlignMode p_align) { - align = p_align; +void BoxContainer::set_alignment(AlignmentMode p_alignment) { + alignment = p_alignment; _resort(); } -BoxContainer::AlignMode BoxContainer::get_alignment() const { - return align; +BoxContainer::AlignmentMode BoxContainer::get_alignment() const { + return alignment; } Control *BoxContainer::add_spacer(bool p_begin) { @@ -331,6 +334,30 @@ Control *BoxContainer::add_spacer(bool p_begin) { return c; } +Vector<int> BoxContainer::get_allowed_size_flags_horizontal() const { + Vector<int> flags; + flags.append(SIZE_FILL); + if (!vertical) { + flags.append(SIZE_EXPAND); + } + flags.append(SIZE_SHRINK_BEGIN); + flags.append(SIZE_SHRINK_CENTER); + flags.append(SIZE_SHRINK_END); + return flags; +} + +Vector<int> BoxContainer::get_allowed_size_flags_vertical() const { + Vector<int> flags; + flags.append(SIZE_FILL); + if (vertical) { + flags.append(SIZE_EXPAND); + } + flags.append(SIZE_SHRINK_BEGIN); + flags.append(SIZE_SHRINK_CENTER); + flags.append(SIZE_SHRINK_END); + return flags; +} + BoxContainer::BoxContainer(bool p_vertical) { vertical = p_vertical; } @@ -340,9 +367,9 @@ void BoxContainer::_bind_methods() { ClassDB::bind_method(D_METHOD("get_alignment"), &BoxContainer::get_alignment); ClassDB::bind_method(D_METHOD("set_alignment", "alignment"), &BoxContainer::set_alignment); - BIND_ENUM_CONSTANT(ALIGN_BEGIN); - BIND_ENUM_CONSTANT(ALIGN_CENTER); - BIND_ENUM_CONSTANT(ALIGN_END); + 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"); } @@ -351,11 +378,11 @@ MarginContainer *VBoxContainer::add_margin_child(const String &p_label, Control Label *l = memnew(Label); l->set_theme_type_variation("HeaderSmall"); l->set_text(p_label); - add_child(l, false, INTERNAL_MODE_FRONT); + add_child(l); MarginContainer *mc = memnew(MarginContainer); mc->add_theme_constant_override("margin_left", 0); - mc->add_child(p_control); - add_child(mc, false, INTERNAL_MODE_FRONT); + mc->add_child(p_control, true); + add_child(mc); if (p_expand) { mc->set_v_size_flags(SIZE_EXPAND_FILL); } diff --git a/scene/gui/box_container.h b/scene/gui/box_container.h index 23feea565c..3043c3ea45 100644 --- a/scene/gui/box_container.h +++ b/scene/gui/box_container.h @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ @@ -37,15 +37,15 @@ class BoxContainer : public Container { GDCLASS(BoxContainer, Container); public: - enum AlignMode { - ALIGN_BEGIN, - ALIGN_CENTER, - ALIGN_END + enum AlignmentMode { + ALIGNMENT_BEGIN, + ALIGNMENT_CENTER, + ALIGNMENT_END }; private: bool vertical = false; - AlignMode align = ALIGN_BEGIN; + AlignmentMode alignment = ALIGNMENT_BEGIN; void _resort(); @@ -57,11 +57,14 @@ protected: public: Control *add_spacer(bool p_begin = false); - void set_alignment(AlignMode p_align); - AlignMode get_alignment() const; + void set_alignment(AlignmentMode p_alignment); + AlignmentMode get_alignment() const; virtual Size2 get_minimum_size() const override; + virtual Vector<int> get_allowed_size_flags_horizontal() const override; + virtual Vector<int> get_allowed_size_flags_vertical() const override; + BoxContainer(bool p_vertical = false); }; @@ -84,6 +87,6 @@ public: BoxContainer(true) {} }; -VARIANT_ENUM_CAST(BoxContainer::AlignMode); +VARIANT_ENUM_CAST(BoxContainer::AlignmentMode); #endif // BOX_CONTAINER_H diff --git a/scene/gui/button.cpp b/scene/gui/button.cpp index 9818c8f0cc..724714b93b 100644 --- a/scene/gui/button.cpp +++ b/scene/gui/button.cpp @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ @@ -50,9 +50,9 @@ Size2 Button::get_minimum_size() const { if (!_icon.is_null()) { minsize.height = MAX(minsize.height, _icon->get_height()); - if (icon_align != ALIGN_CENTER) { + if (icon_alignment != HORIZONTAL_ALIGNMENT_CENTER) { minsize.width += _icon->get_width(); - if (xl_text != "") { + if (!xl_text.is_empty()) { minsize.width += get_theme_constant(SNAME("hseparation")); } } else { @@ -78,19 +78,22 @@ void Button::_notification(int p_what) { case NOTIFICATION_LAYOUT_DIRECTION_CHANGED: { update(); } break; + case NOTIFICATION_TRANSLATION_CHANGED: { xl_text = atr(text); _shape(); - minimum_size_changed(); + update_minimum_size(); update(); } break; + case NOTIFICATION_THEME_CHANGED: { _shape(); - minimum_size_changed(); + update_minimum_size(); update(); } break; + case NOTIFICATION_DRAW: { RID ci = get_canvas_item(); Size2 size = get_size(); @@ -126,7 +129,8 @@ void Button::_notification(int p_what) { } } break; case DRAW_HOVER_PRESSED: { - if (has_theme_stylebox(SNAME("hover_pressed")) && has_theme_stylebox_override("hover_pressed")) { + // 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")); } else { @@ -138,8 +142,6 @@ void Button::_notification(int p_what) { } if (has_theme_color(SNAME("font_hover_pressed_color"))) { color = get_theme_color(SNAME("font_hover_pressed_color")); - } else { - color = get_theme_color(SNAME("font_color")); } if (has_theme_color(SNAME("icon_hover_pressed_color"))) { color_icon = get_theme_color(SNAME("icon_hover_pressed_color")); @@ -198,6 +200,8 @@ void Button::_notification(int p_what) { color = get_theme_color(SNAME("font_disabled_color")); if (has_theme_color(SNAME("icon_disabled_color"))) { color_icon = get_theme_color(SNAME("icon_disabled_color")); + } else { + color_icon.a = 0.4; } } break; @@ -216,38 +220,35 @@ void Button::_notification(int p_what) { } Rect2 icon_region = Rect2(); - TextAlign icon_align_rtl_checked = icon_align; - TextAlign align_rtl_checked = align; + HorizontalAlignment icon_align_rtl_checked = icon_alignment; + HorizontalAlignment align_rtl_checked = alignment; // Swap icon and text alignment sides if right-to-left layout is set. if (rtl) { - if (icon_align == ALIGN_RIGHT) { - icon_align_rtl_checked = ALIGN_LEFT; - } else if (icon_align == ALIGN_LEFT) { - icon_align_rtl_checked = ALIGN_RIGHT; + if (icon_alignment == HORIZONTAL_ALIGNMENT_RIGHT) { + icon_align_rtl_checked = HORIZONTAL_ALIGNMENT_LEFT; + } else if (icon_alignment == HORIZONTAL_ALIGNMENT_LEFT) { + icon_align_rtl_checked = HORIZONTAL_ALIGNMENT_RIGHT; } - if (align == ALIGN_RIGHT) { - align_rtl_checked = ALIGN_LEFT; - } else if (align == ALIGN_LEFT) { - align_rtl_checked = ALIGN_RIGHT; + if (alignment == HORIZONTAL_ALIGNMENT_RIGHT) { + align_rtl_checked = HORIZONTAL_ALIGNMENT_LEFT; + } else if (alignment == HORIZONTAL_ALIGNMENT_LEFT) { + align_rtl_checked = HORIZONTAL_ALIGNMENT_RIGHT; } } if (!_icon.is_null()) { int valign = size.height - style->get_minimum_size().y; - if (is_disabled()) { - color_icon.a = 0.4; - } float icon_ofs_region = 0.0; Point2 style_offset; Size2 icon_size = _icon->get_size(); - if (icon_align_rtl_checked == ALIGN_LEFT) { + 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("hseparation")); } - } else if (icon_align_rtl_checked == ALIGN_CENTER) { + } else if (icon_align_rtl_checked == HORIZONTAL_ALIGNMENT_CENTER) { style_offset.x = 0.0; - } else if (icon_align_rtl_checked == ALIGN_RIGHT) { + } 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("hseparation")); @@ -258,7 +259,7 @@ void Button::_notification(int p_what) { if (expand_icon) { Size2 _size = get_size() - style->get_offset() * 2; _size.width -= get_theme_constant(SNAME("hseparation")) + icon_ofs_region; - if (!clip_text && icon_align_rtl_checked != ALIGN_CENTER) { + if (!clip_text && icon_align_rtl_checked != HORIZONTAL_ALIGNMENT_CENTER) { _size.width -= text_buf->get_size().width; } float icon_width = _icon->get_width() * _size.height / _icon->get_height(); @@ -272,9 +273,9 @@ void Button::_notification(int p_what) { icon_size = Size2(icon_width, icon_height); } - if (icon_align_rtl_checked == ALIGN_LEFT) { + if (icon_align_rtl_checked == HORIZONTAL_ALIGNMENT_LEFT) { icon_region = Rect2(style_offset + Point2(icon_ofs_region, Math::floor((valign - icon_size.y) * 0.5)), icon_size); - } else if (icon_align_rtl_checked == ALIGN_CENTER) { + } else if (icon_align_rtl_checked == HORIZONTAL_ALIGNMENT_CENTER) { icon_region = Rect2(style_offset + Point2(icon_ofs_region + Math::floor((size.x - icon_size.x) * 0.5), Math::floor((valign - icon_size.y) * 0.5)), icon_size); } else { icon_region = Rect2(style_offset + Point2(icon_ofs_region + size.x - icon_size.x, Math::floor((valign - icon_size.y) * 0.5)), icon_size); @@ -286,7 +287,7 @@ void Button::_notification(int p_what) { } Point2 icon_ofs = !_icon.is_null() ? Point2(icon_region.size.width + get_theme_constant(SNAME("hseparation")), 0) : Point2(); - if (align_rtl_checked == ALIGN_CENTER && icon_align_rtl_checked == ALIGN_CENTER) { + if (align_rtl_checked == HORIZONTAL_ALIGNMENT_CENTER && icon_align_rtl_checked == HORIZONTAL_ALIGNMENT_CENTER) { icon_ofs.x = 0.0; } int text_clip = size.width - style->get_minimum_size().width - icon_ofs.width; @@ -303,9 +304,12 @@ void Button::_notification(int p_what) { 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; + text_buf->set_alignment(align_rtl_checked); + text_buf->set_width(text_width); switch (align_rtl_checked) { - case ALIGN_LEFT: { - if (icon_align_rtl_checked != ALIGN_LEFT) { + case HORIZONTAL_ALIGNMENT_FILL: + case HORIZONTAL_ALIGNMENT_LEFT: { + if (icon_align_rtl_checked != HORIZONTAL_ALIGNMENT_LEFT) { icon_ofs.x = 0.0; } if (_internal_margin[SIDE_LEFT] > 0) { @@ -315,23 +319,23 @@ void Button::_notification(int p_what) { } text_ofs.y += style->get_offset().y; } break; - case ALIGN_CENTER: { + case HORIZONTAL_ALIGNMENT_CENTER: { if (text_ofs.x < 0) { text_ofs.x = 0; } - if (icon_align_rtl_checked == ALIGN_LEFT) { + if (icon_align_rtl_checked == HORIZONTAL_ALIGNMENT_LEFT) { text_ofs += icon_ofs; } text_ofs += style->get_offset(); } break; - case ALIGN_RIGHT: { + 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("hseparation")); } else { text_ofs.x = size.x - style->get_margin(SIDE_RIGHT) - text_width; } text_ofs.y += style->get_offset().y; - if (icon_align_rtl_checked == ALIGN_RIGHT) { + if (icon_align_rtl_checked == HORIZONTAL_ALIGNMENT_RIGHT) { text_ofs.x -= icon_ofs.x; } } break; @@ -358,7 +362,7 @@ void Button::_shape() { } else { text_buf->set_direction((TextServer::Direction)text_direction); } - text_buf->add_string(xl_text, font, font_size, opentype_features, (language != "") ? language : TranslationServer::get_singleton()->get_tool_locale()); + text_buf->add_string(xl_text, font, font_size, opentype_features, (!language.is_empty()) ? language : TranslationServer::get_singleton()->get_tool_locale()); } void Button::set_text(const String &p_text) { @@ -368,7 +372,7 @@ void Button::set_text(const String &p_text) { _shape(); update(); - minimum_size_changed(); + update_minimum_size(); } } @@ -428,7 +432,7 @@ void Button::set_icon(const Ref<Texture2D> &p_icon) { if (icon != p_icon) { icon = p_icon; update(); - minimum_size_changed(); + update_minimum_size(); } } @@ -440,7 +444,7 @@ void Button::set_expand_icon(bool p_enabled) { if (expand_icon != p_enabled) { expand_icon = p_enabled; update(); - minimum_size_changed(); + update_minimum_size(); } } @@ -463,7 +467,7 @@ void Button::set_clip_text(bool p_enabled) { if (clip_text != p_enabled) { clip_text = p_enabled; update(); - minimum_size_changed(); + update_minimum_size(); } } @@ -471,25 +475,25 @@ bool Button::get_clip_text() const { return clip_text; } -void Button::set_text_align(TextAlign p_align) { - if (align != p_align) { - align = p_align; +void Button::set_text_alignment(HorizontalAlignment p_alignment) { + if (alignment != p_alignment) { + alignment = p_alignment; update(); } } -Button::TextAlign Button::get_text_align() const { - return align; +HorizontalAlignment Button::get_text_alignment() const { + return alignment; } -void Button::set_icon_align(TextAlign p_align) { - icon_align = p_align; - minimum_size_changed(); +void Button::set_icon_alignment(HorizontalAlignment p_alignment) { + icon_alignment = p_alignment; + update_minimum_size(); update(); } -Button::TextAlign Button::get_icon_align() const { - return icon_align; +HorizontalAlignment Button::get_icon_alignment() const { + return icon_alignment; } bool Button::_set(const StringName &p_name, const Variant &p_value) { @@ -497,7 +501,7 @@ bool Button::_set(const StringName &p_name, const Variant &p_value) { if (str.begins_with("opentype_features/")) { String name = str.get_slicec('/', 1); int32_t tag = TS->name_to_tag(name); - double value = p_value; + int value = p_value; if (value == -1) { if (opentype_features.has(tag)) { opentype_features.erase(tag); @@ -505,7 +509,7 @@ bool Button::_set(const StringName &p_name, const Variant &p_value) { update(); } } else { - if ((double)opentype_features[tag] != value) { + if (!opentype_features.has(tag) || (int)opentype_features[tag] != value) { opentype_features[tag] = value; _shape(); update(); @@ -537,7 +541,7 @@ bool Button::_get(const StringName &p_name, Variant &r_ret) const { void Button::_get_property_list(List<PropertyInfo> *p_list) const { for (const Variant *ftr = opentype_features.next(nullptr); ftr != nullptr; ftr = opentype_features.next(ftr)) { String name = TS->tag_to_name(*ftr); - p_list->push_back(PropertyInfo(Variant::FLOAT, "opentype_features/" + name)); + p_list->push_back(PropertyInfo(Variant::INT, "opentype_features/" + name)); } p_list->push_back(PropertyInfo(Variant::NIL, "opentype_features/_new", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_EDITOR)); } @@ -558,33 +562,29 @@ void Button::_bind_methods() { ClassDB::bind_method(D_METHOD("is_flat"), &Button::is_flat); ClassDB::bind_method(D_METHOD("set_clip_text", "enabled"), &Button::set_clip_text); ClassDB::bind_method(D_METHOD("get_clip_text"), &Button::get_clip_text); - ClassDB::bind_method(D_METHOD("set_text_align", "align"), &Button::set_text_align); - ClassDB::bind_method(D_METHOD("get_text_align"), &Button::get_text_align); - ClassDB::bind_method(D_METHOD("set_icon_align", "icon_align"), &Button::set_icon_align); - ClassDB::bind_method(D_METHOD("get_icon_align"), &Button::get_icon_align); + ClassDB::bind_method(D_METHOD("set_text_alignment", "alignment"), &Button::set_text_alignment); + ClassDB::bind_method(D_METHOD("get_text_alignment"), &Button::get_text_alignment); + ClassDB::bind_method(D_METHOD("set_icon_alignment", "icon_alignment"), &Button::set_icon_alignment); + ClassDB::bind_method(D_METHOD("get_icon_alignment"), &Button::get_icon_alignment); ClassDB::bind_method(D_METHOD("set_expand_icon", "enabled"), &Button::set_expand_icon); ClassDB::bind_method(D_METHOD("is_expand_icon"), &Button::is_expand_icon); - BIND_ENUM_CONSTANT(ALIGN_LEFT); - BIND_ENUM_CONSTANT(ALIGN_CENTER); - BIND_ENUM_CONSTANT(ALIGN_RIGHT); - ADD_PROPERTY(PropertyInfo(Variant::STRING, "text", PROPERTY_HINT_MULTILINE_TEXT, "", PROPERTY_USAGE_DEFAULT_INTL), "set_text", "get_text"); 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"), "set_language", "get_language"); + ADD_PROPERTY(PropertyInfo(Variant::STRING, "language", PROPERTY_HINT_LOCALE_ID, ""), "set_language", "get_language"); ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "icon", PROPERTY_HINT_RESOURCE_TYPE, "Texture2D"), "set_button_icon", "get_button_icon"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "flat"), "set_flat", "is_flat"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "clip_text"), "set_clip_text", "get_clip_text"); - ADD_PROPERTY(PropertyInfo(Variant::INT, "align", PROPERTY_HINT_ENUM, "Left,Center,Right"), "set_text_align", "get_text_align"); - ADD_PROPERTY(PropertyInfo(Variant::INT, "icon_align", PROPERTY_HINT_ENUM, "Left,Center,Right"), "set_icon_align", "get_icon_align"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "alignment", PROPERTY_HINT_ENUM, "Left,Center,Right"), "set_text_alignment", "get_text_alignment"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "icon_alignment", PROPERTY_HINT_ENUM, "Left,Center,Right"), "set_icon_alignment", "get_icon_alignment"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "expand_icon"), "set_expand_icon", "is_expand_icon"); } Button::Button(const String &p_text) { text_buf.instantiate(); text_buf->set_flags(TextServer::BREAK_MANDATORY); - set_mouse_filter(MOUSE_FILTER_STOP); + set_text(p_text); } diff --git a/scene/gui/button.h b/scene/gui/button.h index fd36cb77af..1abf86c986 100644 --- a/scene/gui/button.h +++ b/scene/gui/button.h @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ @@ -37,13 +37,6 @@ class Button : public BaseButton { GDCLASS(Button, BaseButton); -public: - enum TextAlign { - ALIGN_LEFT, - ALIGN_CENTER, - ALIGN_RIGHT - }; - private: bool flat = false; String text; @@ -57,8 +50,8 @@ private: Ref<Texture2D> icon; bool expand_icon = false; bool clip_text = false; - TextAlign align = ALIGN_CENTER; - TextAlign icon_align = ALIGN_LEFT; + HorizontalAlignment alignment = HORIZONTAL_ALIGNMENT_CENTER; + HorizontalAlignment icon_alignment = HORIZONTAL_ALIGNMENT_LEFT; float _internal_margin[4] = {}; void _shape(); @@ -100,16 +93,14 @@ public: void set_clip_text(bool p_enabled); bool get_clip_text() const; - void set_text_align(TextAlign p_align); - TextAlign get_text_align() const; + void set_text_alignment(HorizontalAlignment p_alignment); + HorizontalAlignment get_text_alignment() const; - void set_icon_align(TextAlign p_align); - TextAlign get_icon_align() const; + void set_icon_alignment(HorizontalAlignment p_alignment); + HorizontalAlignment get_icon_alignment() const; Button(const String &p_text = String()); ~Button(); }; -VARIANT_ENUM_CAST(Button::TextAlign); - #endif diff --git a/scene/gui/center_container.cpp b/scene/gui/center_container.cpp index 909516e7ef..33ec4006ae 100644 --- a/scene/gui/center_container.cpp +++ b/scene/gui/center_container.cpp @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ @@ -61,7 +61,7 @@ void CenterContainer::set_use_top_left(bool p_enable) { use_top_left = p_enable; - minimum_size_changed(); + update_minimum_size(); queue_sort(); } @@ -69,22 +69,32 @@ bool CenterContainer::is_using_top_left() const { return use_top_left; } +Vector<int> CenterContainer::get_allowed_size_flags_horizontal() const { + return Vector<int>(); +} + +Vector<int> CenterContainer::get_allowed_size_flags_vertical() const { + return Vector<int>(); +} + void CenterContainer::_notification(int p_what) { - if (p_what == NOTIFICATION_SORT_CHILDREN) { - Size2 size = get_size(); - for (int i = 0; i < get_child_count(); i++) { - Control *c = Object::cast_to<Control>(get_child(i)); - if (!c) { - continue; - } - if (c->is_set_as_top_level()) { - continue; - } + switch (p_what) { + case NOTIFICATION_SORT_CHILDREN: { + Size2 size = get_size(); + for (int i = 0; i < get_child_count(); i++) { + Control *c = Object::cast_to<Control>(get_child(i)); + if (!c) { + continue; + } + if (c->is_set_as_top_level()) { + continue; + } - Size2 minsize = c->get_combined_minimum_size(); - Point2 ofs = use_top_left ? (-minsize * 0.5).floor() : ((size - minsize) / 2.0).floor(); - fit_child_in_rect(c, Rect2(ofs, minsize)); - } + Size2 minsize = c->get_combined_minimum_size(); + Point2 ofs = use_top_left ? (-minsize * 0.5).floor() : ((size - minsize) / 2.0).floor(); + fit_child_in_rect(c, Rect2(ofs, minsize)); + } + } break; } } diff --git a/scene/gui/center_container.h b/scene/gui/center_container.h index 0944f200fc..c35e0c4e29 100644 --- a/scene/gui/center_container.h +++ b/scene/gui/center_container.h @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ @@ -48,6 +48,9 @@ public: virtual Size2 get_minimum_size() const override; + virtual Vector<int> get_allowed_size_flags_horizontal() const override; + virtual Vector<int> get_allowed_size_flags_vertical() const override; + CenterContainer(); }; diff --git a/scene/gui/check_box.cpp b/scene/gui/check_box.cpp index 411fb2e1f0..063a154bb2 100644 --- a/scene/gui/check_box.cpp +++ b/scene/gui/check_box.cpp @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ @@ -84,34 +84,40 @@ Size2 CheckBox::get_minimum_size() const { } void CheckBox::_notification(int p_what) { - if ((p_what == NOTIFICATION_THEME_CHANGED) || (p_what == NOTIFICATION_LAYOUT_DIRECTION_CHANGED || (p_what == NOTIFICATION_TRANSLATION_CHANGED))) { - if (is_layout_rtl()) { - _set_internal_margin(SIDE_LEFT, 0.f); - _set_internal_margin(SIDE_RIGHT, get_icon_size().width); - } else { - _set_internal_margin(SIDE_LEFT, get_icon_size().width); - _set_internal_margin(SIDE_RIGHT, 0.f); - } - } else if (p_what == 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")); - - Vector2 ofs; - if (is_layout_rtl()) { - ofs.x = get_size().x - sb->get_margin(SIDE_RIGHT) - get_icon_size().width; - } else { - ofs.x = sb->get_margin(SIDE_LEFT); - } - ofs.y = int((get_size().height - get_icon_size().height) / 2) + get_theme_constant(SNAME("check_vadjust")); - - if (is_pressed()) { - on->draw(ci, ofs); - } else { - off->draw(ci, ofs); - } + switch (p_what) { + case NOTIFICATION_THEME_CHANGED: + case NOTIFICATION_LAYOUT_DIRECTION_CHANGED: + case NOTIFICATION_TRANSLATION_CHANGED: { + if (is_layout_rtl()) { + _set_internal_margin(SIDE_LEFT, 0.f); + _set_internal_margin(SIDE_RIGHT, get_icon_size().width); + } else { + _set_internal_margin(SIDE_LEFT, get_icon_size().width); + _set_internal_margin(SIDE_RIGHT, 0.f); + } + } break; + + 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")); + + Vector2 ofs; + if (is_layout_rtl()) { + ofs.x = get_size().x - sb->get_margin(SIDE_RIGHT) - get_icon_size().width; + } else { + ofs.x = sb->get_margin(SIDE_LEFT); + } + ofs.y = int((get_size().height - get_icon_size().height) / 2) + get_theme_constant(SNAME("check_vadjust")); + + if (is_pressed()) { + on->draw(ci, ofs); + } else { + off->draw(ci, ofs); + } + } break; } } @@ -123,7 +129,7 @@ CheckBox::CheckBox(const String &p_text) : Button(p_text) { set_toggle_mode(true); - set_text_align(ALIGN_LEFT); + set_text_alignment(HORIZONTAL_ALIGNMENT_LEFT); if (is_layout_rtl()) { _set_internal_margin(SIDE_RIGHT, get_icon_size().width); diff --git a/scene/gui/check_box.h b/scene/gui/check_box.h index 9fb0aea218..fcdb2ce08c 100644 --- a/scene/gui/check_box.h +++ b/scene/gui/check_box.h @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ @@ -32,9 +32,7 @@ #define CHECK_BOX_H #include "scene/gui/button.h" -/** -@author Mariano Suligoy <marianognu.esyrpg@gmail.com> -*/ + class CheckBox : public Button { GDCLASS(CheckBox, Button); @@ -50,4 +48,4 @@ public: ~CheckBox(); }; -#endif +#endif // CHECK_BOX_H diff --git a/scene/gui/check_button.cpp b/scene/gui/check_button.cpp index 162a256d23..527b0061ac 100644 --- a/scene/gui/check_button.cpp +++ b/scene/gui/check_button.cpp @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ @@ -61,53 +61,62 @@ Size2 CheckButton::get_minimum_size() const { } void CheckButton::_notification(int p_what) { - if ((p_what == NOTIFICATION_THEME_CHANGED) || (p_what == NOTIFICATION_LAYOUT_DIRECTION_CHANGED) || (p_what == NOTIFICATION_TRANSLATION_CHANGED)) { - if (is_layout_rtl()) { - _set_internal_margin(SIDE_LEFT, get_icon_size().width); - _set_internal_margin(SIDE_RIGHT, 0.f); - } else { - _set_internal_margin(SIDE_LEFT, 0.f); - _set_internal_margin(SIDE_RIGHT, get_icon_size().width); - } - } else if (p_what == NOTIFICATION_DRAW) { - 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; - if (rtl) { - off = Control::get_theme_icon(is_disabled() ? "off_disabled_mirrored" : "off_mirrored"); - } else { - off = Control::get_theme_icon(is_disabled() ? "off_disabled" : "off"); - } - - 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); - } else { - ofs.x = get_size().width - (tex_size.width + sb->get_margin(SIDE_RIGHT)); - } - ofs.y = (get_size().height - tex_size.height) / 2 + get_theme_constant(SNAME("check_vadjust")); - - if (is_pressed()) { - on->draw(ci, ofs); - } else { - off->draw(ci, ofs); - } + switch (p_what) { + case NOTIFICATION_THEME_CHANGED: + case NOTIFICATION_LAYOUT_DIRECTION_CHANGED: + case NOTIFICATION_TRANSLATION_CHANGED: { + if (is_layout_rtl()) { + _set_internal_margin(SIDE_LEFT, get_icon_size().width); + _set_internal_margin(SIDE_RIGHT, 0.f); + } else { + _set_internal_margin(SIDE_LEFT, 0.f); + _set_internal_margin(SIDE_RIGHT, get_icon_size().width); + } + } break; + + case NOTIFICATION_DRAW: { + 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; + if (rtl) { + off = Control::get_theme_icon(is_disabled() ? "off_disabled_mirrored" : "off_mirrored"); + } else { + off = Control::get_theme_icon(is_disabled() ? "off_disabled" : "off"); + } + + 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); + } else { + ofs.x = get_size().width - (tex_size.width + sb->get_margin(SIDE_RIGHT)); + } + ofs.y = (get_size().height - tex_size.height) / 2 + get_theme_constant(SNAME("check_vadjust")); + + if (is_pressed()) { + on->draw(ci, ofs); + } else { + off->draw(ci, ofs); + } + } break; } } -CheckButton::CheckButton() { +CheckButton::CheckButton(const String &p_text) : + Button(p_text) { set_toggle_mode(true); - set_text_align(ALIGN_LEFT); + + set_text_alignment(HORIZONTAL_ALIGNMENT_LEFT); + if (is_layout_rtl()) { _set_internal_margin(SIDE_LEFT, get_icon_size().width); } else { diff --git a/scene/gui/check_button.h b/scene/gui/check_button.h index 29c557ce89..7d4bb8bdfc 100644 --- a/scene/gui/check_button.h +++ b/scene/gui/check_button.h @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ @@ -32,9 +32,7 @@ #define CHECK_BUTTON_H #include "scene/gui/button.h" -/** -@author Juan Linietsky <reduzio@gmail.com> -*/ + class CheckButton : public Button { GDCLASS(CheckButton, Button); @@ -44,8 +42,8 @@ protected: void _notification(int p_what); public: - CheckButton(); + CheckButton(const String &p_text = String()); ~CheckButton(); }; -#endif +#endif // CHECK_BUTTON_H diff --git a/scene/gui/code_edit.cpp b/scene/gui/code_edit.cpp index 046d256867..3fa0cec302 100644 --- a/scene/gui/code_edit.cpp +++ b/scene/gui/code_edit.cpp @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ @@ -34,14 +34,6 @@ #include "core/string/string_builder.h" #include "core/string/ustring.h" -static bool _is_whitespace(char32_t c) { - return c == '\t' || c == ' '; -} - -static bool _is_char(char32_t c) { - return !is_symbol(c); -} - void CodeEdit::_notification(int p_what) { switch (p_what) { case NOTIFICATION_THEME_CHANGED: @@ -82,6 +74,7 @@ void CodeEdit::_notification(int p_what) { line_length_guideline_color = get_theme_color(SNAME("line_length_guideline_color")); } break; + case NOTIFICATION_DRAW: { RID ci = get_canvas_item(); const Size2 size = get_size(); @@ -92,7 +85,7 @@ void CodeEdit::_notification(int p_what) { if (line_length_guideline_columns.size() > 0) { const int xmargin_beg = style_normal->get_margin(SIDE_LEFT) + get_total_gutter_width(); const int xmargin_end = size.width - style_normal->get_margin(SIDE_RIGHT) - (is_drawing_minimap() ? get_minimap_width() : 0); - const int char_size = (int)font->get_char_size('0', 0, font_size).width; + const float char_size = font->get_char_size('0', 0, font_size).width; for (int i = 0; i < line_length_guideline_columns.size(); i++) { const int xoffset = xmargin_beg + char_size * (int)line_length_guideline_columns[i] - get_h_scroll(); @@ -143,7 +136,6 @@ void CodeEdit::_notification(int p_what) { code_completion_line_ofs = CLAMP(code_completion_current_selected - lines / 2, 0, code_completion_options_count - lines); RenderingServer::get_singleton()->canvas_item_add_rect(ci, Rect2(Point2(code_completion_rect.position.x, code_completion_rect.position.y + (code_completion_current_selected - code_completion_line_ofs) * row_height), Size2(code_completion_rect.size.width, row_height)), code_completion_selected_color); - draw_rect(Rect2(code_completion_rect.position + Vector2(icon_area_size.x + icon_hsep, 0), Size2(MIN(code_completion_base_width, code_completion_rect.size.width - (icon_area_size.x + icon_hsep)), code_completion_rect.size.height)), code_completion_existing_color); for (int i = 0; i < lines; i++) { int l = code_completion_line_ofs + i; @@ -170,13 +162,24 @@ void CodeEdit::_notification(int p_what) { if (code_completion_options[l].default_value.get_type() == Variant::COLOR) { draw_rect(Rect2(Point2(code_completion_rect.position.x, icon_area.position.y), icon_area_size), (Color)code_completion_options[l].default_value); } - tl->set_align(HALIGN_RIGHT); + tl->set_horizontal_alignment(HORIZONTAL_ALIGNMENT_RIGHT); } else { if (code_completion_options[l].default_value.get_type() == Variant::COLOR) { draw_rect(Rect2(Point2(code_completion_rect.position.x + code_completion_rect.size.width - icon_area_size.x, icon_area.position.y), icon_area_size), (Color)code_completion_options[l].default_value); } - tl->set_align(HALIGN_LEFT); + tl->set_horizontal_alignment(HORIZONTAL_ALIGNMENT_LEFT); + } + + Point2 match_pos = Point2(code_completion_rect.position.x + icon_area_size.x + icon_hsep, code_completion_rect.position.y + i * row_height); + + for (int j = 0; j < code_completion_options[l].matches.size(); j++) { + Pair<int, int> match = code_completion_options[l].matches[j]; + int match_offset = font->get_string_size(code_completion_options[l].display.substr(0, match.first), font_size).width; + int match_len = font->get_string_size(code_completion_options[l].display.substr(match.first, match.second), font_size).width; + + draw_rect(Rect2(match_pos + Point2(match_offset, 0), Size2(match_len, row_height)), code_completion_existing_color); } + tl->draw(ci, title_pos, code_completion_options[l].font_color); } @@ -189,7 +192,7 @@ void CodeEdit::_notification(int p_what) { } /* Code hint */ - if (caret_visible && code_hint != "" && (!code_completion_active || (code_completion_below != code_hint_draw_below))) { + if (caret_visible && !code_hint.is_empty() && (!code_completion_active || (code_completion_below != code_hint_draw_below))) { const int font_height = font->get_height(font_size); Ref<StyleBox> sb = get_theme_stylebox(SNAME("panel"), SNAME("TooltipPanel")); Color font_color = get_theme_color(SNAME("font_color"), SNAME("TooltipLabel")); @@ -222,14 +225,14 @@ void CodeEdit::_notification(int p_what) { int begin = 0; int end = 0; - if (line.find(String::chr(0xFFFF)) != -1) { + if (line.contains(String::chr(0xFFFF))) { begin = font->get_string_size(line.substr(0, line.find(String::chr(0xFFFF))), font_size).x; end = font->get_string_size(line.substr(0, line.rfind(String::chr(0xFFFF))), font_size).x; } Point2 round_ofs = hint_ofs + sb->get_offset() + Vector2(0, font->get_ascent(font_size) + font_height * i + yofs); round_ofs = round_ofs.round(); - draw_string(font, round_ofs, line.replace(String::chr(0xFFFF), ""), HALIGN_LEFT, -1, font_size, font_color); + draw_string(font, round_ofs, line.replace(String::chr(0xFFFF), ""), HORIZONTAL_ALIGNMENT_LEFT, -1, font_size, font_color); if (end > 0) { // Draw an underline for the currently edited function parameter. const Vector2 b = hint_ofs + sb->get_offset() + Vector2(begin, font_height + font_height * i + yofs); @@ -263,19 +266,19 @@ void CodeEdit::gui_input(const Ref<InputEvent> &p_gui_input) { } switch (mb->get_button_index()) { - case MOUSE_BUTTON_WHEEL_UP: { + case MouseButton::WHEEL_UP: { if (code_completion_current_selected > 0) { code_completion_current_selected--; update(); } } break; - case MOUSE_BUTTON_WHEEL_DOWN: { + case MouseButton::WHEEL_DOWN: { if (code_completion_current_selected < code_completion_options.size() - 1) { code_completion_current_selected++; update(); } } break; - case MOUSE_BUTTON_LEFT: { + case MouseButton::LEFT: { code_completion_current_selected = CLAMP(code_completion_line_ofs + (mb->get_position().y - code_completion_rect.position.y) / get_line_height(), 0, code_completion_options.size() - 1); if (mb->is_double_click()) { confirm_code_completion(); @@ -296,11 +299,11 @@ void CodeEdit::gui_input(const Ref<InputEvent> &p_gui_input) { mpos.x = get_size().x - mpos.x; } - Point2i pos = get_line_column_at_pos(mpos); + Point2i pos = get_line_column_at_pos(mpos, false); int line = pos.y; int col = pos.x; - if (mb->get_button_index() == MOUSE_BUTTON_LEFT) { + if (line != -1 && mb->get_button_index() == MouseButton::LEFT) { if (is_line_folded(line)) { int wrap_index = get_line_wrap_index_at_column(line, col); if (wrap_index == get_line_wrap_count(line)) { @@ -314,18 +317,20 @@ void CodeEdit::gui_input(const Ref<InputEvent> &p_gui_input) { } } } else { - if (mb->get_button_index() == MOUSE_BUTTON_LEFT) { - if (mb->is_command_pressed() && symbol_lookup_word != String()) { + if (mb->get_button_index() == MouseButton::LEFT) { + if (mb->is_command_pressed() && !symbol_lookup_word.is_empty()) { Vector2i mpos = mb->get_position(); if (is_layout_rtl()) { mpos.x = get_size().x - mpos.x; } - Point2i pos = get_line_column_at_pos(mpos); + Point2i pos = get_line_column_at_pos(mpos, false); int line = pos.y; int col = pos.x; - emit_signal(SNAME("symbol_lookup"), symbol_lookup_word, line, col); + if (line != -1) { + emit_signal(SNAME("symbol_lookup"), symbol_lookup_word, line, col); + } return; } } @@ -340,7 +345,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() == 0 && !is_dragging_cursor()) { + if (mm->is_command_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); @@ -360,9 +365,9 @@ void CodeEdit::gui_input(const Ref<InputEvent> &p_gui_input) { /* Ctrl + Hover symbols */ #ifdef OSX_ENABLED - if (k->get_keycode() == KEY_META) { + if (k->get_keycode() == Key::META) { #else - if (k->get_keycode() == KEY_CTRL) { + if (k->get_keycode() == Key::CTRL) { #endif if (symbol_lookup_on_click_enabled) { if (k->is_pressed() && !is_dragging_cursor()) { @@ -378,7 +383,7 @@ void CodeEdit::gui_input(const Ref<InputEvent> &p_gui_input) { } /* If a modifier has been pressed, and nothing else, return. */ - if (!k->is_pressed() || k->get_keycode() == KEY_CTRL || k->get_keycode() == KEY_ALT || k->get_keycode() == KEY_SHIFT || k->get_keycode() == KEY_META) { + if (!k->is_pressed() || k->get_keycode() == Key::CTRL || k->get_keycode() == Key::ALT || k->get_keycode() == Key::SHIFT || k->get_keycode() == Key::META) { return; } @@ -528,7 +533,7 @@ void CodeEdit::gui_input(const Ref<InputEvent> &p_gui_input) { /* General overrides */ Control::CursorShape CodeEdit::get_cursor_shape(const Point2 &p_pos) const { - if (symbol_lookup_word != String()) { + if (!symbol_lookup_word.is_empty()) { return CURSOR_POINTING_HAND; } @@ -536,11 +541,11 @@ Control::CursorShape CodeEdit::get_cursor_shape(const Point2 &p_pos) const { return CURSOR_ARROW; } - Point2i pos = get_line_column_at_pos(p_pos); + Point2i pos = get_line_column_at_pos(p_pos, false); int line = pos.y; int col = pos.x; - if (is_line_folded(line)) { + if (line != -1 && is_line_folded(line)) { int wrap_index = get_line_wrap_index_at_column(line, col); if (wrap_index == get_line_wrap_count(line)) { int eol_icon_width = folded_eol_icon->get_width(); @@ -559,6 +564,8 @@ Control::CursorShape CodeEdit::get_cursor_shape(const Point2 &p_pos) const { // Overridable actions void CodeEdit::_handle_unicode_input_internal(const uint32_t p_unicode) { bool had_selection = has_selection(); + String selection_text = (had_selection ? get_selected_text() : ""); + if (had_selection) { begin_complex_operation(); delete_selection(); @@ -579,27 +586,38 @@ void CodeEdit::_handle_unicode_input_internal(const uint32_t p_unicode) { if (auto_brace_completion_enabled) { int cl = get_caret_line(); int cc = get_caret_column(); - int caret_move_offset = 1; - - int post_brace_pair = cc < get_line(cl).length() ? _get_auto_brace_pair_close_at_pos(cl, cc) : -1; - if (has_string_delimiter(chr) && cc > 0 && _is_char(get_line(cl)[cc - 1]) && post_brace_pair == -1) { - insert_text_at_caret(chr); - } else if (cc < get_line(cl).length() && _is_char(get_line(cl)[cc])) { - insert_text_at_caret(chr); - } else if (post_brace_pair != -1 && auto_brace_completion_pairs[post_brace_pair].close_key[0] == chr[0]) { - caret_move_offset = auto_brace_completion_pairs[post_brace_pair].close_key.length(); - } else if (is_in_comment(cl, cc) != -1 || (is_in_string(cl, cc) != -1 && has_string_delimiter(chr))) { + if (had_selection) { insert_text_at_caret(chr); + + String close_key = get_auto_brace_completion_close_key(chr); + if (!close_key.is_empty()) { + insert_text_at_caret(selection_text + close_key); + set_caret_column(get_caret_column() - 1); + } } else { - insert_text_at_caret(chr); + int caret_move_offset = 1; + + int post_brace_pair = cc < get_line(cl).length() ? _get_auto_brace_pair_close_at_pos(cl, cc) : -1; + + if (has_string_delimiter(chr) && cc > 0 && !is_symbol(get_line(cl)[cc - 1]) && post_brace_pair == -1) { + insert_text_at_caret(chr); + } else if (cc < get_line(cl).length() && !is_symbol(get_line(cl)[cc])) { + insert_text_at_caret(chr); + } else if (post_brace_pair != -1 && auto_brace_completion_pairs[post_brace_pair].close_key[0] == chr[0]) { + caret_move_offset = auto_brace_completion_pairs[post_brace_pair].close_key.length(); + } else if (is_in_comment(cl, cc) != -1 || (is_in_string(cl, cc) != -1 && has_string_delimiter(chr))) { + insert_text_at_caret(chr); + } else { + insert_text_at_caret(chr); - int pre_brace_pair = _get_auto_brace_pair_open_at_pos(cl, cc + 1); - if (pre_brace_pair != -1) { - insert_text_at_caret(auto_brace_completion_pairs[pre_brace_pair].close_key); + int pre_brace_pair = _get_auto_brace_pair_open_at_pos(cl, cc + 1); + if (pre_brace_pair != -1) { + insert_text_at_caret(auto_brace_completion_pairs[pre_brace_pair].close_key); + } } + set_caret_column(cc + caret_move_offset); } - set_caret_column(cc + caret_move_offset); } else { insert_text_at_caret(chr); } @@ -925,8 +943,10 @@ void CodeEdit::_new_line(bool p_split_current_line, bool p_above) { return; } - const int cc = get_caret_column(); + /* When not splitting the line, we need to factor in indentation from the end of the current line. */ + const int cc = p_split_current_line ? get_caret_column() : get_line(get_caret_line()).length(); const int cl = get_caret_line(); + const String line = get_line(cl); String ins = "\n"; @@ -974,7 +994,7 @@ void CodeEdit::_new_line(bool p_split_current_line, bool p_above) { } /* Make sure this is the last char, trailing whitespace or comments are okay. */ - if (should_indent && (!_is_whitespace(c) && is_in_comment(cl, cc) == -1)) { + if (should_indent && (!is_whitespace(c) && is_in_comment(cl, cc) == -1)) { should_indent = false; } } @@ -1000,6 +1020,8 @@ void CodeEdit::_new_line(bool p_split_current_line, bool p_above) { bool first_line = false; if (!p_split_current_line) { + deselect(); + if (p_above) { if (cl > 0) { set_caret_line(cl - 1, false); @@ -1417,40 +1439,23 @@ void CodeEdit::fold_line(int p_line) { /* End line is the same therefore we have a block of single line delimiters. */ if (end_line == p_line) { for (int i = p_line + 1; i <= line_count; i++) { - if (i == line_count) { - end_line = line_count; - break; - } - if ((in_string != -1 && is_in_string(i) == -1) || (in_comment != -1 && is_in_comment(i) == -1)) { - end_line = i - 1; break; } + end_line = i; } } } else { int start_indent = get_indent_level(p_line); for (int i = p_line + 1; i <= line_count; i++) { - if (get_line(p_line).strip_edges().size() == 0 || is_in_string(i) != -1 || is_in_comment(i) != -1) { - end_line = i; + if (get_line(i).strip_edges().size() == 0) { continue; } - - if (i == line_count) { - /* Do not fold empty last line of script if any */ + if (get_indent_level(i) > start_indent) { end_line = i; - if (get_line(i).strip_edges().size() == 0) { - end_line--; - } - break; + continue; } - - if ((get_indent_level(i) <= start_indent && get_line(i).strip_edges().size() != 0)) { - /* Keep an empty line unfolded if any */ - end_line = i - 1; - if (get_line(i - 1).strip_edges().size() == 0 && i - 2 > p_line) { - end_line = i - 2; - } + if (is_in_string(i) == -1 && is_in_comment(i) == -1) { break; } } @@ -1798,17 +1803,17 @@ void CodeEdit::request_code_completion(bool p_force) { } if (p_force) { - emit_signal(SNAME("request_code_completion")); + emit_signal(SNAME("code_completion_requested")); return; } String line = get_line(get_caret_line()); int ofs = CLAMP(get_caret_column(), 0, line.length()); - if (ofs > 0 && (is_in_string(get_caret_line(), ofs) != -1 || _is_char(line[ofs - 1]) || code_completion_prefixes.has(line[ofs - 1]))) { - emit_signal(SNAME("request_code_completion")); + if (ofs > 0 && (is_in_string(get_caret_line(), ofs) != -1 || !is_symbol(line[ofs - 1]) || code_completion_prefixes.has(line[ofs - 1]))) { + emit_signal(SNAME("code_completion_requested")); } else if (ofs > 1 && line[ofs - 1] == ' ' && code_completion_prefixes.has(line[ofs - 2])) { - emit_signal(SNAME("request_code_completion")); + emit_signal(SNAME("code_completion_requested")); } } @@ -1914,7 +1919,7 @@ void CodeEdit::confirm_code_completion(bool p_replace) { if (merge_text) { for (; caret_col < line.length(); caret_col++) { - if (!_is_char(line[caret_col])) { + if (is_symbol(line[caret_col])) { break; } } @@ -2016,10 +2021,14 @@ bool CodeEdit::is_symbol_lookup_on_click_enabled() const { String CodeEdit::get_text_for_symbol_lookup() { Point2i mp = get_local_mouse_pos(); - Point2i pos = get_line_column_at_pos(mp); + Point2i pos = get_line_column_at_pos(mp, false); int line = pos.y; int col = pos.x; + if (line == -1) { + return String(); + } + StringBuilder lookup_text; const int text_size = get_line_count(); for (int i = 0; i < text_size; i++) { @@ -2086,8 +2095,6 @@ void CodeEdit::_bind_methods() { ClassDB::bind_method(D_METHOD("get_auto_brace_completion_close_key", "open_key"), &CodeEdit::get_auto_brace_completion_close_key); /* Main Gutter */ - ClassDB::bind_method(D_METHOD("_main_gutter_draw_callback"), &CodeEdit::_main_gutter_draw_callback); - ClassDB::bind_method(D_METHOD("set_draw_breakpoints_gutter", "enable"), &CodeEdit::set_draw_breakpoints_gutter); ClassDB::bind_method(D_METHOD("is_drawing_breakpoints_gutter"), &CodeEdit::is_drawing_breakpoints_gutter); @@ -2116,16 +2123,12 @@ void CodeEdit::_bind_methods() { ClassDB::bind_method(D_METHOD("get_executing_lines"), &CodeEdit::get_executing_lines); /* Line numbers */ - ClassDB::bind_method(D_METHOD("_line_number_draw_callback"), &CodeEdit::_line_number_draw_callback); - ClassDB::bind_method(D_METHOD("set_draw_line_numbers", "enable"), &CodeEdit::set_draw_line_numbers); ClassDB::bind_method(D_METHOD("is_draw_line_numbers_enabled"), &CodeEdit::is_draw_line_numbers_enabled); ClassDB::bind_method(D_METHOD("set_line_numbers_zero_padded", "enable"), &CodeEdit::set_line_numbers_zero_padded); ClassDB::bind_method(D_METHOD("is_line_numbers_zero_padded"), &CodeEdit::is_line_numbers_zero_padded); /* Fold Gutter */ - ClassDB::bind_method(D_METHOD("_fold_gutter_draw_callback"), &CodeEdit::_fold_gutter_draw_callback); - ClassDB::bind_method(D_METHOD("set_draw_fold_gutter", "enable"), &CodeEdit::set_draw_fold_gutter); ClassDB::bind_method(D_METHOD("is_drawing_fold_gutter"), &CodeEdit::is_drawing_fold_gutter); @@ -2268,7 +2271,7 @@ void CodeEdit::_bind_methods() { ADD_SIGNAL(MethodInfo("breakpoint_toggled", PropertyInfo(Variant::INT, "line"))); /* Code Completion */ - ADD_SIGNAL(MethodInfo("request_code_completion")); + ADD_SIGNAL(MethodInfo("code_completion_requested")); /* Symbol lookup */ ADD_SIGNAL(MethodInfo("symbol_lookup", PropertyInfo(Variant::STRING, "symbol"), PropertyInfo(Variant::INT, "line"), PropertyInfo(Variant::INT, "column"))); @@ -2389,7 +2392,7 @@ void CodeEdit::_update_delimiter_cache(int p_from_line, int p_to_line) { if (start_line != end_line) { if (p_to_line < p_from_line) { for (int i = end_line; i > start_line; i--) { - delimiter_cache.remove(i); + delimiter_cache.remove_at(i); } } else { for (int i = start_line; i < end_line; i++) { @@ -2552,7 +2555,7 @@ int CodeEdit::_is_in_delimiter(int p_line, int p_column, DelimiterType p_type) c region = E->value(); in_region = true; for (int i = E->key() - 2; i >= 0; i--) { - if (!_is_whitespace(line[i])) { + if (!is_whitespace(line[i])) { return -1; } } @@ -2571,7 +2574,7 @@ int CodeEdit::_is_in_delimiter(int p_line, int p_column, DelimiterType p_type) c } for (int i = end_col; i < line.length(); i++) { - if (!_is_whitespace(line[i])) { + if (!is_whitespace(line[i])) { return -1; } } @@ -2608,7 +2611,7 @@ void CodeEdit::_add_delimiter(const String &p_start_key, const String &p_end_key delimiter.type = p_type; delimiter.start_key = p_start_key; delimiter.end_key = p_end_key; - delimiter.line_only = p_line_only || p_end_key == ""; + delimiter.line_only = p_line_only || p_end_key.is_empty(); delimiters.insert(at, delimiter); if (!setting_delimiters) { delimiter_cache.clear(); @@ -2626,7 +2629,7 @@ void CodeEdit::_remove_delimiter(const String &p_start_key, DelimiterType p_type break; } - delimiters.remove(i); + delimiters.remove_at(i); if (!setting_delimiters) { delimiter_cache.clear(); _update_delimiter_cache(); @@ -2658,7 +2661,7 @@ void CodeEdit::_set_delimiters(const TypedArray<String> &p_delimiters, Delimiter const String start_key = key.get_slice(" ", 0); const String end_key = key.get_slice_count(" ") > 1 ? key.get_slice(" ", 1) : String(); - _add_delimiter(start_key, end_key, end_key == "", p_type); + _add_delimiter(start_key, end_key, end_key.is_empty(), p_type); } setting_delimiters = false; _update_delimiter_cache(); @@ -2667,7 +2670,7 @@ void CodeEdit::_set_delimiters(const TypedArray<String> &p_delimiters, Delimiter void CodeEdit::_clear_delimiters(DelimiterType p_type) { for (int i = delimiters.size() - 1; i >= 0; i--) { if (delimiters[i].type == p_type) { - delimiters.remove(i); + delimiters.remove_at(i); } } delimiter_cache.clear(); @@ -2787,11 +2790,11 @@ void CodeEdit::_filter_code_completion_candidates_impl() { while (ofs > 0 && line[ofs] == ' ') { ofs--; } - prev_is_word = _is_char(line[ofs]); + prev_is_word = !is_symbol(line[ofs]); /* Otherwise get current word and set cofs to the start. */ } else { int start_cofs = cofs; - while (cofs > 0 && line[cofs - 1] > 32 && (line[cofs - 1] == '/' || _is_char(line[cofs - 1]))) { + while (cofs > 0 && line[cofs - 1] > 32 && (line[cofs - 1] == '/' || !is_symbol(line[cofs - 1]))) { cofs--; } string_to_complete = line.substr(cofs, start_cofs - cofs); @@ -2819,6 +2822,8 @@ void CodeEdit::_filter_code_completion_candidates_impl() { code_completion_base = string_to_complete; Vector<ScriptCodeCompletionOption> completion_options_casei; + Vector<ScriptCodeCompletionOption> completion_options_substr; + Vector<ScriptCodeCompletionOption> completion_options_substr_casei; Vector<ScriptCodeCompletionOption> completion_options_subseq; Vector<ScriptCodeCompletionOption> completion_options_subseq_casei; @@ -2858,7 +2863,7 @@ void CodeEdit::_filter_code_completion_candidates_impl() { completion_options_casei.push_back(option); } else if (s.is_subsequence_of(option.display)) { completion_options_subseq.push_back(option); - } else if (s.is_subsequence_ofi(option.display)) { + } else if (s.is_subsequence_ofn(option.display)) { completion_options_subseq_casei.push_back(option); } @@ -2873,35 +2878,100 @@ void CodeEdit::_filter_code_completion_candidates_impl() { const char32_t *tgt = &option.display[0]; const char32_t *tgt_lower = &display_lower[0]; - const char32_t *ssq_last_tgt = nullptr; - const char32_t *ssq_lower_last_tgt = nullptr; + const char32_t *sst = &string_to_complete[0]; + const char32_t *sst_lower = &display_lower[0]; + + Vector<Pair<int, int>> ssq_matches; + int ssq_match_start = 0; + int ssq_match_len = 0; + + Vector<Pair<int, int>> ssq_lower_matches; + int ssq_lower_match_start = 0; + int ssq_lower_match_len = 0; + + int sst_start = -1; + int sst_lower_start = -1; - for (; *tgt; tgt++, tgt_lower++) { + for (int i = 0; *tgt; tgt++, tgt_lower++, i++) { + // Check substring. + if (*sst == *tgt) { + sst++; + if (sst_start == -1) { + sst_start = i; + } + } else if (sst_start != -1 && *sst) { + sst = &string_to_complete[0]; + sst_start = -1; + } + + // Check subsequence. if (*ssq == *tgt) { ssq++; - ssq_last_tgt = tgt; + if (ssq_match_len == 0) { + ssq_match_start = i; + } + ssq_match_len++; + } else if (ssq_match_len > 0) { + ssq_matches.push_back(Pair<int, int>(ssq_match_start, ssq_match_len)); + ssq_match_len = 0; + } + + // Check lower substring. + if (*sst_lower == *tgt) { + sst_lower++; + if (sst_lower_start == -1) { + sst_lower_start = i; + } + } else if (sst_lower_start != -1 && *sst_lower) { + sst_lower = &string_to_complete[0]; + sst_lower_start = -1; } + + // Check lower subsequence. if (*ssq_lower == *tgt_lower) { ssq_lower++; - ssq_lower_last_tgt = tgt; + if (ssq_lower_match_len == 0) { + ssq_lower_match_start = i; + } + ssq_lower_match_len++; + } else if (ssq_lower_match_len > 0) { + ssq_lower_matches.push_back(Pair<int, int>(ssq_lower_match_start, ssq_lower_match_len)); + ssq_lower_match_len = 0; } } /* Matched the whole subsequence in s. */ - if (!*ssq) { - /* Finished matching in the first s.length() characters. */ - if (ssq_last_tgt == &option.display[string_to_complete.length() - 1]) { + if (!*ssq) { // Matched the whole subsequence in s. + option.matches.clear(); + + if (sst_start == 0) { // Matched substring in beginning of s. + option.matches.push_back(Pair<int, int>(sst_start, string_to_complete.length())); code_completion_options.push_back(option); + } else if (sst_start > 0) { // Matched substring in s. + option.matches.push_back(Pair<int, int>(sst_start, string_to_complete.length())); + completion_options_substr.push_back(option); } else { + if (ssq_match_len > 0) { + ssq_matches.push_back(Pair<int, int>(ssq_match_start, ssq_match_len)); + } + option.matches.append_array(ssq_matches); completion_options_subseq.push_back(option); } max_width = MAX(max_width, font->get_string_size(option.display, font_size).width + offset); - /* Matched the whole subsequence in s_lower. */ - } else if (!*ssq_lower) { - /* Finished matching in the first s.length() characters. */ - if (ssq_lower_last_tgt == &option.display[string_to_complete.length() - 1]) { + } else if (!*ssq_lower) { // Matched the whole subsequence in s_lower. + option.matches.clear(); + + if (sst_lower_start == 0) { // Matched substring in beginning of s_lower. + option.matches.push_back(Pair<int, int>(sst_lower_start, string_to_complete.length())); completion_options_casei.push_back(option); + } else if (sst_lower_start > 0) { // Matched substring in s_lower. + option.matches.push_back(Pair<int, int>(sst_lower_start, string_to_complete.length())); + completion_options_substr_casei.push_back(option); } else { + if (ssq_lower_match_len > 0) { + ssq_lower_matches.push_back(Pair<int, int>(ssq_lower_match_start, ssq_lower_match_len)); + } + option.matches.append_array(ssq_lower_matches); completion_options_subseq_casei.push_back(option); } max_width = MAX(max_width, font->get_string_size(option.display, font_size).width + offset); @@ -3001,7 +3071,7 @@ CodeEdit::CodeEdit() { add_auto_brace_completion_pair("\"", "\""); add_auto_brace_completion_pair("\'", "\'"); - /* Delimiter traking */ + /* Delimiter tracking */ add_string_delimiter("\"", "\"", false); add_string_delimiter("\'", "\'", false); @@ -3018,7 +3088,7 @@ CodeEdit::CodeEdit() { set_gutter_draw(gutter_idx, false); set_gutter_overwritable(gutter_idx, true); set_gutter_type(gutter_idx, GUTTER_TYPE_CUSTOM); - set_gutter_custom_draw(gutter_idx, this, "_main_gutter_draw_callback"); + set_gutter_custom_draw(gutter_idx, callable_mp(this, &CodeEdit::_main_gutter_draw_callback)); gutter_idx++; /* Line numbers */ @@ -3026,7 +3096,7 @@ CodeEdit::CodeEdit() { set_gutter_name(gutter_idx, "line_numbers"); set_gutter_draw(gutter_idx, false); set_gutter_type(gutter_idx, GUTTER_TYPE_CUSTOM); - set_gutter_custom_draw(gutter_idx, this, "_line_number_draw_callback"); + set_gutter_custom_draw(gutter_idx, callable_mp(this, &CodeEdit::_line_number_draw_callback)); gutter_idx++; /* Fold Gutter */ @@ -3034,7 +3104,7 @@ CodeEdit::CodeEdit() { set_gutter_name(gutter_idx, "fold_gutter"); set_gutter_draw(gutter_idx, false); set_gutter_type(gutter_idx, GUTTER_TYPE_CUSTOM); - set_gutter_custom_draw(gutter_idx, this, "_fold_gutter_draw_callback"); + set_gutter_custom_draw(gutter_idx, callable_mp(this, &CodeEdit::_fold_gutter_draw_callback)); gutter_idx++; connect("lines_edited_from", callable_mp(this, &CodeEdit::_lines_edited_from)); diff --git a/scene/gui/code_edit.h b/scene/gui/code_edit.h index f0d971dd35..cb1309ced3 100644 --- a/scene/gui/code_edit.h +++ b/scene/gui/code_edit.h @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ diff --git a/scene/gui/color_picker.cpp b/scene/gui/color_picker.cpp index 894175d559..9f32ac223c 100644 --- a/scene/gui/color_picker.cpp +++ b/scene/gui/color_picker.cpp @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ @@ -33,11 +33,11 @@ #include "core/input/input.h" #include "core/os/keyboard.h" #include "core/os/os.h" +#include "scene/main/window.h" #ifdef TOOLS_ENABLED #include "editor/editor_settings.h" #endif -#include "scene/main/window.h" List<Color> ColorPicker::preset_cache; @@ -45,7 +45,6 @@ void ColorPicker::_notification(int p_what) { switch (p_what) { case NOTIFICATION_ENTER_TREE: { _update_color(); - #ifdef TOOLS_ENABLED if (Engine::get_singleton()->is_editor_hint()) { if (preset_cache.is_empty()) { @@ -582,7 +581,7 @@ void ColorPicker::_update_text_value() { void ColorPicker::_sample_input(const Ref<InputEvent> &p_event) { const Ref<InputEventMouseButton> mb = p_event; - if (mb.is_valid() && mb->is_pressed() && mb->get_button_index() == MOUSE_BUTTON_LEFT) { + if (mb.is_valid() && mb->is_pressed() && mb->get_button_index() == MouseButton::LEFT) { const Rect2 rect_old = Rect2(Point2(), Size2(sample->get_size().width * 0.5, sample->get_size().height * 0.95)); if (rect_old.has_point(mb->get_position())) { // Revert to the old color when left-clicking the old color sample. @@ -827,13 +826,13 @@ void ColorPicker::_uv_input(const Ref<InputEvent> &p_event, Control *c) { Ref<InputEventMouseButton> bev = p_event; if (bev.is_valid()) { - if (bev->is_pressed() && bev->get_button_index() == MOUSE_BUTTON_LEFT) { + if (bev->is_pressed() && bev->get_button_index() == MouseButton::LEFT) { Vector2 center = c->get_size() / 2.0; if (picker_type == SHAPE_VHS_CIRCLE) { real_t dist = center.distance_to(bev->get_position()); if (dist <= center.x) { - real_t rad = bev->get_position().angle_to_point(center); + real_t rad = center.angle_to_point(bev->get_position()); h = ((rad >= 0) ? rad : (Math_TAU + rad)) / Math_TAU; s = CLAMP(dist / center.x, 0, 1); } else { @@ -850,7 +849,7 @@ void ColorPicker::_uv_input(const Ref<InputEvent> &p_event, Control *c) { real_t dist = center.distance_to(bev->get_position()); if (dist >= center.x * 0.84 && dist <= center.x) { - real_t rad = bev->get_position().angle_to_point(center); + real_t rad = center.angle_to_point(bev->get_position()); h = ((rad >= 0) ? rad : (Math_TAU + rad)) / Math_TAU; spinning = true; } else { @@ -875,7 +874,7 @@ void ColorPicker::_uv_input(const Ref<InputEvent> &p_event, Control *c) { if (!deferred_mode_enabled) { emit_signal(SNAME("color_changed"), color); } - } else if (deferred_mode_enabled && !bev->is_pressed() && bev->get_button_index() == MOUSE_BUTTON_LEFT) { + } else if (deferred_mode_enabled && !bev->is_pressed() && bev->get_button_index() == MouseButton::LEFT) { emit_signal(SNAME("color_changed"), color); changing_color = false; spinning = false; @@ -895,12 +894,12 @@ void ColorPicker::_uv_input(const Ref<InputEvent> &p_event, Control *c) { Vector2 center = c->get_size() / 2.0; if (picker_type == SHAPE_VHS_CIRCLE) { real_t dist = center.distance_to(mev->get_position()); - real_t rad = mev->get_position().angle_to_point(center); + real_t rad = center.angle_to_point(mev->get_position()); h = ((rad >= 0) ? rad : (Math_TAU + rad)) / Math_TAU; s = CLAMP(dist / center.x, 0, 1); } else { if (spinning) { - real_t rad = mev->get_position().angle_to_point(center); + real_t rad = center.angle_to_point(mev->get_position()); h = ((rad >= 0) ? rad : (Math_TAU + rad)) / Math_TAU; } else { real_t corner_x = (c == wheel_uv) ? center.x - Math_SQRT12 * c->get_size().width * 0.42 : 0; @@ -929,7 +928,7 @@ void ColorPicker::_w_input(const Ref<InputEvent> &p_event) { Ref<InputEventMouseButton> bev = p_event; if (bev.is_valid()) { - if (bev->is_pressed() && bev->get_button_index() == MOUSE_BUTTON_LEFT) { + if (bev->is_pressed() && bev->get_button_index() == MouseButton::LEFT) { changing_color = true; float y = CLAMP((float)bev->get_position().y, 0, w_edit->get_size().height); if (picker_type == SHAPE_VHS_CIRCLE) { @@ -946,7 +945,7 @@ void ColorPicker::_w_input(const Ref<InputEvent> &p_event) { _update_color(); if (!deferred_mode_enabled) { emit_signal(SNAME("color_changed"), color); - } else if (!bev->is_pressed() && bev->get_button_index() == MOUSE_BUTTON_LEFT) { + } else if (!bev->is_pressed() && bev->get_button_index() == MouseButton::LEFT) { emit_signal(SNAME("color_changed"), color); } } @@ -977,11 +976,11 @@ void ColorPicker::_preset_input(const Ref<InputEvent> &p_event, const Color &p_c Ref<InputEventMouseButton> bev = p_event; if (bev.is_valid()) { - if (bev->is_pressed() && bev->get_button_index() == MOUSE_BUTTON_LEFT) { + if (bev->is_pressed() && bev->get_button_index() == MouseButton::LEFT) { set_pick_color(p_color); _update_color(); emit_signal(SNAME("color_changed"), p_color); - } else if (bev->is_pressed() && bev->get_button_index() == MOUSE_BUTTON_RIGHT && presets_enabled) { + } else if (bev->is_pressed() && bev->get_button_index() == MouseButton::RIGHT && presets_enabled) { erase_preset(p_color); emit_signal(SNAME("preset_removed"), p_color); } @@ -994,7 +993,7 @@ void ColorPicker::_screen_input(const Ref<InputEvent> &p_event) { } Ref<InputEventMouseButton> bev = p_event; - if (bev.is_valid() && bev->get_button_index() == MOUSE_BUTTON_LEFT && !bev->is_pressed()) { + if (bev.is_valid() && bev->get_button_index() == MouseButton::LEFT && !bev->is_pressed()) { emit_signal(SNAME("color_changed"), color); screen->hide(); } @@ -1274,7 +1273,7 @@ ColorPicker::ColorPicker() : preset_container->set_columns(preset_column_count); add_child(preset_container, false, INTERNAL_MODE_FRONT); - btn_add_preset->set_icon_align(Button::ALIGN_CENTER); + btn_add_preset->set_icon_alignment(HORIZONTAL_ALIGNMENT_CENTER); btn_add_preset->set_tooltip(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); @@ -1305,7 +1304,7 @@ void ColorPickerButton::pressed() { Size2 size = get_size() * get_viewport()->get_canvas_transform().get_scale(); - popup->set_as_minsize(); + popup->reset_size(); picker->_update_presets(); Rect2i usable_rect = popup->get_usable_parent_rect(); @@ -1347,17 +1346,18 @@ void ColorPickerButton::_notification(int p_what) { draw_texture(Control::get_theme_icon(SNAME("overbright_indicator"), SNAME("ColorPicker")), normal->get_offset()); } } break; + case NOTIFICATION_WM_CLOSE_REQUEST: { if (popup) { popup->hide(); } } break; - } - if (p_what == NOTIFICATION_VISIBILITY_CHANGED) { - if (popup && !is_visible_in_tree()) { - popup->hide(); - } + case NOTIFICATION_VISIBILITY_CHANGED: { + if (popup && !is_visible_in_tree()) { + popup->hide(); + } + } break; } } @@ -1429,7 +1429,8 @@ void ColorPickerButton::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::BOOL, "edit_alpha"), "set_edit_alpha", "is_editing_alpha"); } -ColorPickerButton::ColorPickerButton() { +ColorPickerButton::ColorPickerButton(const String &p_text) : + Button(p_text) { set_toggle_mode(true); } diff --git a/scene/gui/color_picker.h b/scene/gui/color_picker.h index ad4f5ad5b1..6f3e16009c 100644 --- a/scene/gui/color_picker.h +++ b/scene/gui/color_picker.h @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ @@ -231,7 +231,7 @@ public: ColorPicker *get_picker(); PopupPanel *get_popup(); - ColorPickerButton(); + ColorPickerButton(const String &p_text = String()); }; VARIANT_ENUM_CAST(ColorPicker::PickerShapeType); diff --git a/scene/gui/color_rect.cpp b/scene/gui/color_rect.cpp index e35d37d520..2955f74a0c 100644 --- a/scene/gui/color_rect.cpp +++ b/scene/gui/color_rect.cpp @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ @@ -40,8 +40,10 @@ Color ColorRect::get_color() const { } void ColorRect::_notification(int p_what) { - if (p_what == NOTIFICATION_DRAW) { - draw_rect(Rect2(Point2(), get_size()), color); + switch (p_what) { + case NOTIFICATION_DRAW: { + draw_rect(Rect2(Point2(), get_size()), color); + } break; } } diff --git a/scene/gui/color_rect.h b/scene/gui/color_rect.h index 5c650f9f01..35c8ebcaf8 100644 --- a/scene/gui/color_rect.h +++ b/scene/gui/color_rect.h @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ diff --git a/scene/gui/container.cpp b/scene/gui/container.cpp index a1bd82f6f7..1dd88371ea 100644 --- a/scene/gui/container.cpp +++ b/scene/gui/container.cpp @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ @@ -29,13 +29,14 @@ /*************************************************************************/ #include "container.h" + #include "core/object/message_queue.h" #include "scene/scene_string_names.h" void Container::_child_minsize_changed() { //Size2 ms = get_combined_minimum_size(); //if (ms.width > get_size().width || ms.height > get_size().height) { - minimum_size_changed(); + update_minimum_size(); queue_sort(); } @@ -51,7 +52,7 @@ void Container::add_child_notify(Node *p_child) { control->connect(SNAME("minimum_size_changed"), callable_mp(this, &Container::_child_minsize_changed)); control->connect(SNAME("visibility_changed"), callable_mp(this, &Container::_child_minsize_changed)); - minimum_size_changed(); + update_minimum_size(); queue_sort(); } @@ -62,7 +63,7 @@ void Container::move_child_notify(Node *p_child) { return; } - minimum_size_changed(); + update_minimum_size(); queue_sort(); } @@ -78,7 +79,7 @@ void Container::remove_child_notify(Node *p_child) { control->disconnect("minimum_size_changed", callable_mp(this, &Container::_child_minsize_changed)); control->disconnect("visibility_changed", callable_mp(this, &Container::_child_minsize_changed)); - minimum_size_changed(); + update_minimum_size(); queue_sort(); } @@ -143,18 +144,46 @@ void Container::queue_sort() { pending_sort = true; } +Vector<int> Container::get_allowed_size_flags_horizontal() const { + Vector<int> flags; + if (GDVIRTUAL_CALL(_get_allowed_size_flags_horizontal, flags)) { + return flags; + } + + flags.append(SIZE_FILL); + flags.append(SIZE_EXPAND); + flags.append(SIZE_SHRINK_BEGIN); + flags.append(SIZE_SHRINK_CENTER); + flags.append(SIZE_SHRINK_END); + return flags; +} + +Vector<int> Container::get_allowed_size_flags_vertical() const { + Vector<int> flags; + if (GDVIRTUAL_CALL(_get_allowed_size_flags_vertical, flags)) { + return flags; + } + + flags.append(SIZE_FILL); + flags.append(SIZE_EXPAND); + flags.append(SIZE_SHRINK_BEGIN); + flags.append(SIZE_SHRINK_CENTER); + flags.append(SIZE_SHRINK_END); + return flags; +} + void Container::_notification(int p_what) { switch (p_what) { case NOTIFICATION_ENTER_TREE: { pending_sort = false; queue_sort(); } break; - case NOTIFICATION_RESIZED: { - queue_sort(); - } break; + + case NOTIFICATION_RESIZED: case NOTIFICATION_THEME_CHANGED: { queue_sort(); } break; + case NOTIFICATION_VISIBILITY_CHANGED: { if (is_visible_in_tree()) { queue_sort(); @@ -177,6 +206,9 @@ void Container::_bind_methods() { ClassDB::bind_method(D_METHOD("queue_sort"), &Container::queue_sort); ClassDB::bind_method(D_METHOD("fit_child_in_rect", "child", "rect"), &Container::fit_child_in_rect); + GDVIRTUAL_BIND(_get_allowed_size_flags_horizontal); + GDVIRTUAL_BIND(_get_allowed_size_flags_vertical); + BIND_CONSTANT(NOTIFICATION_PRE_SORT_CHILDREN); BIND_CONSTANT(NOTIFICATION_SORT_CHILDREN); diff --git a/scene/gui/container.h b/scene/gui/container.h index f3ae948556..9ec4ad3200 100644 --- a/scene/gui/container.h +++ b/scene/gui/container.h @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ @@ -46,6 +46,9 @@ protected: virtual void move_child_notify(Node *p_child) override; virtual void remove_child_notify(Node *p_child) override; + GDVIRTUAL0RC(Vector<int>, _get_allowed_size_flags_horizontal) + GDVIRTUAL0RC(Vector<int>, _get_allowed_size_flags_vertical) + void _notification(int p_what); static void _bind_methods(); @@ -57,6 +60,9 @@ public: void fit_child_in_rect(Control *p_child, const Rect2 &p_rect); + virtual Vector<int> get_allowed_size_flags_horizontal() const; + virtual Vector<int> get_allowed_size_flags_vertical() const; + TypedArray<String> get_configuration_warnings() const override; Container(); diff --git a/scene/gui/control.cpp b/scene/gui/control.cpp index 582c8e5860..d8659b1f18 100644 --- a/scene/gui/control.cpp +++ b/scene/gui/control.cpp @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ @@ -47,7 +47,7 @@ #include "servers/text_server.h" #ifdef TOOLS_ENABLED -#include "editor/plugins/canvas_item_editor_plugin.h" +#include "editor/plugins/control_editor_plugin.h" #endif #ifdef TOOLS_ENABLED @@ -56,51 +56,71 @@ Dictionary Control::_edit_get_state() const { s["rotation"] = get_rotation(); s["scale"] = get_scale(); s["pivot"] = get_pivot_offset(); + Array anchors; anchors.push_back(get_anchor(SIDE_LEFT)); anchors.push_back(get_anchor(SIDE_TOP)); anchors.push_back(get_anchor(SIDE_RIGHT)); anchors.push_back(get_anchor(SIDE_BOTTOM)); s["anchors"] = anchors; + Array offsets; offsets.push_back(get_offset(SIDE_LEFT)); offsets.push_back(get_offset(SIDE_TOP)); offsets.push_back(get_offset(SIDE_RIGHT)); offsets.push_back(get_offset(SIDE_BOTTOM)); s["offsets"] = offsets; + + s["layout_mode"] = _get_layout_mode(); + s["anchors_layout_preset"] = _get_anchors_layout_preset(); + return s; } void Control::_edit_set_state(const Dictionary &p_state) { ERR_FAIL_COND((p_state.size() <= 0) || !p_state.has("rotation") || !p_state.has("scale") || - !p_state.has("pivot") || !p_state.has("anchors") || !p_state.has("offsets")); + !p_state.has("pivot") || !p_state.has("anchors") || !p_state.has("offsets") || + !p_state.has("layout_mode") || !p_state.has("anchors_layout_preset")); Dictionary state = p_state; set_rotation(state["rotation"]); set_scale(state["scale"]); set_pivot_offset(state["pivot"]); + Array anchors = state["anchors"]; + + // If anchors are not in their default position, force the anchor layout mode in place of position. + LayoutMode _layout = (LayoutMode)(int)state["layout_mode"]; + if (_layout == LayoutMode::LAYOUT_MODE_POSITION) { + bool anchors_mode = ((real_t)anchors[0] != 0.0 || (real_t)anchors[1] != 0.0 || (real_t)anchors[2] != 0.0 || (real_t)anchors[3] != 0.0); + if (anchors_mode) { + _layout = LayoutMode::LAYOUT_MODE_ANCHORS; + } + } + + _set_layout_mode(_layout); + if (_layout == LayoutMode::LAYOUT_MODE_ANCHORS) { + _set_anchors_layout_preset((int)state["anchors_layout_preset"]); + } + data.anchor[SIDE_LEFT] = anchors[0]; data.anchor[SIDE_TOP] = anchors[1]; data.anchor[SIDE_RIGHT] = anchors[2]; data.anchor[SIDE_BOTTOM] = anchors[3]; + Array offsets = state["offsets"]; data.offset[SIDE_LEFT] = offsets[0]; data.offset[SIDE_TOP] = offsets[1]; data.offset[SIDE_RIGHT] = offsets[2]; data.offset[SIDE_BOTTOM] = offsets[3]; + _size_changed(); } void Control::_edit_set_position(const Point2 &p_position) { -#ifdef TOOLS_ENABLED ERR_FAIL_COND_MSG(!Engine::get_singleton()->is_editor_hint(), "This function can only be used from editor plugins."); - set_position(p_position, CanvasItemEditor::get_singleton()->is_anchors_mode_enabled() && Object::cast_to<Control>(data.parent)); -#else - // Unlikely to happen. TODO: enclose all _edit_ functions into TOOLS_ENABLED - set_position(p_position); -#endif + set_position(p_position, ControlEditorToolbar::get_singleton()->is_anchors_mode_enabled() && Object::cast_to<Control>(data.parent)); }; Point2 Control::_edit_get_position() const { @@ -116,15 +136,9 @@ Size2 Control::_edit_get_scale() const { } void Control::_edit_set_rect(const Rect2 &p_edit_rect) { -#ifdef TOOLS_ENABLED ERR_FAIL_COND_MSG(!Engine::get_singleton()->is_editor_hint(), "This function can only be used from editor plugins."); - set_position((get_position() + get_transform().basis_xform(p_edit_rect.position)).snapped(Vector2(1, 1)), CanvasItemEditor::get_singleton()->is_anchors_mode_enabled()); - set_size(p_edit_rect.size.snapped(Vector2(1, 1)), CanvasItemEditor::get_singleton()->is_anchors_mode_enabled()); -#else - // Unlikely to happen. TODO: enclose all _edit_ functions into TOOLS_ENABLED - set_position((get_position() + get_transform().basis_xform(p_edit_rect.position)).snapped(Vector2(1, 1))); - set_size(p_edit_rect.size.snapped(Vector2(1, 1))); -#endif + set_position((get_position() + get_transform().basis_xform(p_edit_rect.position)).snapped(Vector2(1, 1)), ControlEditorToolbar::get_singleton()->is_anchors_mode_enabled()); + set_size(p_edit_rect.size.snapped(Vector2(1, 1)), ControlEditorToolbar::get_singleton()->is_anchors_mode_enabled()); } Rect2 Control::_edit_get_rect() const { @@ -176,9 +190,10 @@ String Control::properties_managed_by_container[] = { "anchor_top", "anchor_right", "anchor_bottom", - "rect_position", - "rect_scale", - "rect_size" + "position", + "rotation", + "scale", + "size" }; void Control::accept_event() { @@ -192,7 +207,7 @@ void Control::set_custom_minimum_size(const Size2 &p_custom) { return; } data.custom_minimum_size = p_custom; - minimum_size_changed(); + update_minimum_size(); } Size2 Control::get_custom_minimum_size() const { @@ -213,7 +228,7 @@ void Control::_update_minimum_size_cache() { data.minimum_size_valid = true; if (size_changed) { - minimum_size_changed(); + update_minimum_size(); } } @@ -235,42 +250,41 @@ Transform2D Control::_get_internal_transform() const { bool Control::_set(const StringName &p_name, const Variant &p_value) { String name = p_name; - // Prefixes "custom_*" are supported for compatibility with 3.x. - if (!name.begins_with("theme_override") && !name.begins_with("custom")) { + if (!name.begins_with("theme_override")) { return false; } - if (p_value.get_type() == Variant::NIL) { - if (name.begins_with("theme_override_icons/") || name.begins_with("custom_icons/")) { + if (p_value.get_type() == Variant::NIL || (p_value.get_type() == Variant::OBJECT && (Object *)p_value == nullptr)) { + 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::_override_changed)); } data.icon_override.erase(dname); notification(NOTIFICATION_THEME_CHANGED); - } else if (name.begins_with("theme_override_styles/") || name.begins_with("custom_styles/")) { + } 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::_override_changed)); } data.style_override.erase(dname); notification(NOTIFICATION_THEME_CHANGED); - } else if (name.begins_with("theme_override_fonts/") || name.begins_with("custom_fonts/")) { + } 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::_override_changed)); } data.font_override.erase(dname); notification(NOTIFICATION_THEME_CHANGED); - } else if (name.begins_with("theme_override_font_sizes/") || name.begins_with("custom_font_sizes/")) { + } 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); - } else if (name.begins_with("theme_override_colors/") || name.begins_with("custom_colors/")) { + } else if (name.begins_with("theme_override_colors/")) { String dname = name.get_slicec('/', 1); data.color_override.erase(dname); notification(NOTIFICATION_THEME_CHANGED); - } else if (name.begins_with("theme_override_constants/") || name.begins_with("custom_constants/")) { + } else if (name.begins_with("theme_override_constants/")) { String dname = name.get_slicec('/', 1); data.constant_override.erase(dname); notification(NOTIFICATION_THEME_CHANGED); @@ -279,22 +293,22 @@ bool Control::_set(const StringName &p_name, const Variant &p_value) { } } else { - if (name.begins_with("theme_override_icons/") || name.begins_with("custom_icons/")) { + if (name.begins_with("theme_override_icons/")) { String dname = name.get_slicec('/', 1); add_theme_icon_override(dname, p_value); - } else if (name.begins_with("theme_override_styles/") || name.begins_with("custom_styles/")) { + } else if (name.begins_with("theme_override_styles/")) { String dname = name.get_slicec('/', 1); add_theme_style_override(dname, p_value); - } else if (name.begins_with("theme_override_fonts/") || name.begins_with("custom_fonts/")) { + } else if (name.begins_with("theme_override_fonts/")) { String dname = name.get_slicec('/', 1); add_theme_font_override(dname, p_value); - } else if (name.begins_with("theme_override_font_sizes/") || name.begins_with("custom_font_sizes/")) { + } else if (name.begins_with("theme_override_font_sizes/")) { String dname = name.get_slicec('/', 1); add_theme_font_size_override(dname, p_value); - } else if (name.begins_with("theme_override_colors/") || name.begins_with("custom_colors/")) { + } else if (name.begins_with("theme_override_colors/")) { String dname = name.get_slicec('/', 1); add_theme_color_override(dname, p_value); - } else if (name.begins_with("theme_override_constants/") || name.begins_with("custom_constants/")) { + } else if (name.begins_with("theme_override_constants/")) { String dname = name.get_slicec('/', 1); add_theme_constant_override(dname, p_value); } else { @@ -430,6 +444,7 @@ void Control::_get_property_list(List<PropertyInfo> *p_list) const { } void Control::_validate_property(PropertyInfo &property) const { + // Update theme type variation options. if (property.name == "theme_type_variation") { List<StringName> names; @@ -455,10 +470,99 @@ void Control::_validate_property(PropertyInfo &property) const { property.hint_string = hint_string; } - if (!Object::cast_to<Container>(get_parent())) { - return; + + // Validate which positioning properties should be displayed depending on the parent and the layout mode. + Node *parent_node = get_parent_control(); + if (!parent_node) { + // 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; + } + + // 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; + } + } 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; + + } else if (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") { + // 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") { + size_flags = parent_container->get_allowed_size_flags_horizontal(); + } else if (property.name == "size_flags_vertical") { + size_flags = parent_container->get_allowed_size_flags_vertical(); + } + + // Enforce the order of the options, regardless of what the container provided. + String hint_string; + if (size_flags.has(SIZE_FILL)) { + hint_string += "Fill:1"; + } + if (size_flags.has(SIZE_EXPAND)) { + if (!hint_string.is_empty()) { + hint_string += ","; + } + hint_string += "Expand:2"; + } + if (size_flags.has(SIZE_SHRINK_CENTER)) { + if (!hint_string.is_empty()) { + hint_string += ","; + } + hint_string += "Shrink Center:4"; + } + if (size_flags.has(SIZE_SHRINK_END)) { + if (!hint_string.is_empty()) { + hint_string += ","; + } + hint_string += "Shrink End:8"; + } + + if (hint_string.is_empty()) { + property.hint_string = ""; + property.usage |= PROPERTY_USAGE_READ_ONLY; + } else { + 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; + + } else if (property.name == "layout_mode") { + // Set the layout mode to be enabled with proper options. + 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; + } + 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; + } } + // Disable the property if it's managed by the parent container. + if (!Object::cast_to<Container>(parent_node)) { + return; + } 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; @@ -576,21 +680,27 @@ void Control::_update_canvas_item_transform() { Transform2D xform = _get_internal_transform(); xform[2] += get_position(); + // We use a little workaround to avoid flickering when moving the pivot with _edit_set_pivot() + if (is_inside_tree() && Math::abs(Math::sin(data.rotation * 4.0f)) < 0.00001f && get_viewport()->is_snap_controls_to_pixels_enabled()) { + xform[2] = xform[2].round(); + } + RenderingServer::get_singleton()->canvas_item_set_transform(get_canvas_item(), xform); } void Control::_notification(int p_notification) { switch (p_notification) { - case NOTIFICATION_ENTER_TREE: { - } break; case NOTIFICATION_POST_ENTER_TREE: { data.minimum_size_valid = false; data.is_rtl_dirty = true; _size_changed(); } break; + case NOTIFICATION_EXIT_TREE: { + release_focus(); get_viewport()->_gui_remove_control(this); } break; + case NOTIFICATION_READY: { #ifdef DEBUG_ENABLED connect("ready", callable_mp(this, &Control::_clear_size_warning), varray(), CONNECT_DEFERRED | CONNECT_ONESHOT); @@ -653,6 +763,7 @@ void Control::_notification(int p_notification) { viewport->connect("size_changed", callable_mp(this, &Control::_size_changed)); } } break; + case NOTIFICATION_EXIT_CANVAS: { if (data.parent_canvas_item) { data.parent_canvas_item->disconnect("item_rect_changed", callable_mp(this, &Control::_size_changed)); @@ -673,8 +784,8 @@ void Control::_notification(int p_notification) { data.parent_canvas_item = nullptr; data.parent_window = nullptr; data.is_rtl_dirty = true; - } break; + case NOTIFICATION_MOVED_IN_PARENT: { // some parents need to know the order of the children to draw (like TabContainer) // update if necessary @@ -686,54 +797,58 @@ void Control::_notification(int p_notification) { if (data.RI) { get_viewport()->_gui_set_root_order_dirty(); } - } break; + case NOTIFICATION_RESIZED: { emit_signal(SceneStringNames::get_singleton()->resized); } break; + case NOTIFICATION_DRAW: { _update_canvas_item_transform(); RenderingServer::get_singleton()->canvas_item_set_custom_rect(get_canvas_item(), !data.disable_visibility_clip, Rect2(Point2(), get_size())); RenderingServer::get_singleton()->canvas_item_set_clip(get_canvas_item(), data.clip_contents); - //emit_signal(SceneStringNames::get_singleton()->draw); - } break; + case NOTIFICATION_MOUSE_ENTER: { emit_signal(SceneStringNames::get_singleton()->mouse_entered); } break; + case NOTIFICATION_MOUSE_EXIT: { emit_signal(SceneStringNames::get_singleton()->mouse_exited); } break; + case NOTIFICATION_FOCUS_ENTER: { emit_signal(SceneStringNames::get_singleton()->focus_entered); update(); } break; + case NOTIFICATION_FOCUS_EXIT: { emit_signal(SceneStringNames::get_singleton()->focus_exited); update(); } break; + case NOTIFICATION_THEME_CHANGED: { - minimum_size_changed(); + update_minimum_size(); update(); } break; + case NOTIFICATION_VISIBILITY_CHANGED: { if (!is_visible_in_tree()) { if (get_viewport() != nullptr) { get_viewport()->_gui_hide_control(this); } - - //remove key focus - } else { data.minimum_size_valid = false; _size_changed(); } - } break; + case NOTIFICATION_TRANSLATION_CHANGED: case NOTIFICATION_LAYOUT_DIRECTION_CHANGED: { - data.is_rtl_dirty = true; - _size_changed(); + if (is_inside_tree()) { + data.is_rtl_dirty = true; + _size_changed(); + } } break; } } @@ -810,6 +925,10 @@ void Control::set_drag_preview(Control *p_control) { get_viewport()->_gui_set_drag_preview(this, p_control); } +bool Control::is_drag_successful() const { + return is_inside_tree() && get_viewport()->gui_is_drag_successful(); +} + void Control::_call_gui_input(const Ref<InputEvent> &p_event) { emit_signal(SceneStringNames::get_singleton()->gui_input, p_event); //signal should be first, so it's possible to override an event (and then accept it) if (!is_inside_tree() || get_viewport()->is_input_handled()) { @@ -1144,12 +1263,12 @@ float Control::fetch_theme_default_base_scale(Control *p_theme_owner, Window *p_ Window *theme_owner_window = p_theme_owner_window; while (theme_owner || theme_owner_window) { - if (theme_owner && theme_owner->data.theme->has_default_theme_base_scale()) { - return theme_owner->data.theme->get_default_theme_base_scale(); + 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_theme_base_scale()) { - return theme_owner_window->theme->get_default_theme_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(); @@ -1171,13 +1290,16 @@ float Control::fetch_theme_default_base_scale(Control *p_theme_owner, Window *p_ // Secondly, check the project-defined Theme resource. if (Theme::get_project_default().is_valid()) { - if (Theme::get_project_default()->has_default_theme_base_scale()) { - return Theme::get_project_default()->get_default_theme_base_scale(); + 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. - return Theme::get_default()->get_default_theme_base_scale(); + 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 { @@ -1192,12 +1314,12 @@ Ref<Font> Control::fetch_theme_default_font(Control *p_theme_owner, Window *p_th Window *theme_owner_window = p_theme_owner_window; while (theme_owner || theme_owner_window) { - if (theme_owner && theme_owner->data.theme->has_default_theme_font()) { - return theme_owner->data.theme->get_default_theme_font(); + 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_theme_font()) { - return theme_owner_window->theme->get_default_theme_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(); @@ -1219,13 +1341,16 @@ Ref<Font> Control::fetch_theme_default_font(Control *p_theme_owner, Window *p_th // Secondly, check the project-defined Theme resource. if (Theme::get_project_default().is_valid()) { - if (Theme::get_project_default()->has_default_theme_font()) { - return Theme::get_project_default()->get_default_theme_font(); + if (Theme::get_project_default()->has_default_font()) { + return Theme::get_project_default()->get_default_font(); } } // Lastly, fall back on the default Theme. - return Theme::get_default()->get_default_theme_font(); + if (Theme::get_default()->has_default_font()) { + return Theme::get_default()->get_default_font(); + } + return Theme::get_fallback_font(); } Ref<Font> Control::get_theme_default_font() const { @@ -1240,12 +1365,12 @@ int Control::fetch_theme_default_font_size(Control *p_theme_owner, Window *p_the Window *theme_owner_window = p_theme_owner_window; while (theme_owner || theme_owner_window) { - if (theme_owner && theme_owner->data.theme->has_default_theme_font_size()) { - return theme_owner->data.theme->get_default_theme_font_size(); + 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_theme_font_size()) { - return theme_owner_window->theme->get_default_theme_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(); @@ -1267,13 +1392,16 @@ int Control::fetch_theme_default_font_size(Control *p_theme_owner, Window *p_the // Secondly, check the project-defined Theme resource. if (Theme::get_project_default().is_valid()) { - if (Theme::get_project_default()->has_default_theme_font_size()) { - return Theme::get_project_default()->get_default_theme_font_size(); + 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. - return Theme::get_default()->get_default_theme_font_size(); + if (Theme::get_default()->has_default_font_size()) { + return Theme::get_default()->get_default_font_size(); + } + return Theme::get_fallback_font_size(); } int Control::get_theme_default_font_size() const { @@ -1292,7 +1420,7 @@ Rect2 Control::get_parent_anchorable_rect() const { #ifdef TOOLS_ENABLED Node *edited_root = get_tree()->get_edited_scene_root(); if (edited_root && (this == edited_root || edited_root->is_ancestor_of(this))) { - parent_rect.size = Size2(ProjectSettings::get_singleton()->get("display/window/size/width"), ProjectSettings::get_singleton()->get("display/window/size/height")); + parent_rect.size = Size2(ProjectSettings::get_singleton()->get("display/window/size/viewport_width"), ProjectSettings::get_singleton()->get("display/window/size/viewport_height")); } else { parent_rect = get_viewport()->get_visible_rect(); } @@ -1369,6 +1497,54 @@ void Control::_size_changed() { } } +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 (has_meta("_edit_layout_mode") && (int)get_meta("_edit_layout_mode") != (int)p_mode) { + list_changed = true; + } + + set_meta("_edit_layout_mode", (int)p_mode); + + if (p_mode == LayoutMode::LAYOUT_MODE_POSITION) { + set_meta("_edit_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); + } + } else { + if (has_meta("_edit_layout_mode")) { + remove_meta("_edit_layout_mode"); + list_changed = true; + } + } + + if (list_changed) { + notify_property_list_changed(); + } +} + +Control::LayoutMode Control::_get_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; + } + + // If anchors are not in the top-left position, this is definitely in anchors mode. + 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"); + } + // Or fallback on default. + return LayoutMode::LAYOUT_MODE_POSITION; +} + void Control::set_anchor(Side p_side, real_t p_anchor, bool p_keep_offset, bool p_push_opposite_anchor) { ERR_FAIL_INDEX((int)p_side, 4); @@ -1410,6 +1586,133 @@ void Control::set_anchor_and_offset(Side p_side, real_t p_anchor, real_t p_pos, set_offset(p_side, p_pos); } +void Control::_set_anchors_layout_preset(int p_preset) { + bool list_changed = false; + + if (has_meta("_edit_layout_mode") && (int)get_meta("_edit_layout_mode") != (int)LayoutMode::LAYOUT_MODE_ANCHORS) { + list_changed = true; + set_meta("_edit_layout_mode", (int)LayoutMode::LAYOUT_MODE_ANCHORS); + } + + if (p_preset == -1) { + if (!has_meta("_edit_use_custom_anchors") || !(bool)get_meta("_edit_use_custom_anchors")) { + set_meta("_edit_use_custom_anchors", true); + notify_property_list_changed(); + } + return; // Keep settings as is. + } + + if (!has_meta("_edit_use_custom_anchors") || (bool)get_meta("_edit_use_custom_anchors")) { + list_changed = true; + set_meta("_edit_use_custom_anchors", false); + } + + LayoutPreset preset = (LayoutPreset)p_preset; + // Set correct anchors. + set_anchors_preset(preset); + + // Select correct preset mode. + switch (preset) { + case PRESET_TOP_LEFT: + case PRESET_TOP_RIGHT: + case PRESET_BOTTOM_LEFT: + case PRESET_BOTTOM_RIGHT: + case PRESET_CENTER_LEFT: + case PRESET_CENTER_TOP: + case PRESET_CENTER_RIGHT: + case PRESET_CENTER_BOTTOM: + case PRESET_CENTER: + set_offsets_preset(preset, LayoutPresetMode::PRESET_MODE_KEEP_SIZE); + break; + case PRESET_LEFT_WIDE: + case PRESET_TOP_WIDE: + case PRESET_RIGHT_WIDE: + case PRESET_BOTTOM_WIDE: + case PRESET_VCENTER_WIDE: + case PRESET_HCENTER_WIDE: + case PRESET_WIDE: + set_offsets_preset(preset, LayoutPresetMode::PRESET_MODE_MINSIZE); + break; + } + + // Select correct grow directions. + set_grow_direction_preset(preset); + + if (list_changed) { + notify_property_list_changed(); + } +} + +int Control::_get_anchors_layout_preset() const { + // If the custom preset was selected by user, use it. + if (has_meta("_edit_use_custom_anchors") && (bool)get_meta("_edit_use_custom_anchors")) { + return -1; + } + + // Check anchors to determine if the current state matches a preset, or not. + + float left = get_anchor(SIDE_LEFT); + float right = get_anchor(SIDE_RIGHT); + float top = get_anchor(SIDE_TOP); + float bottom = get_anchor(SIDE_BOTTOM); + + if (left == ANCHOR_BEGIN && right == ANCHOR_BEGIN && top == ANCHOR_BEGIN && bottom == ANCHOR_BEGIN) { + return (int)LayoutPreset::PRESET_TOP_LEFT; + } + if (left == ANCHOR_END && right == ANCHOR_END && top == ANCHOR_BEGIN && bottom == ANCHOR_BEGIN) { + return (int)LayoutPreset::PRESET_TOP_RIGHT; + } + if (left == ANCHOR_BEGIN && right == ANCHOR_BEGIN && top == ANCHOR_END && bottom == ANCHOR_END) { + return (int)LayoutPreset::PRESET_BOTTOM_LEFT; + } + if (left == ANCHOR_END && right == ANCHOR_END && top == ANCHOR_END && bottom == ANCHOR_END) { + return (int)LayoutPreset::PRESET_BOTTOM_RIGHT; + } + + if (left == ANCHOR_BEGIN && right == ANCHOR_BEGIN && top == 0.5 && bottom == 0.5) { + return (int)LayoutPreset::PRESET_CENTER_LEFT; + } + if (left == ANCHOR_END && right == ANCHOR_END && top == 0.5 && bottom == 0.5) { + return (int)LayoutPreset::PRESET_CENTER_RIGHT; + } + if (left == 0.5 && right == 0.5 && top == ANCHOR_BEGIN && bottom == ANCHOR_BEGIN) { + return (int)LayoutPreset::PRESET_CENTER_TOP; + } + if (left == 0.5 && right == 0.5 && top == ANCHOR_END && bottom == ANCHOR_END) { + return (int)LayoutPreset::PRESET_CENTER_BOTTOM; + } + if (left == 0.5 && right == 0.5 && top == 0.5 && bottom == 0.5) { + return (int)LayoutPreset::PRESET_CENTER; + } + + if (left == ANCHOR_BEGIN && right == ANCHOR_BEGIN && top == ANCHOR_BEGIN && bottom == ANCHOR_END) { + return (int)LayoutPreset::PRESET_LEFT_WIDE; + } + if (left == ANCHOR_END && right == ANCHOR_END && top == ANCHOR_BEGIN && bottom == ANCHOR_END) { + return (int)LayoutPreset::PRESET_RIGHT_WIDE; + } + if (left == ANCHOR_BEGIN && right == ANCHOR_END && top == ANCHOR_BEGIN && bottom == ANCHOR_BEGIN) { + return (int)LayoutPreset::PRESET_TOP_WIDE; + } + if (left == ANCHOR_BEGIN && right == ANCHOR_END && top == ANCHOR_END && bottom == ANCHOR_END) { + return (int)LayoutPreset::PRESET_BOTTOM_WIDE; + } + + if (left == 0.5 && right == 0.5 && top == ANCHOR_BEGIN && bottom == ANCHOR_END) { + return (int)LayoutPreset::PRESET_VCENTER_WIDE; + } + if (left == ANCHOR_BEGIN && right == ANCHOR_END && top == 0.5 && bottom == 0.5) { + return (int)LayoutPreset::PRESET_HCENTER_WIDE; + } + + if (left == ANCHOR_BEGIN && right == ANCHOR_END && top == ANCHOR_BEGIN && bottom == ANCHOR_END) { + return (int)LayoutPreset::PRESET_WIDE; + } + + // Does not match any preset, return "Custom". + return -1; +} + void Control::set_anchors_preset(LayoutPreset p_preset, bool p_keep_offsets) { ERR_FAIL_INDEX((int)p_preset, 16); @@ -1666,6 +1969,62 @@ void Control::set_anchors_and_offsets_preset(LayoutPreset p_preset, LayoutPreset set_offsets_preset(p_preset, p_resize_mode, p_margin); } +void Control::set_grow_direction_preset(LayoutPreset p_preset) { + // Select correct horizontal grow direction. + switch (p_preset) { + case PRESET_TOP_LEFT: + case PRESET_BOTTOM_LEFT: + case PRESET_CENTER_LEFT: + case PRESET_LEFT_WIDE: + set_h_grow_direction(GrowDirection::GROW_DIRECTION_END); + break; + case PRESET_TOP_RIGHT: + case PRESET_BOTTOM_RIGHT: + case PRESET_CENTER_RIGHT: + case PRESET_RIGHT_WIDE: + set_h_grow_direction(GrowDirection::GROW_DIRECTION_BEGIN); + break; + case PRESET_CENTER_TOP: + case PRESET_CENTER_BOTTOM: + case PRESET_CENTER: + case PRESET_TOP_WIDE: + case PRESET_BOTTOM_WIDE: + case PRESET_VCENTER_WIDE: + case PRESET_HCENTER_WIDE: + case PRESET_WIDE: + set_h_grow_direction(GrowDirection::GROW_DIRECTION_BOTH); + break; + } + + // Select correct vertical grow direction. + switch (p_preset) { + case PRESET_TOP_LEFT: + case PRESET_TOP_RIGHT: + case PRESET_CENTER_TOP: + case PRESET_TOP_WIDE: + set_v_grow_direction(GrowDirection::GROW_DIRECTION_END); + break; + + case PRESET_BOTTOM_LEFT: + case PRESET_BOTTOM_RIGHT: + case PRESET_CENTER_BOTTOM: + case PRESET_BOTTOM_WIDE: + set_v_grow_direction(GrowDirection::GROW_DIRECTION_BEGIN); + break; + + case PRESET_CENTER_LEFT: + case PRESET_CENTER_RIGHT: + case PRESET_CENTER: + case PRESET_LEFT_WIDE: + case PRESET_RIGHT_WIDE: + case PRESET_VCENTER_WIDE: + case PRESET_HCENTER_WIDE: + case PRESET_WIDE: + set_v_grow_direction(GrowDirection::GROW_DIRECTION_BOTH); + break; + } +} + real_t Control::get_anchor(Side p_side) const { ERR_FAIL_INDEX_V(int(p_side), 4, 0.0); @@ -1821,6 +2180,10 @@ Size2 Control::get_size() const { return data.size_cache; } +void Control::reset_size() { + set_size(Size2()); +} + Rect2 Control::get_global_rect() const { return Rect2(get_global_position(), get_size()); } @@ -2171,8 +2534,7 @@ void Control::release_focus() { return; } - get_viewport()->_gui_remove_focus(); - update(); + get_viewport()->gui_release_focus(); } bool Control::is_top_level_control() const { @@ -2526,7 +2888,7 @@ void Control::grab_click_focus() { get_viewport()->_gui_grab_click_focus(this); } -void Control::minimum_size_changed() { +void Control::update_minimum_size() { if (!is_inside_tree() || data.block_minimum_size_adjust) { return; } @@ -2575,11 +2937,6 @@ Control::MouseFilter Control::get_mouse_filter() const { return data.mouse_filter; } -Control *Control::get_focus_owner() const { - ERR_FAIL_COND_V(!is_inside_tree(), nullptr); - return get_viewport()->_gui_get_focus_owner(); -} - void Control::warp_mouse(const Point2 &p_to_pos) { ERR_FAIL_COND(!is_inside_tree()); get_viewport()->warp_mouse(get_global_transform().xform(p_to_pos)); @@ -2589,7 +2946,7 @@ bool Control::is_text_field() const { return false; } -Array Control::structured_text_parser(StructuredTextParser p_theme_type, const Array &p_args, const String p_text) const { +Array Control::structured_text_parser(StructuredTextParser p_theme_type, const Array &p_args, const String &p_text) const { Array ret; switch (p_theme_type) { case STRUCTURED_TEXT_URI: { @@ -2688,7 +3045,7 @@ real_t Control::get_rotation() const { void Control::_override_changed() { notification(NOTIFICATION_THEME_CHANGED); emit_signal(SceneStringNames::get_singleton()->theme_changed); - minimum_size_changed(); // overrides are likely to affect minimum size + update_minimum_size(); // Overrides are likely to affect minimum size. } void Control::set_pivot_offset(const Vector2 &p_pivot) { @@ -2783,7 +3140,7 @@ void Control::get_argument_options(const StringName &p_function, int p_idx, List TypedArray<String> Control::get_configuration_warnings() const { TypedArray<String> warnings = Node::get_configuration_warnings(); - if (data.mouse_filter == MOUSE_FILTER_IGNORE && data.tooltip != "") { + if (data.mouse_filter == MOUSE_FILTER_IGNORE && !data.tooltip.is_empty()) { warnings.push_back(TTR("The Hint Tooltip won't be displayed as the control's Mouse Filter is set to \"Ignore\". To solve this, set the Mouse Filter to \"Stop\" or \"Pass\".")); } @@ -2828,19 +3185,28 @@ void Control::_bind_methods() { ClassDB::bind_method(D_METHOD("accept_event"), &Control::accept_event); ClassDB::bind_method(D_METHOD("get_minimum_size"), &Control::get_minimum_size); ClassDB::bind_method(D_METHOD("get_combined_minimum_size"), &Control::get_combined_minimum_size); + + ClassDB::bind_method(D_METHOD("_set_layout_mode", "mode"), &Control::_set_layout_mode); + ClassDB::bind_method(D_METHOD("_get_layout_mode"), &Control::_get_layout_mode); + ClassDB::bind_method(D_METHOD("_set_anchors_layout_preset", "preset"), &Control::_set_anchors_layout_preset); + ClassDB::bind_method(D_METHOD("_get_anchors_layout_preset"), &Control::_get_anchors_layout_preset); ClassDB::bind_method(D_METHOD("set_anchors_preset", "preset", "keep_offsets"), &Control::set_anchors_preset, DEFVAL(false)); ClassDB::bind_method(D_METHOD("set_offsets_preset", "preset", "resize_mode", "margin"), &Control::set_offsets_preset, DEFVAL(PRESET_MODE_MINSIZE), DEFVAL(0)); ClassDB::bind_method(D_METHOD("set_anchors_and_offsets_preset", "preset", "resize_mode", "margin"), &Control::set_anchors_and_offsets_preset, DEFVAL(PRESET_MODE_MINSIZE), DEFVAL(0)); + ClassDB::bind_method(D_METHOD("_set_anchor", "side", "anchor"), &Control::_set_anchor); ClassDB::bind_method(D_METHOD("set_anchor", "side", "anchor", "keep_offset", "push_opposite_anchor"), &Control::set_anchor, DEFVAL(false), DEFVAL(true)); ClassDB::bind_method(D_METHOD("get_anchor", "side"), &Control::get_anchor); ClassDB::bind_method(D_METHOD("set_offset", "side", "offset"), &Control::set_offset); + ClassDB::bind_method(D_METHOD("get_offset", "offset"), &Control::get_offset); ClassDB::bind_method(D_METHOD("set_anchor_and_offset", "side", "anchor", "offset", "push_opposite_anchor"), &Control::set_anchor_and_offset, DEFVAL(false)); + ClassDB::bind_method(D_METHOD("set_begin", "position"), &Control::set_begin); ClassDB::bind_method(D_METHOD("set_end", "position"), &Control::set_end); ClassDB::bind_method(D_METHOD("set_position", "position", "keep_offsets"), &Control::set_position, DEFVAL(false)); ClassDB::bind_method(D_METHOD("_set_position", "position"), &Control::_set_position); ClassDB::bind_method(D_METHOD("set_size", "size", "keep_offsets"), &Control::set_size, DEFVAL(false)); + ClassDB::bind_method(D_METHOD("reset_size"), &Control::reset_size); ClassDB::bind_method(D_METHOD("_set_size", "size"), &Control::_set_size); ClassDB::bind_method(D_METHOD("set_custom_minimum_size", "size"), &Control::set_custom_minimum_size); ClassDB::bind_method(D_METHOD("set_global_position", "position", "keep_offsets"), &Control::set_global_position, DEFVAL(false)); @@ -2848,7 +3214,6 @@ void Control::_bind_methods() { ClassDB::bind_method(D_METHOD("set_rotation", "radians"), &Control::set_rotation); ClassDB::bind_method(D_METHOD("set_scale", "scale"), &Control::set_scale); ClassDB::bind_method(D_METHOD("set_pivot_offset", "pivot_offset"), &Control::set_pivot_offset); - ClassDB::bind_method(D_METHOD("get_offset", "offset"), &Control::get_offset); ClassDB::bind_method(D_METHOD("get_begin"), &Control::get_begin); ClassDB::bind_method(D_METHOD("get_end"), &Control::get_end); ClassDB::bind_method(D_METHOD("get_position"), &Control::get_position); @@ -2868,7 +3233,6 @@ void Control::_bind_methods() { ClassDB::bind_method(D_METHOD("release_focus"), &Control::release_focus); ClassDB::bind_method(D_METHOD("find_prev_valid_focus"), &Control::find_prev_valid_focus); ClassDB::bind_method(D_METHOD("find_next_valid_focus"), &Control::find_next_valid_focus); - ClassDB::bind_method(D_METHOD("get_focus_owner"), &Control::get_focus_owner); ClassDB::bind_method(D_METHOD("set_h_size_flags", "flags"), &Control::set_h_size_flags); ClassDB::bind_method(D_METHOD("get_h_size_flags"), &Control::get_h_size_flags); @@ -2964,10 +3328,11 @@ void Control::_bind_methods() { ClassDB::bind_method(D_METHOD("set_drag_forwarding", "target"), &Control::set_drag_forwarding); ClassDB::bind_method(D_METHOD("set_drag_preview", "control"), &Control::set_drag_preview); + ClassDB::bind_method(D_METHOD("is_drag_successful"), &Control::is_drag_successful); ClassDB::bind_method(D_METHOD("warp_mouse", "to_position"), &Control::warp_mouse); - ClassDB::bind_method(D_METHOD("minimum_size_changed"), &Control::minimum_size_changed); + ClassDB::bind_method(D_METHOD("update_minimum_size"), &Control::update_minimum_size); ClassDB::bind_method(D_METHOD("set_layout_direction", "direction"), &Control::set_layout_direction); ClassDB::bind_method(D_METHOD("get_layout_direction"), &Control::get_layout_direction); @@ -2976,38 +3341,53 @@ void Control::_bind_methods() { ClassDB::bind_method(D_METHOD("set_auto_translate", "enable"), &Control::set_auto_translate); ClassDB::bind_method(D_METHOD("is_auto_translating"), &Control::is_auto_translating); - ADD_GROUP("Anchor", "anchor_"); + ADD_GROUP("Layout", ""); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "clip_contents"), "set_clip_contents", "is_clipping_contents"); + ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "minimum_size"), "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_DEFAULT("layout_mode", LayoutMode::LAYOUT_MODE_POSITION); + + const String anchors_presets_options = "Custom:-1,PresetWide:15," + "PresetTopLeft:0,PresetTopRight:1,PresetBottomRight:3,PresetBottomLeft:2," + "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_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_GROUP("Offset", "offset_"); + ADD_SUBGROUP_INDENT("Anchor Offsets", "offset_", 1); ADD_PROPERTYI(PropertyInfo(Variant::INT, "offset_left", PROPERTY_HINT_RANGE, "-4096,4096"), "set_offset", "get_offset", SIDE_LEFT); ADD_PROPERTYI(PropertyInfo(Variant::INT, "offset_top", PROPERTY_HINT_RANGE, "-4096,4096"), "set_offset", "get_offset", SIDE_TOP); ADD_PROPERTYI(PropertyInfo(Variant::INT, "offset_right", PROPERTY_HINT_RANGE, "-4096,4096"), "set_offset", "get_offset", SIDE_RIGHT); ADD_PROPERTYI(PropertyInfo(Variant::INT, "offset_bottom", PROPERTY_HINT_RANGE, "-4096,4096"), "set_offset", "get_offset", SIDE_BOTTOM); - ADD_GROUP("Grow Direction", "grow_"); - ADD_PROPERTY(PropertyInfo(Variant::INT, "grow_horizontal", PROPERTY_HINT_ENUM, "Begin,End,Both"), "set_h_grow_direction", "get_h_grow_direction"); - ADD_PROPERTY(PropertyInfo(Variant::INT, "grow_vertical", PROPERTY_HINT_ENUM, "Begin,End,Both"), "set_v_grow_direction", "get_v_grow_direction"); - - ADD_GROUP("Layout Direction", "layout_"); - 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_SUBGROUP_INDENT("Grow Direction", "grow_", 1); + ADD_PROPERTY(PropertyInfo(Variant::INT, "grow_horizontal", PROPERTY_HINT_ENUM, "Left,Right,Both"), "set_h_grow_direction", "get_h_grow_direction"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "grow_vertical", PROPERTY_HINT_ENUM, "Top,Bottom,Both"), "set_v_grow_direction", "get_v_grow_direction"); + + ADD_SUBGROUP("Transform", ""); + ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "size", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_EDITOR), "_set_size", "get_size"); + ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "position", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_EDITOR), "_set_position", "get_position"); + ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "global_position", PROPERTY_HINT_NONE, "", 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::VECTOR2, "scale"), "set_scale", "get_scale"); + ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "pivot_offset"), "set_pivot_offset", "get_pivot_offset"); + + ADD_SUBGROUP("Container Sizing", "size_flags_"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "size_flags_horizontal", PROPERTY_HINT_FLAGS, "Fill:1,Expand:2,Shrink Center:4,Shrink End:8"), "set_h_size_flags", "get_h_size_flags"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "size_flags_vertical", PROPERTY_HINT_FLAGS, "Fill:1,Expand:2,Shrink Center:4,Shrink End:8"), "set_v_size_flags", "get_v_size_flags"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "size_flags_stretch_ratio", PROPERTY_HINT_RANGE, "0,20,0.01,or_greater"), "set_stretch_ratio", "get_stretch_ratio"); ADD_GROUP("Auto Translate", ""); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "auto_translate"), "set_auto_translate", "is_auto_translating"); - ADD_GROUP("Rect", "rect_"); - ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "rect_position", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_EDITOR), "_set_position", "get_position"); - ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "rect_global_position", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NONE), "_set_global_position", "get_global_position"); - ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "rect_size", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_EDITOR), "_set_size", "get_size"); - ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "rect_min_size"), "set_custom_minimum_size", "get_custom_minimum_size"); - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "rect_rotation", PROPERTY_HINT_RANGE, "-360,360,0.1,or_lesser,or_greater,radians"), "set_rotation", "get_rotation"); - ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "rect_scale"), "set_scale", "get_scale"); - ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "rect_pivot_offset"), "set_pivot_offset", "get_pivot_offset"); - ADD_PROPERTY(PropertyInfo(Variant::BOOL, "rect_clip_content"), "set_clip_contents", "is_clipping_contents"); - ADD_GROUP("Hint", "hint_"); ADD_PROPERTY(PropertyInfo(Variant::STRING, "hint_tooltip", PROPERTY_HINT_MULTILINE_TEXT), "set_tooltip", "_get_tooltip"); @@ -3024,11 +3404,6 @@ void Control::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::INT, "mouse_filter", PROPERTY_HINT_ENUM, "Stop,Pass,Ignore"), "set_mouse_filter", "get_mouse_filter"); ADD_PROPERTY(PropertyInfo(Variant::INT, "mouse_default_cursor_shape", PROPERTY_HINT_ENUM, "Arrow,I-Beam,Pointing Hand,Cross,Wait,Busy,Drag,Can Drop,Forbidden,Vertical Resize,Horizontal Resize,Secondary Diagonal Resize,Main Diagonal Resize,Move,Vertical Split,Horizontal Split,Help"), "set_default_cursor_shape", "get_default_cursor_shape"); - ADD_GROUP("Size Flags", "size_flags_"); - ADD_PROPERTY(PropertyInfo(Variant::INT, "size_flags_horizontal", PROPERTY_HINT_FLAGS, "Fill,Expand,Shrink Center,Shrink End"), "set_h_size_flags", "get_h_size_flags"); - ADD_PROPERTY(PropertyInfo(Variant::INT, "size_flags_vertical", PROPERTY_HINT_FLAGS, "Fill,Expand,Shrink Center,Shrink End"), "set_v_size_flags", "get_v_size_flags"); - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "size_flags_stretch_ratio", PROPERTY_HINT_RANGE, "0,20,0.01,or_greater"), "set_stretch_ratio", "get_stretch_ratio"); - ADD_GROUP("Theme", "theme_"); ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "theme", PROPERTY_HINT_RESOURCE_TYPE, "Theme"), "set_theme", "get_theme"); ADD_PROPERTY(PropertyInfo(Variant::STRING, "theme_type_variation", PROPERTY_HINT_ENUM_SUGGESTION), "set_theme_type_variation", "get_theme_type_variation"); @@ -3087,6 +3462,7 @@ void Control::_bind_methods() { BIND_ENUM_CONSTANT(PRESET_MODE_KEEP_HEIGHT); BIND_ENUM_CONSTANT(PRESET_MODE_KEEP_SIZE); + BIND_ENUM_CONSTANT(SIZE_SHRINK_BEGIN); BIND_ENUM_CONSTANT(SIZE_FILL); BIND_ENUM_CONSTANT(SIZE_EXPAND); BIND_ENUM_CONSTANT(SIZE_EXPAND_FILL); diff --git a/scene/gui/control.h b/scene/gui/control.h index 02ab336ef0..becb50a118 100644 --- a/scene/gui/control.h +++ b/scene/gui/control.h @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ @@ -31,12 +31,10 @@ #ifndef CONTROL_H #define CONTROL_H -#include "core/input/shortcut.h" #include "core/math/transform_2d.h" #include "core/object/gdvirtual.gen.inc" #include "core/templates/rid.h" #include "scene/main/canvas_item.h" -#include "scene/main/node.h" #include "scene/main/timer.h" #include "scene/resources/theme.h" @@ -67,12 +65,13 @@ public: }; enum SizeFlags { + SIZE_SHRINK_BEGIN = 0, SIZE_FILL = 1, SIZE_EXPAND = 2, - SIZE_EXPAND_FILL = SIZE_EXPAND | SIZE_FILL, - SIZE_SHRINK_CENTER = 4, //ignored by expand or fill - SIZE_SHRINK_END = 8, //ignored by expand or fill + SIZE_SHRINK_CENTER = 4, + SIZE_SHRINK_END = 8, + SIZE_EXPAND_FILL = SIZE_EXPAND | SIZE_FILL, }; enum MouseFilter { @@ -128,6 +127,13 @@ public: PRESET_MODE_KEEP_SIZE }; + enum LayoutMode { + LAYOUT_MODE_POSITION, + LAYOUT_MODE_ANCHORS, + LAYOUT_MODE_CONTAINER, + LAYOUT_MODE_UNCONTROLLED, + }; + enum LayoutDirection { LAYOUT_DIRECTION_INHERITED, LAYOUT_DIRECTION_LOCALE, @@ -230,7 +236,7 @@ private: } data; - static constexpr unsigned properties_managed_by_container_count = 11; + static constexpr unsigned properties_managed_by_container_count = 12; static String properties_managed_by_container[properties_managed_by_container_count]; void _window_find_focus_neighbor(const Vector2 &p_dir, Node *p_at, const Point2 *p_points, real_t p_min, real_t &r_closest_dist, Control **r_closest); @@ -241,6 +247,12 @@ private: void _set_global_position(const Point2 &p_point); void _set_size(const Size2 &p_size); + void _set_layout_mode(LayoutMode p_mode); + LayoutMode _get_layout_mode() const; + + void _set_anchors_layout_preset(int p_preset); + int _get_anchors_layout_preset() const; + void _theme_changed(); void _notify_theme_changed(); @@ -279,15 +291,16 @@ protected: //virtual void _window_gui_input(InputEvent p_event); - virtual Array structured_text_parser(StructuredTextParser p_theme_type, const Array &p_args, const String p_text) const; + virtual Array structured_text_parser(StructuredTextParser p_theme_type, const Array &p_args, const String &p_text) const; 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 _notification(int p_notification); static void _bind_methods(); - virtual void _validate_property(PropertyInfo &property) const override; //bind helpers @@ -357,6 +370,7 @@ public: virtual void drop_data(const Point2 &p_point, const Variant &p_data); void set_drag_preview(Control *p_control); void force_drag(const Variant &p_data, Control *p_control); + bool is_drag_successful() const; void set_custom_minimum_size(const Size2 &p_custom); Size2 get_custom_minimum_size() const; @@ -377,6 +391,7 @@ public: void set_anchors_preset(LayoutPreset p_preset, bool p_keep_offsets = true); void set_offsets_preset(LayoutPreset p_preset, LayoutPresetMode p_resize_mode = PRESET_MODE_MINSIZE, int p_margin = 0); void set_anchors_and_offsets_preset(LayoutPreset p_preset, LayoutPresetMode p_resize_mode = PRESET_MODE_MINSIZE, int p_margin = 0); + void set_grow_direction_preset(LayoutPreset p_preset); void set_anchor(Side p_side, real_t p_anchor, bool p_keep_offset = true, bool p_push_opposite_anchor = true); real_t get_anchor(Side p_side) const; @@ -400,6 +415,7 @@ public: void set_size(const Size2 &p_size, bool p_keep_offsets = false); Size2 get_size() const; + void reset_size(); Rect2 get_rect() const; Rect2 get_global_rect() const; @@ -439,7 +455,7 @@ public: void set_stretch_ratio(real_t p_ratio); real_t get_stretch_ratio() const; - void minimum_size_changed(); + void update_minimum_size(); /* FOCUS */ @@ -460,8 +476,6 @@ public: void set_focus_previous(const NodePath &p_prev); NodePath get_focus_previous() const; - Control *get_focus_owner() const; - void set_mouse_filter(MouseFilter p_filter); MouseFilter get_mouse_filter() const; @@ -563,6 +577,7 @@ VARIANT_ENUM_CAST(Control::LayoutPresetMode); VARIANT_ENUM_CAST(Control::MouseFilter); VARIANT_ENUM_CAST(Control::GrowDirection); VARIANT_ENUM_CAST(Control::Anchor); +VARIANT_ENUM_CAST(Control::LayoutMode); VARIANT_ENUM_CAST(Control::LayoutDirection); VARIANT_ENUM_CAST(Control::TextDirection); VARIANT_ENUM_CAST(Control::StructuredTextParser); diff --git a/scene/gui/dialogs.cpp b/scene/gui/dialogs.cpp index 71d2778cc3..e3744eedca 100644 --- a/scene/gui/dialogs.cpp +++ b/scene/gui/dialogs.cpp @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ @@ -33,18 +33,13 @@ #include "core/os/keyboard.h" #include "core/string/print_string.h" #include "core/string/translation.h" -#include "line_edit.h" - -#ifdef TOOLS_ENABLED -#include "editor/editor_node.h" -#include "scene/main/window.h" // Only used to check for more modals when dimming the editor. -#endif +#include "scene/gui/line_edit.h" // AcceptDialog void AcceptDialog::_input_from_window(const Ref<InputEvent> &p_event) { Ref<InputEventKey> key = p_event; - if (key.is_valid() && key->is_pressed() && key->get_keycode() == KEY_ESCAPE) { + if (key.is_valid() && key->is_pressed() && key->get_keycode() == Key::ESCAPE) { _cancel_pressed(); } } @@ -72,21 +67,25 @@ 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"))); } break; + case NOTIFICATION_EXIT_TREE: { if (parent_visible) { parent_visible->disconnect("focus_entered", callable_mp(this, &AcceptDialog::_parent_focused)); parent_visible = nullptr; } } break; + case NOTIFICATION_READY: case NOTIFICATION_WM_SIZE_CHANGED: { if (is_visible()) { _update_child_rects(); } } break; + case NOTIFICATION_WM_CLOSE_REQUEST: { _cancel_pressed(); } break; @@ -242,7 +241,7 @@ Button *AcceptDialog::add_button(const String &p_text, bool p_right, const Strin hbc->add_spacer(true); } - if (p_action != "") { + if (!p_action.is_empty()) { button->connect("pressed", callable_mp(this, &AcceptDialog::_custom_action), varray(p_action)); } @@ -251,7 +250,7 @@ Button *AcceptDialog::add_button(const String &p_text, bool p_right, const Strin Button *AcceptDialog::add_cancel_button(const String &p_cancel) { String c = p_cancel; - if (p_cancel == "") { + if (p_cancel.is_empty()) { c = TTRC("Cancel"); } Button *b = swap_cancel_ok ? add_button(c, true) : add_button(c); diff --git a/scene/gui/dialogs.h b/scene/gui/dialogs.h index 8e803a2a7c..1365b1df24 100644 --- a/scene/gui/dialogs.h +++ b/scene/gui/dialogs.h @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ diff --git a/scene/gui/file_dialog.cpp b/scene/gui/file_dialog.cpp index c073be8aa3..e71ab64535 100644 --- a/scene/gui/file_dialog.cpp +++ b/scene/gui/file_dialog.cpp @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ @@ -42,6 +42,17 @@ FileDialog::RegisterFunc FileDialog::unregister_func = nullptr; void FileDialog::popup_file_dialog() { popup_centered_clamped(Size2i(700, 500), 0.8f); + _focus_file_text(); +} + +void FileDialog::_focus_file_text() { + int lp = file->get_text().rfind("."); + if (lp != -1) { + file->select(0, lp); + if (file->is_inside_tree() && !get_tree()->is_node_being_edited(file)) { + file->grab_focus(); + } + } } VBoxContainer *FileDialog::get_vbox() { @@ -81,23 +92,30 @@ void FileDialog::_theme_changed() { } void FileDialog::_notification(int p_what) { - if (p_what == NOTIFICATION_VISIBILITY_CHANGED) { - if (!is_visible()) { - set_process_unhandled_input(false); - } - } - if (p_what == NOTIFICATION_ENTER_TREE) { - dir_up->set_icon(vbox->get_theme_icon(SNAME("parent_folder"), SNAME("FileDialog"))); - 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"))); - } 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"))); - } - 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(); + switch (p_what) { + case NOTIFICATION_VISIBILITY_CHANGED: { + if (!is_visible()) { + set_process_unhandled_input(false); + } + } break; + + case NOTIFICATION_ENTER_TREE: { + dir_up->set_icon(vbox->get_theme_icon(SNAME("parent_folder"), SNAME("FileDialog"))); + 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"))); + } 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"))); + } + 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(); + } break; + + case NOTIFICATION_TRANSLATION_CHANGED: { + update_filters(); + } break; } } @@ -110,7 +128,7 @@ void FileDialog::unhandled_input(const Ref<InputEvent> &p_event) { bool handled = true; switch (k->get_keycode()) { - case KEY_H: { + case Key::H: { if (k->is_command_pressed()) { set_show_hidden_files(!show_hidden_files); } else { @@ -118,10 +136,10 @@ void FileDialog::unhandled_input(const Ref<InputEvent> &p_event) { } } break; - case KEY_F5: { + case Key::F5: { invalidate(); } break; - case KEY_BACKSPACE: { + case Key::BACKSPACE: { _dir_submitted(".."); } break; default: { @@ -155,7 +173,14 @@ void FileDialog::update_dir() { dir->set_text(dir_access->get_current_dir(false)); if (drives->is_visible()) { - drives->select(dir_access->get_current_drive()); + if (dir_access->get_current_dir().is_network_share_path()) { + _update_drives(false); + drives->add_item(RTR("Network")); + drives->set_item_disabled(-1, true); + drives->select(drives->get_item_count() - 1); + } else { + drives->select(dir_access->get_current_drive()); + } } // Deselect any item, to make "Select Current Folder" button text by default. @@ -237,7 +262,8 @@ void FileDialog::_action_pressed() { return; } - String f = dir_access->get_current_dir().plus_file(file->get_text()); + String file_text = file->get_text(); + String f = file_text.is_absolute_path() ? file_text : dir_access->get_current_dir().plus_file(file_text); if ((mode == FILE_MODE_OPEN_ANY || mode == FILE_MODE_OPEN_FILE) && dir_access->file_exists(f)) { emit_signal(SNAME("file_selected"), f); @@ -473,6 +499,13 @@ void FileDialog::update_file_list() { dir_access->list_dir_begin(); + if (dir_access->is_readable(dir_access->get_current_dir().utf8().get_data())) { + message->hide(); + } else { + message->set_text(TTRC("You don't have permission to access contents of this folder.")); + message->show(); + } + 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")); @@ -482,17 +515,11 @@ void FileDialog::update_file_list() { List<String> dirs; bool is_hidden; - String item; - - if (dir_access->is_readable(dir_access->get_current_dir().utf8().get_data())) { - message->hide(); - } else { - message->set_text(TTRC("You don't have permission to access contents of this folder.")); - message->show(); - } + String item = dir_access->get_next(); - while ((item = dir_access->get_next()) != "") { + while (!item.is_empty()) { if (item == "." || item == "..") { + item = dir_access->get_next(); continue; } @@ -505,6 +532,7 @@ void FileDialog::update_file_list() { dirs.push_back(item); } } + item = dir_access->get_next(); } dirs.sort_custom<NaturalNoCaseComparator>(); @@ -625,7 +653,7 @@ void FileDialog::update_filters() { all_filters += ", ..."; } - filter->add_item(String(TTRC("All Recognized")) + " (" + all_filters + ")"); + filter->add_item(RTR("All Recognized") + " (" + all_filters + ")"); } for (int i = 0; i < filters.size(); i++) { String flt = filters[i].get_slice(";", 0).strip_edges(); @@ -637,7 +665,7 @@ void FileDialog::update_filters() { } } - filter->add_item(TTRC("All Files (*)")); + filter->add_item(RTR("All Files") + " (*)"); } void FileDialog::clear_filters() { @@ -647,6 +675,7 @@ void FileDialog::clear_filters() { } void FileDialog::add_filter(const String &p_filter) { + ERR_FAIL_COND_MSG(p_filter.begins_with("."), "Filter must be \"filename.extension\", can't start with dot."); filters.push_back(p_filter); update_filters(); invalidate(); @@ -685,13 +714,7 @@ void FileDialog::set_current_file(const String &p_file) { file->set_text(p_file); update_dir(); invalidate(); - int lp = p_file.rfind("."); - if (lp != -1) { - file->select(0, lp); - if (file->is_inside_tree() && !get_tree()->is_node_being_edited(file)) { - file->grab_focus(); - } - } + _focus_file_text(); } void FileDialog::set_current_path(const String &p_path) { @@ -835,7 +858,7 @@ void FileDialog::_select_drive(int p_idx) { _push_history(); } -void FileDialog::_update_drives() { +void FileDialog::_update_drives(bool p_select) { int dc = dir_access->get_drive_count(); if (dc == 0 || access != ACCESS_FILESYSTEM) { drives->hide(); @@ -853,7 +876,9 @@ void FileDialog::_update_drives() { drives->add_item(dir_access->get_drive(i)); } - drives->select(dir_access->get_current_drive()); + if (p_select) { + drives->select(dir_access->get_current_drive()); + } } } @@ -998,8 +1023,8 @@ FileDialog::FileDialog() { message = memnew(Label); message->hide(); message->set_anchors_and_offsets_preset(Control::PRESET_WIDE); - message->set_align(Label::ALIGN_CENTER); - message->set_valign(Label::VALIGN_CENTER); + message->set_horizontal_alignment(HORIZONTAL_ALIGNMENT_CENTER); + message->set_vertical_alignment(VERTICAL_ALIGNMENT_CENTER); tree->add_child(message); file_box = memnew(HBoxContainer); diff --git a/scene/gui/file_dialog.h b/scene/gui/file_dialog.h index b5190bdab1..7a50efe40f 100644 --- a/scene/gui/file_dialog.h +++ b/scene/gui/file_dialog.h @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ @@ -70,7 +70,6 @@ private: Button *makedir; Access access = ACCESS_RESOURCES; - //Button *action; VBoxContainer *vbox; FileMode mode; LineEdit *dir; @@ -113,6 +112,8 @@ private: void update_file_list(); void update_filters(); + void _focus_file_text(); + void _tree_multi_selected(Object *p_object, int p_cell, bool p_selected); void _tree_selected(); @@ -130,7 +131,7 @@ private: void _go_back(); void _go_forward(); - void _update_drives(); + void _update_drives(bool p_select = true); virtual void unhandled_input(const Ref<InputEvent> &p_event) override; diff --git a/scene/gui/flow_container.cpp b/scene/gui/flow_container.cpp new file mode 100644 index 0000000000..3bd21f96b2 --- /dev/null +++ b/scene/gui/flow_container.cpp @@ -0,0 +1,276 @@ +/*************************************************************************/ +/* flow_container.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 "flow_container.h" + +struct _LineData { + int child_count = 0; + int min_line_height = 0; + int min_line_length = 0; + int stretch_avail = 0; + float stretch_ratio_total = 0; +}; + +void FlowContainer::_resort() { + int separation_horizontal = get_theme_constant(SNAME("hseparation")); + int separation_vertical = get_theme_constant(SNAME("vseparation")); + + bool rtl = is_layout_rtl(); + + Map<Control *, Size2i> children_minsize_cache; + + Vector<_LineData> lines_data; + + Vector2i ofs; + int line_height = 0; + int line_length = 0; + float line_stretch_ratio_total = 0; + int current_container_size = vertical ? get_rect().size.y : get_rect().size.x; + int children_in_current_line = 0; + + // First pass for line wrapping and minimum size calculation. + for (int i = 0; i < get_child_count(); i++) { + Control *child = Object::cast_to<Control>(get_child(i)); + if (!child || !child->is_visible_in_tree()) { + continue; + } + if (child->is_set_as_top_level()) { + continue; + } + + Size2i child_msc = child->get_combined_minimum_size(); + + if (vertical) { /* VERTICAL */ + if (children_in_current_line > 0) { + ofs.y += separation_vertical; + } + if (ofs.y + child_msc.y > current_container_size) { + line_length = ofs.y - separation_vertical; + 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.y = 0; + line_height = 0; + line_stretch_ratio_total = 0; + children_in_current_line = 0; + } + + line_height = MAX(line_height, child_msc.x); + if (child->get_v_size_flags() & SIZE_EXPAND) { + line_stretch_ratio_total += child->get_stretch_ratio(); + } + ofs.y += child_msc.y; + + } else { /* HORIZONTAL */ + if (children_in_current_line > 0) { + ofs.x += separation_horizontal; + } + if (ofs.x + child_msc.x > current_container_size) { + line_length = ofs.x - separation_horizontal; + 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.x = 0; + line_height = 0; + line_stretch_ratio_total = 0; + children_in_current_line = 0; + } + + line_height = MAX(line_height, child_msc.y); + if (child->get_h_size_flags() & SIZE_EXPAND) { + line_stretch_ratio_total += child->get_stretch_ratio(); + } + ofs.x += child_msc.x; + } + + children_minsize_cache[child] = child_msc; + children_in_current_line++; + } + line_length = vertical ? (ofs.y) : (ofs.x); + lines_data.push_back(_LineData{ children_in_current_line, line_height, line_length, current_container_size - line_length, line_stretch_ratio_total }); + + // Second pass for in-line expansion and alignment. + + int current_line_idx = 0; + int child_idx_in_line = 0; + + ofs.x = 0; + ofs.y = 0; + + for (int i = 0; i < get_child_count(); i++) { + Control *child = Object::cast_to<Control>(get_child(i)); + if (!child || !child->is_visible_in_tree()) { + continue; + } + if (child->is_set_as_top_level()) { + continue; + } + Size2i child_size = children_minsize_cache[child]; + + _LineData line_data = lines_data[current_line_idx]; + if (child_idx_in_line >= lines_data[current_line_idx].child_count) { + current_line_idx++; + child_idx_in_line = 0; + if (vertical) { + ofs.x += line_data.min_line_height + separation_horizontal; + ofs.y = 0; + } else { + ofs.x = 0; + ofs.y += line_data.min_line_height + separation_vertical; + } + line_data = lines_data[current_line_idx]; + } + + if (vertical) { /* VERTICAL */ + if (child->get_h_size_flags() & (SIZE_FILL | SIZE_SHRINK_CENTER | SIZE_SHRINK_END)) { + child_size.width = line_data.min_line_height; + } + + if (child->get_v_size_flags() & SIZE_EXPAND) { + int stretch = line_data.stretch_avail * child->get_stretch_ratio() / line_data.stretch_ratio_total; + child_size.height += stretch; + } + + } else { /* HORIZONTAL */ + if (child->get_v_size_flags() & (SIZE_FILL | SIZE_SHRINK_CENTER | SIZE_SHRINK_END)) { + child_size.height = line_data.min_line_height; + } + + if (child->get_h_size_flags() & SIZE_EXPAND) { + int stretch = line_data.stretch_avail * child->get_stretch_ratio() / line_data.stretch_ratio_total; + child_size.width += stretch; + } + } + + Rect2 child_rect = Rect2(ofs, child_size); + if (rtl) { + child_rect.position.x = get_rect().size.x - child_rect.position.x - child_rect.size.width; + } + + fit_child_in_rect(child, child_rect); + + if (vertical) { /* VERTICAL */ + ofs.y += child_size.height + separation_vertical; + } else { /* HORIZONTAL */ + ofs.x += child_size.width + separation_horizontal; + } + + child_idx_in_line++; + } + cached_size = (vertical ? ofs.x : ofs.y) + line_height; + cached_line_count = lines_data.size(); +} + +Size2 FlowContainer::get_minimum_size() const { + Size2i minimum; + + for (int i = 0; i < get_child_count(); i++) { + Control *c = Object::cast_to<Control>(get_child(i)); + if (!c) { + continue; + } + if (c->is_set_as_top_level()) { + continue; + } + + if (!c->is_visible()) { + continue; + } + + Size2i size = c->get_combined_minimum_size(); + + if (vertical) { /* VERTICAL */ + minimum.height = MAX(minimum.height, size.height); + minimum.width = cached_size; + + } else { /* HORIZONTAL */ + minimum.width = MAX(minimum.width, size.width); + minimum.height = cached_size; + } + } + + return minimum; +} + +Vector<int> FlowContainer::get_allowed_size_flags_horizontal() const { + Vector<int> flags; + flags.append(SIZE_FILL); + if (!vertical) { + flags.append(SIZE_EXPAND); + } + flags.append(SIZE_SHRINK_BEGIN); + flags.append(SIZE_SHRINK_CENTER); + flags.append(SIZE_SHRINK_END); + return flags; +} + +Vector<int> FlowContainer::get_allowed_size_flags_vertical() const { + Vector<int> flags; + flags.append(SIZE_FILL); + if (vertical) { + flags.append(SIZE_EXPAND); + } + flags.append(SIZE_SHRINK_BEGIN); + flags.append(SIZE_SHRINK_CENTER); + flags.append(SIZE_SHRINK_END); + return flags; +} + +void FlowContainer::_notification(int p_what) { + switch (p_what) { + case NOTIFICATION_SORT_CHILDREN: { + _resort(); + update_minimum_size(); + } break; + + case NOTIFICATION_THEME_CHANGED: { + update_minimum_size(); + } break; + + case NOTIFICATION_TRANSLATION_CHANGED: + case NOTIFICATION_LAYOUT_DIRECTION_CHANGED: { + queue_sort(); + } break; + } +} + +int FlowContainer::get_line_count() const { + return cached_line_count; +} + +FlowContainer::FlowContainer(bool p_vertical) { + vertical = p_vertical; +} + +void FlowContainer::_bind_methods() { + ClassDB::bind_method(D_METHOD("get_line_count"), &FlowContainer::get_line_count); +} diff --git a/scene/gui/flow_container.h b/scene/gui/flow_container.h new file mode 100644 index 0000000000..a2da43e071 --- /dev/null +++ b/scene/gui/flow_container.h @@ -0,0 +1,79 @@ +/*************************************************************************/ +/* flow_container.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 FLOW_CONTAINER_H +#define FLOW_CONTAINER_H + +#include "scene/gui/container.h" + +class FlowContainer : public Container { + GDCLASS(FlowContainer, Container); + +private: + int cached_size = 0; + int cached_line_count = 0; + + bool vertical = false; + + void _resort(); + +protected: + void _notification(int p_what); + + static void _bind_methods(); + +public: + int get_line_count() const; + + virtual Size2 get_minimum_size() const override; + + virtual Vector<int> get_allowed_size_flags_horizontal() const override; + virtual Vector<int> get_allowed_size_flags_vertical() const override; + + FlowContainer(bool p_vertical = false); +}; + +class HFlowContainer : public FlowContainer { + GDCLASS(HFlowContainer, FlowContainer); + +public: + HFlowContainer() : + FlowContainer(false) {} +}; + +class VFlowContainer : public FlowContainer { + GDCLASS(VFlowContainer, FlowContainer); + +public: + VFlowContainer() : + FlowContainer(true) {} +}; + +#endif // FLOW_CONTAINER_H diff --git a/scene/gui/gradient_edit.cpp b/scene/gui/gradient_edit.cpp index 5d024d3be7..0690acbe16 100644 --- a/scene/gui/gradient_edit.cpp +++ b/scene/gui/gradient_edit.cpp @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ @@ -39,6 +39,10 @@ GradientEdit::GradientEdit() { 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); } @@ -47,7 +51,7 @@ int GradientEdit::_get_point_from_pos(int x) { 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 + // 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) { @@ -84,8 +88,8 @@ void GradientEdit::gui_input(const Ref<InputEvent> &p_event) { Ref<InputEventKey> k = p_event; - if (k.is_valid() && k->is_pressed() && k->get_keycode() == KEY_DELETE && grabbed != -1) { - points.remove(grabbed); + if (k.is_valid() && k->is_pressed() && k->get_keycode() == Key::KEY_DELETE && grabbed != -1) { + points.remove_at(grabbed); grabbed = -1; grabbing = false; update(); @@ -94,18 +98,18 @@ void GradientEdit::gui_input(const Ref<InputEvent> &p_event) { } Ref<InputEventMouseButton> mb = p_event; - //Show color picker on double click. - if (mb.is_valid() && mb->get_button_index() == 1 && mb->is_double_click() && mb->is_pressed()) { + // 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() == 2 && mb->is_pressed()) { + // 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(grabbed); + points.remove_at(grabbed); grabbed = -1; grabbing = false; update(); @@ -114,20 +118,20 @@ void GradientEdit::gui_input(const Ref<InputEvent> &p_event) { } } - //Hold alt key to duplicate selected color - if (mb.is_valid() && mb->get_button_index() == 1 && mb->is_pressed() && mb->is_alt_pressed()) { + // 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 newPoint = points[grabbed]; - newPoint.offset = CLAMP(x / float(total_w), 0, 1); + Gradient::Point new_point = points[grabbed]; + new_point.offset = CLAMP(x / float(total_w), 0, 1); - points.push_back(newPoint); + points.push_back(new_point); points.sort(); for (int i = 0; i < points.size(); ++i) { - if (points[i].offset == newPoint.offset) { + if (points[i].offset == new_point.offset) { grabbed = i; break; } @@ -138,8 +142,8 @@ void GradientEdit::gui_input(const Ref<InputEvent> &p_event) { } } - //select - if (mb.is_valid() && mb->get_button_index() == 1 && mb->is_pressed()) { + // 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; @@ -158,16 +162,16 @@ void GradientEdit::gui_input(const Ref<InputEvent> &p_event) { return; } - //insert - Gradient::Point newPoint; - newPoint.offset = CLAMP(x / float(total_w), 0, 1); + // 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 < newPoint.offset) { + if (points[i].offset < new_point.offset) { pos = i; } } @@ -191,12 +195,12 @@ void GradientEdit::gui_input(const Ref<InputEvent> &p_event) { prev = points[pos]; } - newPoint.color = prev.color.lerp(next.color, (newPoint.offset - prev.offset) / (next.offset - prev.offset)); + new_point.color = prev.color.lerp(next.color, (new_point.offset - prev.offset) / (next.offset - prev.offset)); - points.push_back(newPoint); + points.push_back(new_point); points.sort(); for (int i = 0; i < points.size(); i++) { - if (points[i].offset == newPoint.offset) { + if (points[i].offset == new_point.offset) { grabbed = i; break; } @@ -205,7 +209,7 @@ void GradientEdit::gui_input(const Ref<InputEvent> &p_event) { emit_signal(SNAME("ramp_changed")); } - if (mb.is_valid() && mb->get_button_index() == 1 && !mb->is_pressed()) { + if (mb.is_valid() && mb->get_button_index() == MouseButton::LEFT && !mb->is_pressed()) { if (grabbing) { grabbing = false; emit_signal(SNAME("ramp_changed")); @@ -223,7 +227,7 @@ void GradientEdit::gui_input(const Ref<InputEvent> &p_event) { float newofs = CLAMP(x / float(total_w), 0, 1); // Snap to "round" coordinates if holding Ctrl. - // Be more precise if holding Shift as well + // 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()) { @@ -283,119 +287,85 @@ void GradientEdit::gui_input(const Ref<InputEvent> &p_event) { } void GradientEdit::_notification(int p_what) { - if (p_what == 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)); + 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; - if (p_what == NOTIFICATION_ENTER_TREE || p_what == NOTIFICATION_THEME_CHANGED) { - draw_spacing = BASE_SPACING * get_theme_default_base_scale(); - draw_point_width = BASE_POINT_WIDTH * get_theme_default_base_scale(); - } + case NOTIFICATION_DRAW: { + int w = get_size().x; + int h = get_size().y; - if (p_what == 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. + } - 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; - 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 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 color ramp - Gradient::Point prev; - prev.offset = 0; - if (points.size() == 0) { - prev.color = Color(0, 0, 0); //Draw black rectangle if we have no points - } else { - prev.color = points[0].color; //Extend color of first point to the beginning. - } + // Draw point markers. + for (int i = 0; i < points.size(); i++) { + Color col = points[i].color.inverted(); + col.a = 0.9; - for (int i = -1; i < points.size(); i++) { - Gradient::Point next; - //If there is no next point - if (i + 1 == points.size()) { - if (points.size() == 0) { - next.color = Color(0, 0, 0); //Draw black rectangle if we have no points - } else { - next.color = points[i].color; //Extend color of last point to the end. + 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); } - next.offset = 1; - } else { - next = points[i + 1]; } - if (prev.offset == next.offset) { - prev = next; - continue; + // 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)); } - Vector<Vector2> points; - Vector<Color> colors; - points.push_back(Vector2(prev.offset * total_w, h)); - points.push_back(Vector2(prev.offset * total_w, 0)); - points.push_back(Vector2(next.offset * total_w, 0)); - points.push_back(Vector2(next.offset * total_w, h)); - colors.push_back(prev.color); - colors.push_back(prev.color); - colors.push_back(next.color); - colors.push_back(next.color); - draw_primitive(points, colors, Vector<Point2>()); - prev = next; - } - - //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 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)); } - } - - //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; - if (p_what == NOTIFICATION_VISIBILITY_CHANGED) { - if (!is_visible()) { - grabbing = false; - } + case NOTIFICATION_VISIBILITY_CHANGED: { + if (!is_visible()) { + grabbing = false; + } + } break; } } @@ -448,12 +418,25 @@ void GradientEdit::set_points(Vector<Gradient::Point> &p_points) { } 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; +} + void GradientEdit::_bind_methods() { ADD_SIGNAL(MethodInfo("ramp_changed")); } diff --git a/scene/gui/gradient_edit.h b/scene/gui/gradient_edit.h index f3a39daaf6..67531d4f4a 100644 --- a/scene/gui/gradient_edit.h +++ b/scene/gui/gradient_edit.h @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ @@ -33,7 +33,6 @@ #include "scene/gui/color_picker.h" #include "scene/gui/popup.h" -#include "scene/resources/default_theme/theme_data.h" #include "scene/resources/gradient.h" class GradientEdit : public Control { @@ -45,6 +44,10 @@ class GradientEdit : public Control { 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; @@ -69,6 +72,10 @@ public: 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(); + virtual Size2 get_minimum_size() const override; GradientEdit(); diff --git a/scene/gui/graph_edit.cpp b/scene/gui/graph_edit.cpp index 35e31be9af..1394b4192f 100644 --- a/scene/gui/graph_edit.cpp +++ b/scene/gui/graph_edit.cpp @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ @@ -35,6 +35,7 @@ #include "core/os/keyboard.h" #include "scene/gui/box_container.h" #include "scene/gui/button.h" +#include "scene/gui/view_panner.h" constexpr int MINIMAP_OFFSET = 12; constexpr int MINIMAP_PADDING = 5; @@ -150,7 +151,7 @@ void GraphEditMinimap::gui_input(const Ref<InputEvent> &p_ev) { Ref<InputEventMouseButton> mb = p_ev; Ref<InputEventMouseMotion> mm = p_ev; - if (mb.is_valid() && mb->get_button_index() == MOUSE_BUTTON_LEFT) { + if (mb.is_valid() && mb->get_button_index() == MouseButton::LEFT) { if (mb->is_pressed()) { is_pressing = true; @@ -422,82 +423,123 @@ void GraphEdit::remove_child_notify(Node *p_child) { } void GraphEdit::_notification(int p_what) { - if (p_what == NOTIFICATION_ENTER_TREE || p_what == NOTIFICATION_THEME_CHANGED) { - port_grab_distance_horizontal = get_theme_constant(SNAME("port_grab_distance_horizontal")); - port_grab_distance_vertical = get_theme_constant(SNAME("port_grab_distance_vertical")); + switch (p_what) { + case NOTIFICATION_ENTER_TREE: + case NOTIFICATION_THEME_CHANGED: { + port_grab_distance_horizontal = get_theme_constant(SNAME("port_grab_distance_horizontal")); + port_grab_distance_vertical = get_theme_constant(SNAME("port_grab_distance_vertical")); + + zoom_minus->set_icon(get_theme_icon(SNAME("minus"))); + zoom_reset->set_icon(get_theme_icon(SNAME("reset"))); + zoom_plus->set_icon(get_theme_icon(SNAME("more"))); + snap_button->set_icon(get_theme_icon(SNAME("snap"))); + minimap_button->set_icon(get_theme_icon(SNAME("minimap"))); + layout_button->set_icon(get_theme_icon(SNAME("layout"))); + + zoom_label->set_custom_minimum_size(Size2(48, 0) * get_theme_default_base_scale()); + } break; - zoom_minus->set_icon(get_theme_icon(SNAME("minus"))); - zoom_reset->set_icon(get_theme_icon(SNAME("reset"))); - zoom_plus->set_icon(get_theme_icon(SNAME("more"))); - snap_button->set_icon(get_theme_icon(SNAME("snap"))); - minimap_button->set_icon(get_theme_icon(SNAME("minimap"))); - layout_button->set_icon(get_theme_icon(SNAME("layout"))); + case NOTIFICATION_READY: { + Size2 hmin = h_scroll->get_combined_minimum_size(); + Size2 vmin = v_scroll->get_combined_minimum_size(); - zoom_label->set_custom_minimum_size(Size2(48, 0) * get_theme_default_base_scale()); - } - if (p_what == NOTIFICATION_READY) { - Size2 hmin = h_scroll->get_combined_minimum_size(); - Size2 vmin = v_scroll->get_combined_minimum_size(); + h_scroll->set_anchor_and_offset(SIDE_LEFT, ANCHOR_BEGIN, 0); + h_scroll->set_anchor_and_offset(SIDE_RIGHT, ANCHOR_END, 0); + h_scroll->set_anchor_and_offset(SIDE_TOP, ANCHOR_END, -hmin.height); + h_scroll->set_anchor_and_offset(SIDE_BOTTOM, ANCHOR_END, 0); - h_scroll->set_anchor_and_offset(SIDE_LEFT, ANCHOR_BEGIN, 0); - h_scroll->set_anchor_and_offset(SIDE_RIGHT, ANCHOR_END, 0); - h_scroll->set_anchor_and_offset(SIDE_TOP, ANCHOR_END, -hmin.height); - h_scroll->set_anchor_and_offset(SIDE_BOTTOM, ANCHOR_END, 0); + v_scroll->set_anchor_and_offset(SIDE_LEFT, ANCHOR_END, -vmin.width); + v_scroll->set_anchor_and_offset(SIDE_RIGHT, ANCHOR_END, 0); + v_scroll->set_anchor_and_offset(SIDE_TOP, ANCHOR_BEGIN, 0); + v_scroll->set_anchor_and_offset(SIDE_BOTTOM, ANCHOR_END, 0); + } break; - v_scroll->set_anchor_and_offset(SIDE_LEFT, ANCHOR_END, -vmin.width); - v_scroll->set_anchor_and_offset(SIDE_RIGHT, ANCHOR_END, 0); - v_scroll->set_anchor_and_offset(SIDE_TOP, ANCHOR_BEGIN, 0); - v_scroll->set_anchor_and_offset(SIDE_BOTTOM, ANCHOR_END, 0); - } - if (p_what == NOTIFICATION_DRAW) { - draw_style_box(get_theme_stylebox(SNAME("bg")), Rect2(Point2(), get_size())); + case NOTIFICATION_DRAW: { + draw_style_box(get_theme_stylebox(SNAME("bg")), Rect2(Point2(), get_size())); - if (is_using_snap()) { - //draw grid + if (is_using_snap()) { + // Draw grid. + int snap = get_snap(); - int snap = get_snap(); + Vector2 offset = get_scroll_ofs() / zoom; + Size2 size = get_size() / zoom; - Vector2 offset = get_scroll_ofs() / zoom; - Size2 size = get_size() / zoom; + Point2i from = (offset / float(snap)).floor(); + Point2i len = (size / float(snap)).floor() + Vector2(1, 1); - Point2i from = (offset / float(snap)).floor(); - Point2i len = (size / float(snap)).floor() + Vector2(1, 1); + Color grid_minor = get_theme_color(SNAME("grid_minor")); + Color grid_major = get_theme_color(SNAME("grid_major")); - Color grid_minor = get_theme_color(SNAME("grid_minor")); - Color grid_major = get_theme_color(SNAME("grid_major")); + for (int i = from.x; i < from.x + len.x; i++) { + Color color; - for (int i = from.x; i < from.x + len.x; i++) { - Color color; + if (ABS(i) % 10 == 0) { + color = grid_major; + } else { + color = grid_minor; + } - if (ABS(i) % 10 == 0) { - color = grid_major; - } else { - color = grid_minor; + float base_ofs = i * snap * zoom - offset.x * zoom; + draw_line(Vector2(base_ofs, 0), Vector2(base_ofs, get_size().height), color); } - float base_ofs = i * snap * zoom - offset.x * zoom; - draw_line(Vector2(base_ofs, 0), Vector2(base_ofs, get_size().height), color); - } + for (int i = from.y; i < from.y + len.y; i++) { + Color color; - for (int i = from.y; i < from.y + len.y; i++) { - Color color; + if (ABS(i) % 10 == 0) { + color = grid_major; + } else { + color = grid_minor; + } - if (ABS(i) % 10 == 0) { - color = grid_major; - } else { - color = grid_minor; + float base_ofs = i * snap * zoom - offset.y * zoom; + draw_line(Vector2(0, base_ofs), Vector2(get_size().width, base_ofs), color); } - - float base_ofs = i * snap * zoom - offset.y * zoom; - draw_line(Vector2(0, base_ofs), Vector2(get_size().width, base_ofs), color); } + } break; + + case NOTIFICATION_RESIZED: { + _update_scroll(); + top_layer->update(); + minimap->update(); + } break; + } +} + +void GraphEdit::_update_comment_enclosed_nodes_list(GraphNode *p_node, HashMap<StringName, Vector<GraphNode *>> &p_comment_enclosed_nodes) { + Rect2 comment_node_rect = p_node->get_rect(); + Vector<GraphNode *> enclosed_nodes; + + for (int i = 0; i < get_child_count(); i++) { + GraphNode *gn = Object::cast_to<GraphNode>(get_child(i)); + if (!gn || gn->is_selected()) { + continue; + } + + Rect2 node_rect = gn->get_rect(); + bool included = comment_node_rect.encloses(node_rect); + if (included) { + enclosed_nodes.push_back(gn); } } - if (p_what == NOTIFICATION_RESIZED) { - _update_scroll(); - top_layer->update(); - minimap->update(); + p_comment_enclosed_nodes.set(p_node->get_name(), enclosed_nodes); +} + +void GraphEdit::_set_drag_comment_enclosed_nodes(GraphNode *p_node, HashMap<StringName, Vector<GraphNode *>> &p_comment_enclosed_nodes, bool p_drag) { + for (int i = 0; i < p_comment_enclosed_nodes[p_node->get_name()].size(); i++) { + p_comment_enclosed_nodes[p_node->get_name()][i]->set_drag(p_drag); + } +} + +void GraphEdit::_set_position_of_comment_enclosed_nodes(GraphNode *p_node, HashMap<StringName, Vector<GraphNode *>> &p_comment_enclosed_nodes, Vector2 p_drag_accum) { + for (int i = 0; i < p_comment_enclosed_nodes[p_node->get_name()].size(); i++) { + Vector2 pos = (p_comment_enclosed_nodes[p_node->get_name()][i]->get_drag_from() * zoom + drag_accum) / zoom; + if (is_using_snap() ^ Input::get_singleton()->is_key_pressed(Key::CTRL)) { + const int snap = get_snap(); + pos = pos.snapped(Vector2(snap, snap)); + } + p_comment_enclosed_nodes[p_node->get_name()][i]->set_position_offset(pos); } } @@ -512,15 +554,13 @@ bool GraphEdit::_filter_input(const Point2 &p_point) { } for (int j = 0; j < gn->get_connection_output_count(); j++) { - Vector2 pos = gn->get_connection_output_position(j) + gn->get_position(); - if (is_in_hot_zone(pos / zoom, p_point / zoom, port_size, false)) { + if (is_in_output_hotzone(gn, j, p_point / zoom, port_size)) { return true; } } for (int j = 0; j < gn->get_connection_input_count(); j++) { - Vector2 pos = gn->get_connection_input_position(j) + gn->get_position(); - if (is_in_hot_zone(pos / zoom, p_point / zoom, port_size, true)) { + if (is_in_input_hotzone(gn, j, p_point / zoom, port_size)) { return true; } } @@ -531,7 +571,7 @@ bool GraphEdit::_filter_input(const Point2 &p_point) { void GraphEdit::_top_layer_input(const Ref<InputEvent> &p_ev) { Ref<InputEventMouseButton> mb = p_ev; - if (mb.is_valid() && mb->get_button_index() == MOUSE_BUTTON_LEFT && mb->is_pressed()) { + if (mb.is_valid() && mb->get_button_index() == MouseButton::LEFT && mb->is_pressed()) { Ref<Texture2D> port = get_theme_icon(SNAME("port"), SNAME("GraphNode")); Vector2i port_size = Vector2i(port->get_width(), port->get_height()); @@ -545,7 +585,7 @@ void GraphEdit::_top_layer_input(const Ref<InputEvent> &p_ev) { for (int j = 0; j < gn->get_connection_output_count(); j++) { Vector2 pos = gn->get_connection_output_position(j) + gn->get_position(); - if (is_in_hot_zone(pos / zoom, click_pos, port_size, false)) { + if (is_in_output_hotzone(gn, j, click_pos, port_size)) { if (valid_left_disconnect_types.has(gn->get_connection_output_type(j))) { //check disconnect for (const Connection &E : connections) { @@ -565,6 +605,7 @@ void GraphEdit::_top_layer_input(const Ref<InputEvent> &p_ev) { 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; } @@ -581,13 +622,14 @@ void GraphEdit::_top_layer_input(const Ref<InputEvent> &p_ev) { connecting_target = false; connecting_to = pos; just_disconnected = false; + emit_signal(SNAME("connection_drag_started"), connecting_from, connecting_index, true); return; } } for (int j = 0; j < gn->get_connection_input_count(); j++) { Vector2 pos = gn->get_connection_input_position(j) + gn->get_position(); - if (is_in_hot_zone(pos / zoom, click_pos, port_size, true)) { + if (is_in_input_hotzone(gn, j, click_pos, port_size)) { if (right_disconnects || valid_right_disconnect_types.has(gn->get_connection_input_type(j))) { //check disconnect for (const Connection &E : connections) { @@ -607,6 +649,7 @@ void GraphEdit::_top_layer_input(const Ref<InputEvent> &p_ev) { 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; } @@ -623,7 +666,7 @@ void GraphEdit::_top_layer_input(const Ref<InputEvent> &p_ev) { connecting_target = false; connecting_to = pos; just_disconnected = false; - + emit_signal(SNAME("connection_drag_started"), connecting_from, connecting_index, false); return; } } @@ -653,7 +696,7 @@ void GraphEdit::_top_layer_input(const Ref<InputEvent> &p_ev) { for (int j = 0; j < gn->get_connection_output_count(); j++) { Vector2 pos = gn->get_connection_output_position(j) + gn->get_position(); int type = gn->get_connection_output_type(j); - if ((type == connecting_type || valid_connection_types.has(ConnType(type, connecting_type))) && is_in_hot_zone(pos / zoom, mpos, port_size, false)) { + if ((type == connecting_type || valid_connection_types.has(ConnType(type, connecting_type))) && is_in_output_hotzone(gn, j, mpos, port_size)) { connecting_target = true; connecting_to = pos; connecting_target_to = gn->get_name(); @@ -665,7 +708,7 @@ void GraphEdit::_top_layer_input(const Ref<InputEvent> &p_ev) { for (int j = 0; j < gn->get_connection_input_count(); j++) { Vector2 pos = gn->get_connection_input_position(j) + gn->get_position(); int type = gn->get_connection_input_type(j); - if ((type == connecting_type || valid_connection_types.has(ConnType(type, connecting_type))) && is_in_hot_zone(pos / zoom, mpos, port_size, true)) { + if ((type == connecting_type || valid_connection_types.has(ConnType(type, connecting_type))) && is_in_input_hotzone(gn, j, mpos, port_size)) { connecting_target = true; connecting_to = pos; connecting_target_to = gn->get_name(); @@ -678,7 +721,7 @@ void GraphEdit::_top_layer_input(const Ref<InputEvent> &p_ev) { } } - if (mb.is_valid() && mb->get_button_index() == MOUSE_BUTTON_LEFT && !mb->is_pressed()) { + if (mb.is_valid() && mb->get_button_index() == MouseButton::LEFT && !mb->is_pressed()) { if (connecting_valid) { if (connecting && connecting_target) { String from = connecting_from; @@ -705,11 +748,9 @@ void GraphEdit::_top_layer_input(const Ref<InputEvent> &p_ev) { } } - connecting = false; - top_layer->update(); - minimap->update(); - update(); - connections_layer->update(); + if (connecting) { + force_connection_drag_end(); + } } } @@ -736,25 +777,29 @@ bool GraphEdit::_check_clickable_control(Control *p_control, const Vector2 &pos) } } -bool GraphEdit::is_in_hot_zone(const Vector2 &pos, const Vector2 &p_mouse_pos, const Vector2i &p_port_size, bool p_left) { - if (p_left) { - if (!Rect2( - pos.x - p_port_size.x / 2 - port_grab_distance_horizontal, - pos.y - p_port_size.y / 2 - port_grab_distance_vertical / 2, - p_port_size.x + port_grab_distance_horizontal, - p_port_size.y + port_grab_distance_vertical) - .has_point(p_mouse_pos)) { - return false; - } +bool GraphEdit::is_in_input_hotzone(GraphNode *p_graph_node, int p_slot_index, const Vector2 &p_mouse_pos, const Vector2i &p_port_size) { + bool success; + if (GDVIRTUAL_CALL(_is_in_input_hotzone, p_graph_node, p_slot_index, p_mouse_pos, success)) { + return success; } else { - if (!Rect2( - pos.x - p_port_size.x / 2, - pos.y - p_port_size.y / 2 - port_grab_distance_vertical / 2, - p_port_size.x + port_grab_distance_horizontal, - p_port_size.y + port_grab_distance_vertical) - .has_point(p_mouse_pos)) { - return false; - } + Vector2 pos = p_graph_node->get_connection_input_position(p_slot_index) + p_graph_node->get_position(); + return is_in_port_hotzone(pos / zoom, p_mouse_pos, p_port_size, true); + } +} + +bool GraphEdit::is_in_output_hotzone(GraphNode *p_graph_node, int p_slot_index, const Vector2 &p_mouse_pos, const Vector2i &p_port_size) { + bool success; + if (GDVIRTUAL_CALL(_is_in_output_hotzone, p_graph_node, p_slot_index, p_mouse_pos, success)) { + return success; + } else { + Vector2 pos = p_graph_node->get_connection_output_position(p_slot_index) + p_graph_node->get_position(); + return is_in_port_hotzone(pos / zoom, p_mouse_pos, p_port_size, false); + } +} + +bool GraphEdit::is_in_port_hotzone(const Vector2 &pos, const Vector2 &p_mouse_pos, const Vector2i &p_port_size, bool p_left) { + if (!Rect2(pos.x - port_grab_distance_horizontal, pos.y - port_grab_distance_vertical, port_grab_distance_horizontal * 2, port_grab_distance_vertical * 2).has_point(p_mouse_pos)) { + return false; } for (int i = 0; i < get_child_count(); i++) { @@ -1029,13 +1074,11 @@ void GraphEdit::set_selected(Node *p_child) { void GraphEdit::gui_input(const Ref<InputEvent> &p_ev) { ERR_FAIL_COND(p_ev.is_null()); + if (panner->gui_input(p_ev, warped_panning ? get_global_rect() : Rect2())) { + return; + } Ref<InputEventMouseMotion> mm = p_ev; - if (mm.is_valid() && (mm->get_button_mask() & MOUSE_BUTTON_MASK_MIDDLE || (mm->get_button_mask() & MOUSE_BUTTON_MASK_LEFT && Input::get_singleton()->is_key_pressed(KEY_SPACE)))) { - Vector2i relative = Input::get_singleton()->warp_mouse_motion(mm, get_global_rect()); - h_scroll->set_value(h_scroll->get_value() - relative.x); - v_scroll->set_value(v_scroll->get_value() - relative.y); - } if (mm.is_valid() && dragging) { if (!moving_selection) { @@ -1052,12 +1095,15 @@ void GraphEdit::gui_input(const Ref<InputEvent> &p_ev) { // Snapping can be toggled temporarily by holding down Ctrl. // This is done here as to not toggle the grid when holding down Ctrl. - if (is_using_snap() ^ Input::get_singleton()->is_key_pressed(KEY_CTRL)) { + if (is_using_snap() ^ Input::get_singleton()->is_key_pressed(Key::CTRL)) { const int snap = get_snap(); pos = pos.snapped(Vector2(snap, snap)); } gn->set_position_offset(pos); + if (gn->is_comment()) { + _set_position_of_comment_enclosed_nodes(gn, comment_enclosed_nodes, drag_accum); + } } } } @@ -1101,7 +1147,7 @@ void GraphEdit::gui_input(const Ref<InputEvent> &p_ev) { Ref<InputEventMouseButton> b = p_ev; if (b.is_valid()) { - if (b->get_button_index() == MOUSE_BUTTON_RIGHT && b->is_pressed()) { + if (b->get_button_index() == MouseButton::RIGHT && b->is_pressed()) { if (box_selecting) { box_selecting = false; for (int i = get_child_count() - 1; i >= 0; i--) { @@ -1122,17 +1168,15 @@ void GraphEdit::gui_input(const Ref<InputEvent> &p_ev) { minimap->update(); } else { if (connecting) { - connecting = false; - top_layer->update(); - minimap->update(); + force_connection_drag_end(); } else { - emit_signal(SNAME("popup_request"), b->get_global_position()); + emit_signal(SNAME("popup_request"), b->get_position()); } } } - if (b->get_button_index() == MOUSE_BUTTON_LEFT && !b->is_pressed() && dragging) { - if (!just_selected && drag_accum == Vector2() && Input::get_singleton()->is_key_pressed(KEY_CTRL)) { + if (b->get_button_index() == MouseButton::LEFT && !b->is_pressed() && dragging) { + if (!just_selected && drag_accum == Vector2() && Input::get_singleton()->is_key_pressed(Key::CTRL)) { //deselect current node for (int i = get_child_count() - 1; i >= 0; i--) { GraphNode *gn = Object::cast_to<GraphNode>(get_child(i)); @@ -1153,6 +1197,9 @@ void GraphEdit::gui_input(const Ref<InputEvent> &p_ev) { GraphNode *gn = Object::cast_to<GraphNode>(get_child(i)); if (gn && gn->is_selected()) { gn->set_drag(false); + if (gn->is_comment()) { + _set_drag_comment_enclosed_nodes(gn, comment_enclosed_nodes, false); + } } } } @@ -1170,7 +1217,7 @@ void GraphEdit::gui_input(const Ref<InputEvent> &p_ev) { connections_layer->update(); } - if (b->get_button_index() == MOUSE_BUTTON_LEFT && b->is_pressed()) { + if (b->get_button_index() == MouseButton::LEFT && b->is_pressed()) { GraphNode *gn = nullptr; for (int i = get_child_count() - 1; i >= 0; i--) { @@ -1196,7 +1243,7 @@ void GraphEdit::gui_input(const Ref<InputEvent> &p_ev) { dragging = true; drag_accum = Vector2(); just_selected = !gn->is_selected(); - if (!gn->is_selected() && !Input::get_singleton()->is_key_pressed(KEY_CTRL)) { + 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) { @@ -1220,6 +1267,10 @@ void GraphEdit::gui_input(const Ref<InputEvent> &p_ev) { } if (o_gn->is_selected()) { o_gn->set_drag(true); + if (o_gn->is_comment()) { + _update_comment_enclosed_nodes_list(o_gn, comment_enclosed_nodes); + _set_drag_comment_enclosed_nodes(o_gn, comment_enclosed_nodes, true); + } } } @@ -1227,7 +1278,7 @@ void GraphEdit::gui_input(const Ref<InputEvent> &p_ev) { if (_filter_input(b->get_position())) { return; } - if (Input::get_singleton()->is_key_pressed(KEY_SPACE)) { + if (panner->is_panning()) { return; } @@ -1272,29 +1323,13 @@ void GraphEdit::gui_input(const Ref<InputEvent> &p_ev) { } } - if (b->get_button_index() == MOUSE_BUTTON_LEFT && !b->is_pressed() && box_selecting) { + if (b->get_button_index() == MouseButton::LEFT && !b->is_pressed() && box_selecting) { box_selecting = false; box_selecting_rect = Rect2(); previous_selected.clear(); top_layer->update(); minimap->update(); } - - int scroll_direction = (b->get_button_index() == MOUSE_BUTTON_WHEEL_DOWN) - (b->get_button_index() == MOUSE_BUTTON_WHEEL_UP); - if (scroll_direction != 0) { - if (b->is_ctrl_pressed()) { - if (b->is_shift_pressed()) { - // Horizontal scrolling. - h_scroll->set_value(h_scroll->get_value() + (h_scroll->get_page() * b->get_factor() / 8) * scroll_direction); - } else { - // Vertical scrolling. - v_scroll->set_value(v_scroll->get_value() + (v_scroll->get_page() * b->get_factor() / 8) * scroll_direction); - } - } else { - // Zooming. - set_zoom_custom(scroll_direction < 0 ? zoom * zoom_step : zoom / zoom_step, b->get_position()); - } - } } if (p_ev->is_pressed()) { @@ -1325,6 +1360,23 @@ void GraphEdit::gui_input(const Ref<InputEvent> &p_ev) { } } +void GraphEdit::_scroll_callback(Vector2 p_scroll_vec, bool p_alt) { + if (p_scroll_vec.x != 0) { + h_scroll->set_value(h_scroll->get_value() + (h_scroll->get_page() * Math::abs(p_scroll_vec.x) / 8) * SIGN(p_scroll_vec.x)); + } else { + v_scroll->set_value(v_scroll->get_value() + (v_scroll->get_page() * Math::abs(p_scroll_vec.y) / 8) * SIGN(p_scroll_vec.y)); + } +} + +void GraphEdit::_pan_callback(Vector2 p_scroll_vec) { + h_scroll->set_value(h_scroll->get_value() - p_scroll_vec.x); + v_scroll->set_value(v_scroll->get_value() - p_scroll_vec.y); +} + +void GraphEdit::_zoom_callback(Vector2 p_scroll_vec, Vector2 p_origin, bool p_alt) { + set_zoom_custom(p_scroll_vec.y < 0 ? zoom * zoom_step : zoom / zoom_step, p_origin); +} + void GraphEdit::set_connection_activity(const StringName &p_from, int p_from_port, const StringName &p_to, int p_to_port, float p_activity) { for (Connection &E : connections) { if (E.from == p_from && E.from_port == p_from_port && E.to == p_to && E.to_port == p_to_port) { @@ -1347,6 +1399,26 @@ void GraphEdit::clear_connections() { connections_layer->update(); } +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(); + emit_signal(SNAME("connection_drag_ended")); +} + +void GraphEdit::set_panning_scheme(PanningScheme p_scheme) { + panning_scheme = p_scheme; + panner->set_control_scheme((ViewPanner::ControlScheme)p_scheme); +} + +GraphEdit::PanningScheme GraphEdit::get_panning_scheme() const { + return panning_scheme; +} + void GraphEdit::set_zoom(float p_zoom) { set_zoom_custom(p_zoom, get_size() / 2); } @@ -1612,12 +1684,21 @@ HBoxContainer *GraphEdit::get_zoom_hbox() { return zoom_hb; } +Ref<ViewPanner> GraphEdit::get_panner() { + return panner; +} + +void GraphEdit::set_warped_panning(bool p_warped) { + warped_panning = p_warped; +} + int GraphEdit::_set_operations(SET_OPERATIONS p_operation, Set<StringName> &r_u, const Set<StringName> &r_v) { switch (p_operation) { case GraphEdit::IS_EQUAL: { for (Set<StringName>::Element *E = r_u.front(); E; E = E->next()) { - if (!r_v.has(E->get())) + if (!r_v.has(E->get())) { return 0; + } } return r_u.size() == r_v.size(); } break; @@ -1626,8 +1707,9 @@ int GraphEdit::_set_operations(SET_OPERATIONS p_operation, Set<StringName> &r_u, return 1; } for (Set<StringName>::Element *E = r_u.front(); E; E = E->next()) { - if (!r_v.has(E->get())) + if (!r_v.has(E->get())) { return 0; + } } return 1; } break; @@ -1645,7 +1727,7 @@ int GraphEdit::_set_operations(SET_OPERATIONS p_operation, Set<StringName> &r_u, r_u.insert(E->get()); } } - return r_v.size(); + return r_u.size(); } break; default: break; @@ -1740,8 +1822,8 @@ void GraphEdit::_horizontal_alignment(Dictionary &r_root, Dictionary &r_align, c } } - int start = up.size() / 2; - int end = up.size() % 2 ? start : start + 1; + int start = (up.size() - 1) / 2; + int end = (up.size() - 1) % 2 ? start + 1 : start; for (int p = start; p <= end; p++) { StringName Align = r_align[current_node]; if (Align == current_node && r < up[p].first) { @@ -2060,7 +2142,7 @@ void GraphEdit::arrange_nodes() { } while (u != E->get()); } - //Compute horizontal co-ordinates individually for layers to get uniform gap + // Compute horizontal coordinates individually for layers to get uniform gap. float start_from = origin.x; float largest_node_size = 0.0f; @@ -2118,6 +2200,7 @@ void GraphEdit::_bind_methods() { ClassDB::bind_method(D_METHOD("set_connection_activity", "from", "from_port", "to", "to_port", "amount"), &GraphEdit::set_connection_activity); ClassDB::bind_method(D_METHOD("get_connection_list"), &GraphEdit::_get_connection_list); ClassDB::bind_method(D_METHOD("clear_connections"), &GraphEdit::clear_connections); + ClassDB::bind_method(D_METHOD("force_connection_drag_end"), &GraphEdit::force_connection_drag_end); ClassDB::bind_method(D_METHOD("get_scroll_ofs"), &GraphEdit::get_scroll_ofs); ClassDB::bind_method(D_METHOD("set_scroll_ofs", "ofs"), &GraphEdit::set_scroll_ofs); @@ -2130,6 +2213,9 @@ void GraphEdit::_bind_methods() { ClassDB::bind_method(D_METHOD("is_valid_connection_type", "from_type", "to_type"), &GraphEdit::is_valid_connection_type); ClassDB::bind_method(D_METHOD("get_connection_line", "from", "to"), &GraphEdit::get_connection_line); + ClassDB::bind_method(D_METHOD("set_panning_scheme", "scheme"), &GraphEdit::set_panning_scheme); + ClassDB::bind_method(D_METHOD("get_panning_scheme"), &GraphEdit::get_panning_scheme); + ClassDB::bind_method(D_METHOD("set_zoom", "zoom"), &GraphEdit::set_zoom); ClassDB::bind_method(D_METHOD("get_zoom"), &GraphEdit::get_zoom); @@ -2169,6 +2255,8 @@ void GraphEdit::_bind_methods() { ClassDB::bind_method(D_METHOD("is_right_disconnects_enabled"), &GraphEdit::is_right_disconnects_enabled); ClassDB::bind_method(D_METHOD("_update_scroll_offset"), &GraphEdit::_update_scroll_offset); + GDVIRTUAL_BIND(_is_in_input_hotzone, "graph_node", "slot_index", "mouse_position"); + GDVIRTUAL_BIND(_is_in_output_hotzone, "graph_node", "slot_index", "mouse_position"); ClassDB::bind_method(D_METHOD("get_zoom_hbox"), &GraphEdit::get_zoom_hbox); @@ -2182,6 +2270,7 @@ void GraphEdit::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "scroll_offset"), "set_scroll_ofs", "get_scroll_ofs"); ADD_PROPERTY(PropertyInfo(Variant::INT, "snap_distance"), "set_snap", "get_snap"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "use_snap"), "set_use_snap", "is_using_snap"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "panning_scheme", PROPERTY_HINT_ENUM, "Scroll Zooms,Scroll Pans"), "set_panning_scheme", "get_panning_scheme"); ADD_GROUP("Connection Lines", "connection_lines"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "connection_lines_thickness"), "set_connection_lines_thickness", "get_connection_lines_thickness"); @@ -2213,6 +2302,11 @@ 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, "ofs"))); + ADD_SIGNAL(MethodInfo("connection_drag_started", PropertyInfo(Variant::STRING, "from"), PropertyInfo(Variant::STRING, "slot"), PropertyInfo(Variant::BOOL, "is_output"))); + ADD_SIGNAL(MethodInfo("connection_drag_ended")); + + BIND_ENUM_CONSTANT(SCROLL_ZOOMS); + BIND_ENUM_CONSTANT(SCROLL_PANS); } GraphEdit::GraphEdit() { @@ -2225,12 +2319,16 @@ GraphEdit::GraphEdit() { // Allow zooming 4 times from the default zoom level. zoom_max = (1 * Math::pow(zoom_step, 4)); + panner.instantiate(); + panner->set_callbacks(callable_mp(this, &GraphEdit::_scroll_callback), callable_mp(this, &GraphEdit::_pan_callback), callable_mp(this, &GraphEdit::_zoom_callback)); + top_layer = memnew(GraphEditFilter(this)); add_child(top_layer, false, INTERNAL_MODE_BACK); top_layer->set_mouse_filter(MOUSE_FILTER_PASS); top_layer->set_anchors_and_offsets_preset(Control::PRESET_WIDE); top_layer->connect("draw", callable_mp(this, &GraphEdit::_top_layer_draw)); top_layer->connect("gui_input", callable_mp(this, &GraphEdit::_top_layer_input)); + top_layer->connect("focus_exited", callable_mp(panner.ptr(), &ViewPanner::release_pan_key)); connections_layer = memnew(Control); add_child(connections_layer, false, INTERNAL_MODE_FRONT); @@ -2265,7 +2363,7 @@ GraphEdit::GraphEdit() { zoom_hb->add_child(zoom_label); zoom_label->set_visible(false); zoom_label->set_v_size_flags(Control::SIZE_SHRINK_CENTER); - zoom_label->set_align(Label::ALIGN_CENTER); + zoom_label->set_horizontal_alignment(HORIZONTAL_ALIGNMENT_CENTER); zoom_label->set_custom_minimum_size(Size2(48, 0)); _update_zoom_label(); diff --git a/scene/gui/graph_edit.h b/scene/gui/graph_edit.h index 44e50aa3c2..b0d1944d6e 100644 --- a/scene/gui/graph_edit.h +++ b/scene/gui/graph_edit.h @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ @@ -36,11 +36,10 @@ #include "scene/gui/graph_node.h" #include "scene/gui/label.h" #include "scene/gui/scroll_bar.h" -#include "scene/gui/slider.h" #include "scene/gui/spin_box.h" -#include "scene/gui/texture_rect.h" class GraphEdit; +class ViewPanner; class GraphEditFilter : public Control { GDCLASS(GraphEditFilter, Control); @@ -103,6 +102,12 @@ public: float activity = 0.0; }; + // Should be in sync with ControlScheme in ViewPanner. + enum PanningScheme { + SCROLL_ZOOMS, + SCROLL_PANS, + }; + private: Label *zoom_label; Button *zoom_minus; @@ -122,6 +127,12 @@ private: float port_grab_distance_horizontal = 0.0; float port_grab_distance_vertical; + Ref<ViewPanner> panner; + bool warped_panning = true; + void _scroll_callback(Vector2 p_scroll_vec, bool p_alt); + void _pan_callback(Vector2 p_scroll_vec); + void _zoom_callback(Vector2 p_scroll_vec, Vector2 p_origin, bool p_alt); + bool connecting = false; String connecting_from; bool connecting_out = false; @@ -136,6 +147,7 @@ private: bool connecting_valid = false; Vector2 click_pos; + PanningScheme panning_scheme = SCROLL_ZOOMS; bool dragging = false; bool just_selected = false; bool moving_selection = false; @@ -183,7 +195,9 @@ private: GraphEditMinimap *minimap; void _top_layer_input(const Ref<InputEvent> &p_ev); - bool is_in_hot_zone(const Vector2 &pos, const Vector2 &p_mouse_pos, const Vector2i &p_port_size, bool p_left); + bool is_in_input_hotzone(GraphNode *p_graph_node, int p_slot_index, const Vector2 &p_mouse_pos, const Vector2i &p_port_size); + bool is_in_output_hotzone(GraphNode *p_graph_node, int p_slot_index, const Vector2 &p_mouse_pos, const Vector2i &p_port_size); + bool is_in_port_hotzone(const Vector2 &pos, const Vector2 &p_mouse_pos, const Vector2i &p_port_size, bool p_left); void _top_layer_draw(); void _connections_layer_draw(); @@ -217,6 +231,11 @@ private: Set<int> valid_left_disconnect_types; Set<int> valid_right_disconnect_types; + HashMap<StringName, Vector<GraphNode *>> comment_enclosed_nodes; + void _update_comment_enclosed_nodes_list(GraphNode *p_node, HashMap<StringName, Vector<GraphNode *>> &p_comment_enclosed_nodes); + void _set_drag_comment_enclosed_nodes(GraphNode *p_node, HashMap<StringName, Vector<GraphNode *>> &p_comment_enclosed_nodes, bool p_drag); + void _set_position_of_comment_enclosed_nodes(GraphNode *p_node, HashMap<StringName, Vector<GraphNode *>> &p_comment_enclosed_nodes, Vector2 p_pos); + HBoxContainer *zoom_hb; friend class GraphEditFilter; @@ -254,12 +273,15 @@ protected: void _notification(int p_what); GDVIRTUAL2RC(Vector<Vector2>, _get_connection_line, Vector2, Vector2) + GDVIRTUAL3R(bool, _is_in_input_hotzone, Object *, int, Vector2) + GDVIRTUAL3R(bool, _is_in_output_hotzone, Object *, int, Vector2) public: Error connect_node(const StringName &p_from, int p_from_port, const StringName &p_to, int p_to_port); bool is_node_connected(const StringName &p_from, int p_from_port, const StringName &p_to, int p_to_port); void disconnect_node(const StringName &p_from, int p_from_port, const StringName &p_to, int p_to_port); void clear_connections(); + void force_connection_drag_end(); void set_connection_activity(const StringName &p_from, int p_from_port, const StringName &p_to, int p_to_port, float p_activity); @@ -267,6 +289,9 @@ public: void remove_valid_connection_type(int p_type, int p_with_type); bool is_valid_connection_type(int p_type, int p_with_type) const; + void set_panning_scheme(PanningScheme p_scheme); + PanningScheme get_panning_scheme() const; + void set_zoom(float p_zoom); void set_zoom_custom(float p_zoom, const Vector2 &p_center); float get_zoom() const; @@ -322,10 +347,14 @@ public: bool is_connection_lines_antialiased() const; HBoxContainer *get_zoom_hbox(); + Ref<ViewPanner> get_panner(); + void set_warped_panning(bool p_warped); void arrange_nodes(); GraphEdit(); }; +VARIANT_ENUM_CAST(GraphEdit::PanningScheme); + #endif // GRAPHEdit_H diff --git a/scene/gui/graph_node.cpp b/scene/gui/graph_node.cpp index 8462fd259e..ef0ac75cb4 100644 --- a/scene/gui/graph_node.cpp +++ b/scene/gui/graph_node.cpp @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ @@ -31,6 +31,7 @@ #include "graph_node.h" #include "core/string/translation.h" + #ifdef TOOLS_ENABLED #include "graph_edit.h" #endif @@ -46,7 +47,7 @@ bool GraphNode::_set(const StringName &p_name, const Variant &p_value) { if (str.begins_with("opentype_features/")) { String name = str.get_slicec('/', 1); int32_t tag = TS->name_to_tag(name); - double value = p_value; + int value = p_value; if (value == -1) { if (opentype_features.has(tag)) { opentype_features.erase(tag); @@ -54,7 +55,7 @@ bool GraphNode::_set(const StringName &p_name, const Variant &p_value) { update(); } } else { - if ((double)opentype_features[tag] != value) { + if (!opentype_features.has(tag) || (int)opentype_features[tag] != value) { opentype_features[tag] = value; _shape(); update(); @@ -153,7 +154,7 @@ bool GraphNode::_get(const StringName &p_name, Variant &r_ret) const { void GraphNode::_get_property_list(List<PropertyInfo> *p_list) const { for (const Variant *ftr = opentype_features.next(nullptr); ftr != nullptr; ftr = opentype_features.next(ftr)) { String name = TS->tag_to_name(*ftr); - p_list->push_back(PropertyInfo(Variant::FLOAT, "opentype_features/" + name)); + p_list->push_back(PropertyInfo(Variant::INT, "opentype_features/" + name)); } p_list->push_back(PropertyInfo(Variant::NIL, "opentype_features/_new", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_EDITOR)); @@ -442,7 +443,7 @@ void GraphNode::_notification(int p_what) { case NOTIFICATION_THEME_CHANGED: { _shape(); - minimum_size_changed(); + update_minimum_size(); update(); } break; } @@ -458,7 +459,7 @@ void GraphNode::_shape() { } else { title_buf->set_direction((TextServer::Direction)text_direction); } - title_buf->add_string(title, font, font_size, opentype_features, (language != "") ? language : TranslationServer::get_singleton()->get_tool_locale()); + title_buf->add_string(title, font, font_size, opentype_features, (!language.is_empty()) ? language : TranslationServer::get_singleton()->get_tool_locale()); } #ifdef TOOLS_ENABLED @@ -666,7 +667,7 @@ void GraphNode::set_title(const String &p_title) { _shape(); update(); - minimum_size_changed(); + update_minimum_size(); } String GraphNode::get_title() const { @@ -894,7 +895,7 @@ void GraphNode::gui_input(const Ref<InputEvent> &p_ev) { if (mb.is_valid()) { ERR_FAIL_COND_MSG(get_parent_control() == nullptr, "GraphNode must be the child of a GraphEdit node."); - if (mb->is_pressed() && mb->get_button_index() == MOUSE_BUTTON_LEFT) { + if (mb->is_pressed() && mb->get_button_index() == MouseButton::LEFT) { Vector2 mpos = mb->get_position(); if (close_rect.size != Size2() && close_rect.has_point(mpos)) { //send focus to parent @@ -917,7 +918,7 @@ void GraphNode::gui_input(const Ref<InputEvent> &p_ev) { emit_signal(SNAME("raise_request")); } - if (!mb->is_pressed() && mb->get_button_index() == MOUSE_BUTTON_LEFT) { + if (!mb->is_pressed() && mb->get_button_index() == MouseButton::LEFT) { resizing = false; } } @@ -959,6 +960,25 @@ bool GraphNode::is_resizable() const { return resizable; } +Vector<int> GraphNode::get_allowed_size_flags_horizontal() const { + Vector<int> flags; + flags.append(SIZE_FILL); + flags.append(SIZE_SHRINK_BEGIN); + flags.append(SIZE_SHRINK_CENTER); + flags.append(SIZE_SHRINK_END); + return flags; +} + +Vector<int> GraphNode::get_allowed_size_flags_vertical() const { + Vector<int> flags; + flags.append(SIZE_FILL); + flags.append(SIZE_EXPAND); + flags.append(SIZE_SHRINK_BEGIN); + flags.append(SIZE_SHRINK_CENTER); + flags.append(SIZE_SHRINK_END); + return flags; +} + void GraphNode::_bind_methods() { ClassDB::bind_method(D_METHOD("set_title", "title"), &GraphNode::set_title); ClassDB::bind_method(D_METHOD("get_title"), &GraphNode::get_title); @@ -1022,7 +1042,7 @@ void GraphNode::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::STRING, "title"), "set_title", "get_title"); 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"), "set_language", "get_language"); + ADD_PROPERTY(PropertyInfo(Variant::STRING, "language", PROPERTY_HINT_LOCALE_ID, ""), "set_language", "get_language"); ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "position_offset"), "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"); diff --git a/scene/gui/graph_node.h b/scene/gui/graph_node.h index 2238cfdb56..7eb5f27cff 100644 --- a/scene/gui/graph_node.h +++ b/scene/gui/graph_node.h @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ @@ -182,6 +182,9 @@ public: virtual Size2 get_minimum_size() const override; + virtual Vector<int> get_allowed_size_flags_horizontal() const override; + virtual Vector<int> get_allowed_size_flags_vertical() const override; + bool is_resizing() const { return resizing; } GraphNode(); diff --git a/scene/gui/grid_container.cpp b/scene/gui/grid_container.cpp index 2beb2624d2..3c1f4bb93b 100644 --- a/scene/gui/grid_container.cpp +++ b/scene/gui/grid_container.cpp @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ @@ -179,11 +179,12 @@ void GridContainer::_notification(int p_what) { col_ofs += s.width + hsep; } } - } break; + case NOTIFICATION_THEME_CHANGED: { - minimum_size_changed(); + update_minimum_size(); } break; + case NOTIFICATION_TRANSLATION_CHANGED: case NOTIFICATION_LAYOUT_DIRECTION_CHANGED: { queue_sort(); @@ -195,7 +196,7 @@ void GridContainer::set_columns(int p_columns) { ERR_FAIL_COND(p_columns < 1); columns = p_columns; queue_sort(); - minimum_size_changed(); + update_minimum_size(); } int GridContainer::get_columns() const { diff --git a/scene/gui/grid_container.h b/scene/gui/grid_container.h index 9b43a5bc7e..9d77f90ab3 100644 --- a/scene/gui/grid_container.h +++ b/scene/gui/grid_container.h @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ diff --git a/scene/gui/item_list.cpp b/scene/gui/item_list.cpp index 4215c9aff4..8c0f696a9f 100644 --- a/scene/gui/item_list.cpp +++ b/scene/gui/item_list.cpp @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ @@ -29,6 +29,7 @@ /*************************************************************************/ #include "item_list.h" + #include "core/config/project_settings.h" #include "core/os/os.h" #include "core/string/translation.h" @@ -42,7 +43,7 @@ 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.opentype_features, (item.language != "") ? item.language : TranslationServer::get_singleton()->get_tool_locale()); + item.text_buf->add_string(item.text, get_theme_font(SNAME("font")), get_theme_font_size(SNAME("font_size")), item.opentype_features, (!item.language.is_empty()) ? item.language : TranslationServer::get_singleton()->get_tool_locale()); if (icon_mode == ICON_MODE_TOP && max_text_lines > 0) { item.text_buf->set_flags(TextServer::BREAK_MANDATORY | TextServer::BREAK_WORD_BOUND | TextServer::BREAK_GRAPHEME_BOUND); } else { @@ -82,6 +83,9 @@ int ItemList::add_icon_item(const Ref<Texture2D> &p_item, bool p_selectable) { } void ItemList::set_item_text(int p_idx, const String &p_text) { + if (p_idx < 0) { + p_idx += get_item_count(); + } ERR_FAIL_INDEX(p_idx, items.size()); items.write[p_idx].text = p_text; @@ -96,6 +100,9 @@ String ItemList::get_item_text(int p_idx) const { } void ItemList::set_item_text_direction(int p_idx, Control::TextDirection p_text_direction) { + if (p_idx < 0) { + p_idx += get_item_count(); + } ERR_FAIL_INDEX(p_idx, items.size()); ERR_FAIL_COND((int)p_text_direction < -1 || (int)p_text_direction > 3); if (items[p_idx].text_direction != p_text_direction) { @@ -118,6 +125,9 @@ void ItemList::clear_item_opentype_features(int p_idx) { } void ItemList::set_item_opentype_feature(int p_idx, const String &p_name, int p_value) { + if (p_idx < 0) { + p_idx += get_item_count(); + } ERR_FAIL_INDEX(p_idx, items.size()); int32_t tag = TS->name_to_tag(p_name); if (!items[p_idx].opentype_features.has(tag) || (int)items[p_idx].opentype_features[tag] != p_value) { @@ -137,6 +147,9 @@ int ItemList::get_item_opentype_feature(int p_idx, const String &p_name) const { } void ItemList::set_item_language(int p_idx, const String &p_language) { + if (p_idx < 0) { + p_idx += get_item_count(); + } ERR_FAIL_INDEX(p_idx, items.size()); if (items[p_idx].language != p_language) { items.write[p_idx].language = p_language; @@ -151,6 +164,9 @@ String ItemList::get_item_language(int p_idx) const { } void ItemList::set_item_tooltip_enabled(int p_idx, const bool p_enabled) { + if (p_idx < 0) { + p_idx += get_item_count(); + } ERR_FAIL_INDEX(p_idx, items.size()); items.write[p_idx].tooltip_enabled = p_enabled; } @@ -161,6 +177,9 @@ bool ItemList::is_item_tooltip_enabled(int p_idx) const { } void ItemList::set_item_tooltip(int p_idx, const String &p_tooltip) { + if (p_idx < 0) { + p_idx += get_item_count(); + } ERR_FAIL_INDEX(p_idx, items.size()); items.write[p_idx].tooltip = p_tooltip; @@ -174,6 +193,9 @@ String ItemList::get_item_tooltip(int p_idx) const { } void ItemList::set_item_icon(int p_idx, const Ref<Texture2D> &p_icon) { + if (p_idx < 0) { + p_idx += get_item_count(); + } ERR_FAIL_INDEX(p_idx, items.size()); items.write[p_idx].icon = p_icon; @@ -188,6 +210,9 @@ Ref<Texture2D> ItemList::get_item_icon(int p_idx) const { } void ItemList::set_item_icon_transposed(int p_idx, const bool p_transposed) { + if (p_idx < 0) { + p_idx += get_item_count(); + } ERR_FAIL_INDEX(p_idx, items.size()); items.write[p_idx].icon_transposed = p_transposed; @@ -202,6 +227,9 @@ bool ItemList::is_item_icon_transposed(int p_idx) const { } void ItemList::set_item_icon_region(int p_idx, const Rect2 &p_region) { + if (p_idx < 0) { + p_idx += get_item_count(); + } ERR_FAIL_INDEX(p_idx, items.size()); items.write[p_idx].icon_region = p_region; @@ -216,6 +244,9 @@ Rect2 ItemList::get_item_icon_region(int p_idx) const { } void ItemList::set_item_icon_modulate(int p_idx, const Color &p_modulate) { + if (p_idx < 0) { + p_idx += get_item_count(); + } ERR_FAIL_INDEX(p_idx, items.size()); items.write[p_idx].icon_modulate = p_modulate; @@ -229,6 +260,9 @@ Color ItemList::get_item_icon_modulate(int p_idx) const { } void ItemList::set_item_custom_bg_color(int p_idx, const Color &p_custom_bg_color) { + if (p_idx < 0) { + p_idx += get_item_count(); + } ERR_FAIL_INDEX(p_idx, items.size()); items.write[p_idx].custom_bg = p_custom_bg_color; @@ -242,6 +276,9 @@ Color ItemList::get_item_custom_bg_color(int p_idx) const { } void ItemList::set_item_custom_fg_color(int p_idx, const Color &p_custom_fg_color) { + if (p_idx < 0) { + p_idx += get_item_count(); + } ERR_FAIL_INDEX(p_idx, items.size()); items.write[p_idx].custom_fg = p_custom_fg_color; @@ -255,6 +292,9 @@ Color ItemList::get_item_custom_fg_color(int p_idx) const { } void ItemList::set_item_tag_icon(int p_idx, const Ref<Texture2D> &p_tag_icon) { + if (p_idx < 0) { + p_idx += get_item_count(); + } ERR_FAIL_INDEX(p_idx, items.size()); items.write[p_idx].tag_icon = p_tag_icon; @@ -269,6 +309,9 @@ Ref<Texture2D> ItemList::get_item_tag_icon(int p_idx) const { } void ItemList::set_item_selectable(int p_idx, bool p_selectable) { + if (p_idx < 0) { + p_idx += get_item_count(); + } ERR_FAIL_INDEX(p_idx, items.size()); items.write[p_idx].selectable = p_selectable; @@ -280,6 +323,9 @@ bool ItemList::is_item_selectable(int p_idx) const { } void ItemList::set_item_disabled(int p_idx, bool p_disabled) { + if (p_idx < 0) { + p_idx += get_item_count(); + } ERR_FAIL_INDEX(p_idx, items.size()); items.write[p_idx].disabled = p_disabled; @@ -292,6 +338,9 @@ bool ItemList::is_item_disabled(int p_idx) const { } void ItemList::set_item_metadata(int p_idx, const Variant &p_metadata) { + if (p_idx < 0) { + p_idx += get_item_count(); + } ERR_FAIL_INDEX(p_idx, items.size()); items.write[p_idx].metadata = p_metadata; @@ -380,7 +429,7 @@ void ItemList::move_item(int p_from_idx, int p_to_idx) { } Item item = items[p_from_idx]; - items.remove(p_from_idx); + items.remove_at(p_from_idx); items.insert(p_to_idx, item); update(); @@ -403,7 +452,7 @@ int ItemList::get_item_count() const { void ItemList::remove_item(int p_idx) { ERR_FAIL_INDEX(p_idx, items.size()); - items.remove(p_idx); + items.remove_at(p_idx); if (current == p_idx) { current = -1; } @@ -546,7 +595,7 @@ void ItemList::gui_input(const Ref<InputEvent> &p_event) { Ref<InputEventMouseButton> mb = p_event; - if (defer_select_single >= 0 && mb.is_valid() && mb->get_button_index() == MOUSE_BUTTON_LEFT && !mb->is_pressed()) { + if (defer_select_single >= 0 && mb.is_valid() && mb->get_button_index() == MouseButton::LEFT && !mb->is_pressed()) { select(defer_select_single, true); emit_signal(SNAME("multi_selected"), defer_select_single, true); @@ -554,7 +603,7 @@ void ItemList::gui_input(const Ref<InputEvent> &p_event) { return; } - if (mb.is_valid() && (mb->get_button_index() == MOUSE_BUTTON_LEFT || (allow_rmb_select && mb->get_button_index() == MOUSE_BUTTON_RIGHT)) && mb->is_pressed()) { + if (mb.is_valid() && (mb->get_button_index() == MouseButton::LEFT || (allow_rmb_select && mb->get_button_index() == MouseButton::RIGHT)) && mb->is_pressed()) { search_string = ""; //any mousepress cancels Vector2 pos = mb->get_position(); Ref<StyleBox> bg = get_theme_stylebox(SNAME("bg")); @@ -600,16 +649,16 @@ void ItemList::gui_input(const Ref<InputEvent> &p_event) { } } - if (mb->get_button_index() == MOUSE_BUTTON_RIGHT) { + if (mb->get_button_index() == MouseButton::RIGHT) { emit_signal(SNAME("item_rmb_selected"), i, get_local_mouse_position()); } } 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() == MOUSE_BUTTON_LEFT) { + 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) { defer_select_single = i; return; } - if (items[i].selected && mb->get_button_index() == MOUSE_BUTTON_RIGHT) { + if (items[i].selected && mb->get_button_index() == MouseButton::RIGHT) { emit_signal(SNAME("item_rmb_selected"), i, get_local_mouse_position()); } else { bool selected = items[i].selected; @@ -624,7 +673,7 @@ void ItemList::gui_input(const Ref<InputEvent> &p_event) { } } - if (mb->get_button_index() == MOUSE_BUTTON_RIGHT) { + if (mb->get_button_index() == MouseButton::RIGHT) { emit_signal(SNAME("item_rmb_selected"), i, get_local_mouse_position()); } else if (/*select_mode==SELECT_SINGLE &&*/ mb->is_double_click()) { emit_signal(SNAME("item_activated"), i); @@ -634,7 +683,7 @@ void ItemList::gui_input(const Ref<InputEvent> &p_event) { return; } - if (mb->get_button_index() == MOUSE_BUTTON_RIGHT) { + if (mb->get_button_index() == MouseButton::RIGHT) { emit_signal(SNAME("rmb_clicked"), mb->get_position()); return; @@ -643,16 +692,16 @@ void ItemList::gui_input(const Ref<InputEvent> &p_event) { // Since closest is null, more likely we clicked on empty space, so send signal to interested controls. Allows, for example, implement items deselecting. emit_signal(SNAME("nothing_selected")); } - if (mb.is_valid() && mb->get_button_index() == MOUSE_BUTTON_WHEEL_UP && mb->is_pressed()) { + if (mb.is_valid() && mb->get_button_index() == MouseButton::WHEEL_UP && mb->is_pressed()) { scroll_bar->set_value(scroll_bar->get_value() - scroll_bar->get_page() * mb->get_factor() / 8); } - if (mb.is_valid() && mb->get_button_index() == MOUSE_BUTTON_WHEEL_DOWN && mb->is_pressed()) { + if (mb.is_valid() && mb->get_button_index() == MouseButton::WHEEL_DOWN && mb->is_pressed()) { scroll_bar->set_value(scroll_bar->get_value() + scroll_bar->get_page() * mb->get_factor() / 8); } if (p_event->is_pressed() && items.size() > 0) { if (p_event->is_action("ui_up")) { - if (search_string != "") { + if (!search_string.is_empty()) { uint64_t now = OS::get_singleton()->get_ticks_msec(); uint64_t diff = now - search_time_msec; @@ -682,7 +731,7 @@ void ItemList::gui_input(const Ref<InputEvent> &p_event) { accept_event(); } } else if (p_event->is_action("ui_down")) { - if (search_string != "") { + if (!search_string.is_empty()) { uint64_t now = OS::get_singleton()->get_ticks_msec(); uint64_t diff = now - search_time_msec; @@ -854,445 +903,451 @@ static Rect2 _adjust_to_max_size(Size2 p_size, Size2 p_max_size) { } void ItemList::_notification(int p_what) { - if (p_what == NOTIFICATION_RESIZED) { - shape_changed = true; - update(); - } + switch (p_what) { + case NOTIFICATION_RESIZED: { + shape_changed = true; + update(); + } break; + + case NOTIFICATION_LAYOUT_DIRECTION_CHANGED: + case NOTIFICATION_TRANSLATION_CHANGED: + case NOTIFICATION_THEME_CHANGED: { + for (int i = 0; i < items.size(); i++) { + _shape(i); + } + shape_changed = true; + update(); + } break; - if ((p_what == NOTIFICATION_LAYOUT_DIRECTION_CHANGED) || (p_what == NOTIFICATION_TRANSLATION_CHANGED) || (p_what == NOTIFICATION_THEME_CHANGED)) { - for (int i = 0; i < items.size(); i++) { - _shape(i); - } - shape_changed = true; - update(); - } + case NOTIFICATION_DRAW: { + Ref<StyleBox> bg = get_theme_stylebox(SNAME("bg")); - if (p_what == 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)); - 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)); + Size2 size = get_size(); - Size2 size = get_size(); + int width = size.width - bg->get_minimum_size().width; + if (scroll_bar->is_visible()) { + width -= mw; + } - 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(bg, Rect2(Point2(), size)); + int hseparation = get_theme_constant(SNAME("hseparation")); + int vseparation = get_theme_constant(SNAME("vseparation")); + 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")); - int hseparation = get_theme_constant(SNAME("hseparation")); - int vseparation = get_theme_constant(SNAME("vseparation")); - 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 = 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")); + bool rtl = is_layout_rtl(); - 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")); - 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")); - 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)); + RenderingServer::get_singleton()->canvas_item_add_clip_ignore(get_canvas_item(), false); + } - 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)); - RenderingServer::get_singleton()->canvas_item_add_clip_ignore(get_canvas_item(), false); - } + if (shape_changed) { + float max_column_width = 0.0; - if (shape_changed) { - float max_column_width = 0.0; + //1- compute item minimum sizes + for (int i = 0; i < items.size(); i++) { + Size2 minsize; + if (items[i].icon.is_valid()) { + if (fixed_icon_size.x > 0 && fixed_icon_size.y > 0) { + minsize = fixed_icon_size * icon_scale; + } else { + minsize = items[i].get_icon_size() * icon_scale; + } - //1- compute item minimum sizes - for (int i = 0; i < items.size(); i++) { - Size2 minsize; - if (items[i].icon.is_valid()) { - if (fixed_icon_size.x > 0 && fixed_icon_size.y > 0) { - minsize = fixed_icon_size * icon_scale; - } else { - minsize = items[i].get_icon_size() * icon_scale; + if (!items[i].text.is_empty()) { + if (icon_mode == ICON_MODE_TOP) { + minsize.y += icon_margin; + } else { + minsize.x += icon_margin; + } + } } - if (items[i].text != "") { - if (icon_mode == ICON_MODE_TOP) { - minsize.y += icon_margin; - } else { - minsize.x += icon_margin; + if (!items[i].text.is_empty()) { + int max_width = -1; + if (fixed_column_width) { + max_width = fixed_column_width; + } else if (same_column_width) { + max_width = items[i].rect_cache.size.x; } - } - } + items.write[i].text_buf->set_width(max_width); + Size2 s = items[i].text_buf->get_size(); - if (items[i].text != "") { - int max_width = -1; - if (fixed_column_width) { - max_width = fixed_column_width; - } else if (same_column_width) { - max_width = items[i].rect_cache.size.x; - } - items.write[i].text_buf->set_width(max_width); - Size2 s = items[i].text_buf->get_size(); + 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; + } else { + minsize.y += s.height; + } - 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; } else { - minsize.y += s.height; + minsize.y = MAX(minsize.y, s.height); + minsize.x += s.width; } + } - } else { - minsize.y = MAX(minsize.y, s.height); - minsize.x += s.width; + if (fixed_column_width > 0) { + minsize.x = fixed_column_width; } + max_column_width = MAX(max_column_width, minsize.x); + + // elements need to adapt to the selected size + minsize.y += vseparation; + minsize.x += hseparation; + items.write[i].rect_cache.size = minsize; + items.write[i].min_rect_cache.size = minsize; } - if (fixed_column_width > 0) { - minsize.x = fixed_column_width; + int fit_size = size.x - bg->get_minimum_size().width - mw; + + //2-attempt best fit + current_columns = 0x7FFFFFFF; + if (max_columns > 0) { + current_columns = max_columns; } - max_column_width = MAX(max_column_width, minsize.x); - // elements need to adapt to the selected size - minsize.y += vseparation; - minsize.x += hseparation; - items.write[i].rect_cache.size = minsize; - items.write[i].min_rect_cache.size = minsize; - } + while (true) { + //repeat until all fits + bool all_fit = true; + Vector2 ofs; + int col = 0; + int max_h = 0; + separators.clear(); + for (int i = 0; i < items.size(); i++) { + if (current_columns > 1 && items[i].rect_cache.size.width + ofs.x > fit_size) { + //went past + current_columns = MAX(col, 1); + all_fit = false; + break; + } - int fit_size = size.x - bg->get_minimum_size().width - mw; + if (same_column_width) { + items.write[i].rect_cache.size.x = max_column_width; + } + 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; + col++; + if (col == current_columns) { + if (i < items.size() - 1) { + separators.push_back(ofs.y + max_h + vseparation / 2); + } - //2-attempt best fit - current_columns = 0x7FFFFFFF; - if (max_columns > 0) { - current_columns = max_columns; - } + for (int j = i; j >= 0 && col > 0; j--, col--) { + items.write[j].rect_cache.size.y = max_h; + } - while (true) { - //repeat until all fits - bool all_fit = true; - Vector2 ofs; - int col = 0; - int max_h = 0; - separators.clear(); - for (int i = 0; i < items.size(); i++) { - if (current_columns > 1 && items[i].rect_cache.size.width + ofs.x > fit_size) { - //went past - current_columns = MAX(col, 1); - all_fit = false; - break; + ofs.x = 0; + ofs.y += max_h + vseparation; + col = 0; + max_h = 0; + } } - if (same_column_width) { - items.write[i].rect_cache.size.x = max_column_width; + for (int j = items.size() - 1; j >= 0 && col > 0; j--, col--) { + items.write[j].rect_cache.size.y = max_h; } - 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; - col++; - if (col == current_columns) { - if (i < items.size() - 1) { - separators.push_back(ofs.y + max_h + vseparation / 2); - } - for (int j = i; j >= 0 && col > 0; j--, col--) { - items.write[j].rect_cache.size.y = max_h; + if (all_fit) { + float page = MAX(0, size.height - bg->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; } + scroll_bar->set_max(max); + scroll_bar->set_page(page); + if (max <= page) { + scroll_bar->set_value(0); + scroll_bar->hide(); + } else { + scroll_bar->show(); - ofs.x = 0; - ofs.y += max_h + vseparation; - col = 0; - max_h = 0; + if (do_autoscroll_to_bottom) { + scroll_bar->set_value(max); + } + } + break; } } - for (int j = items.size() - 1; j >= 0 && col > 0; j--, col--) { - items.write[j].rect_cache.size.y = max_h; - } + update_minimum_size(); + shape_changed = false; + } - if (all_fit) { - float page = MAX(0, size.height - bg->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; - } - scroll_bar->set_max(max); - scroll_bar->set_page(page); - if (max <= page) { - scroll_bar->set_value(0); - scroll_bar->hide(); - } else { - scroll_bar->show(); + //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; + int from = scroll_bar->get_value(); + int to = from + scroll_bar->get_page(); - if (do_autoscroll_to_bottom) { - scroll_bar->set_value(max); - } - } - break; + if (r.position.y < from) { + scroll_bar->set_value(r.position.y); + } else if (r.position.y + r.size.y > to) { + scroll_bar->set_value(r.position.y + r.size.y - (to - from)); } } - minimum_size_changed(); - shape_changed = false; - } - - //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; - int from = scroll_bar->get_value(); - int to = from + scroll_bar->get_page(); + ensure_selected_visible = false; - if (r.position.y < from) { - scroll_bar->set_value(r.position.y); - } else if (r.position.y + r.size.y > to) { - scroll_bar->set_value(r.position.y + r.size.y - (to - from)); - } - } + Vector2 base_ofs = bg->get_offset(); + base_ofs.y -= int(scroll_bar->get_value()); - ensure_selected_visible = false; + const Rect2 clip(-base_ofs, size); // visible frame, don't need to draw outside of there - Vector2 base_ofs = bg->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 - - int first_item_visible; - { - // do a binary search to find the first item whose rect reaches below clip.position.y - int lo = 0; - int hi = items.size(); - while (lo < hi) { - const int mid = (lo + hi) / 2; - const Rect2 &rcache = items[mid].rect_cache; - if (rcache.position.y + rcache.size.y < clip.position.y) { - lo = mid + 1; - } else { - hi = mid; + int first_item_visible; + { + // do a binary search to find the first item whose rect reaches below clip.position.y + int lo = 0; + int hi = items.size(); + while (lo < hi) { + const int mid = (lo + hi) / 2; + const Rect2 &rcache = items[mid].rect_cache; + if (rcache.position.y + rcache.size.y < clip.position.y) { + lo = mid + 1; + } else { + hi = mid; + } } - } - // we might have ended up with column 2, or 3, ..., so let's find the first column - while (lo > 0 && items[lo - 1].rect_cache.position.y == items[lo].rect_cache.position.y) { - lo -= 1; - } - first_item_visible = lo; - } - - for (int i = first_item_visible; i < items.size(); i++) { - Rect2 rcache = items[i].rect_cache; - - if (rcache.position.y > clip.position.y + clip.size.y) { - break; // done + // we might have ended up with column 2, or 3, ..., so let's find the first column + while (lo > 0 && items[lo - 1].rect_cache.position.y == items[lo].rect_cache.position.y) { + lo -= 1; + } + first_item_visible = lo; } - if (!clip.intersects(rcache)) { - continue; - } + for (int i = first_item_visible; i < items.size(); i++) { + Rect2 rcache = items[i].rect_cache; - if (current_columns == 1) { - rcache.size.width = width - rcache.position.x; - } + if (rcache.position.y > clip.position.y + clip.size.y) { + break; // done + } - 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; - - if (rtl) { - r.position.x = size.width - r.position.x - r.size.x; + if (!clip.intersects(rcache)) { + continue; } - draw_style_box(sbsel, r); - } - if (items[i].custom_bg.a > 0.001) { - Rect2 r = rcache; - 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; - - if (rtl) { - r.position.x = size.width - r.position.x - r.size.x; + if (current_columns == 1) { + rcache.size.width = width - rcache.position.x; } - draw_rect(r, items[i].custom_bg); - } + 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; - Vector2 text_ofs; - if (items[i].icon.is_valid()) { - Size2 icon_size; - //= _adjust_to_max_size(items[i].get_icon_size(),fixed_icon_size) * icon_scale; + if (rtl) { + r.position.x = size.width - r.position.x - r.size.x; + } - if (fixed_icon_size.x > 0 && fixed_icon_size.y > 0) { - icon_size = fixed_icon_size * icon_scale; - } else { - icon_size = items[i].get_icon_size() * icon_scale; + draw_style_box(sbsel, r); } + if (items[i].custom_bg.a > 0.001) { + Rect2 r = rcache; + r.position += base_ofs; - Vector2 icon_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; - Point2 pos = items[i].rect_cache.position + icon_ofs + base_ofs; + if (rtl) { + r.position.x = size.width - r.position.x - r.size.x; + } - 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; - } else { - pos.y += Math::floor((items[i].rect_cache.size.height - icon_size.height) / 2); - text_ofs.x = icon_size.width + icon_margin; + draw_rect(r, items[i].custom_bg); } - Rect2 draw_rect = Rect2(pos, icon_size); + Vector2 text_ofs; + if (items[i].icon.is_valid()) { + Size2 icon_size; + //= _adjust_to_max_size(items[i].get_icon_size(),fixed_icon_size) * icon_scale; - if (fixed_icon_size.x > 0 && fixed_icon_size.y > 0) { - Rect2 adj = _adjust_to_max_size(items[i].get_icon_size() * icon_scale, icon_size); - draw_rect.position += adj.position; - draw_rect.size = adj.size; - } + if (fixed_icon_size.x > 0 && fixed_icon_size.y > 0) { + icon_size = fixed_icon_size * icon_scale; + } else { + icon_size = items[i].get_icon_size() * icon_scale; + } - Color modulate = items[i].icon_modulate; - if (items[i].disabled) { - modulate.a *= 0.5; - } + Vector2 icon_ofs; - // If the icon is transposed, we have to switch the size so that it is drawn correctly - if (items[i].icon_transposed) { - Size2 size_tmp = draw_rect.size; - draw_rect.size.x = size_tmp.y; - draw_rect.size.y = size_tmp.x; - } + Point2 pos = items[i].rect_cache.position + icon_ofs + base_ofs; - Rect2 region = (items[i].icon_region.size.x == 0 || items[i].icon_region.size.y == 0) ? Rect2(Vector2(), items[i].icon->get_size()) : Rect2(items[i].icon_region); + 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; + } else { + pos.y += Math::floor((items[i].rect_cache.size.height - icon_size.height) / 2); + text_ofs.x = icon_size.width + icon_margin; + } - if (rtl) { - draw_rect.position.x = size.width - draw_rect.position.x - draw_rect.size.x; - } - draw_texture_rect_region(items[i].icon, draw_rect, region, modulate, items[i].icon_transposed); - } + Rect2 draw_rect = Rect2(pos, icon_size); - if (items[i].tag_icon.is_valid()) { - Point2 draw_pos = items[i].rect_cache.position; - if (rtl) { - draw_pos.x = size.width - draw_pos.x - items[i].tag_icon->get_width(); - } - draw_texture(items[i].tag_icon, draw_pos + base_ofs); - } + if (fixed_icon_size.x > 0 && fixed_icon_size.y > 0) { + Rect2 adj = _adjust_to_max_size(items[i].get_icon_size() * icon_scale, icon_size); + draw_rect.position += adj.position; + draw_rect.size = adj.size; + } + + Color modulate = items[i].icon_modulate; + if (items[i].disabled) { + modulate.a *= 0.5; + } - if (items[i].text != "") { - int max_len = -1; + // If the icon is transposed, we have to switch the size so that it is drawn correctly + if (items[i].icon_transposed) { + Size2 size_tmp = draw_rect.size; + draw_rect.size.x = size_tmp.y; + draw_rect.size.y = size_tmp.x; + } - Vector2 size2 = items[i].text_buf->get_size(); - if (fixed_column_width) { - max_len = fixed_column_width; - } else if (same_column_width) { - max_len = items[i].rect_cache.size.x; - } else { - max_len = size2.x; - } + Rect2 region = (items[i].icon_region.size.x == 0 || items[i].icon_region.size.y == 0) ? Rect2(Vector2(), items[i].icon->get_size()) : Rect2(items[i].icon_region); - Color modulate = items[i].selected ? font_selected_color : (items[i].custom_fg != Color() ? items[i].custom_fg : font_color); - if (items[i].disabled) { - modulate.a *= 0.5; + if (rtl) { + draw_rect.position.x = size.width - draw_rect.position.x - draw_rect.size.x; + } + draw_texture_rect_region(items[i].icon, draw_rect, region, modulate, items[i].icon_transposed); } - if (icon_mode == ICON_MODE_TOP && max_text_lines > 0) { - text_ofs += base_ofs; - text_ofs += items[i].rect_cache.position; - + if (items[i].tag_icon.is_valid()) { + Point2 draw_pos = items[i].rect_cache.position; if (rtl) { - text_ofs.x = size.width - text_ofs.x - max_len; + draw_pos.x = size.width - draw_pos.x - items[i].tag_icon->get_width(); } + draw_texture(items[i].tag_icon, draw_pos + base_ofs); + } - items.write[i].text_buf->set_align(HALIGN_CENTER); + if (!items[i].text.is_empty()) { + int max_len = -1; - 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); + Vector2 size2 = items[i].text_buf->get_size(); + if (fixed_column_width) { + max_len = fixed_column_width; + } else if (same_column_width) { + max_len = items[i].rect_cache.size.x; + } else { + max_len = size2.x; } - items[i].text_buf->draw(get_canvas_item(), text_ofs, modulate); - } else { - if (fixed_column_width > 0) { - size2.x = MIN(size2.x, fixed_column_width); + Color modulate = items[i].selected ? font_selected_color : (items[i].custom_fg != Color() ? items[i].custom_fg : font_color); + if (items[i].disabled) { + modulate.a *= 0.5; } - if (icon_mode == ICON_MODE_TOP) { - text_ofs.x += (items[i].rect_cache.size.width - size2.x) / 2; - } else { - text_ofs.y += (items[i].rect_cache.size.height - size2.y) / 2; - } + if (icon_mode == ICON_MODE_TOP && max_text_lines > 0) { + text_ofs += base_ofs; + text_ofs += items[i].rect_cache.position; - text_ofs += base_ofs; - text_ofs += items[i].rect_cache.position; + if (rtl) { + text_ofs.x = size.width - text_ofs.x - max_len; + } - if (rtl) { - text_ofs.x = size.width - text_ofs.x - max_len; - } + items.write[i].text_buf->set_alignment(HORIZONTAL_ALIGNMENT_CENTER); - items.write[i].text_buf->set_width(max_len); + 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 (rtl) { - items.write[i].text_buf->set_align(HALIGN_RIGHT); + items[i].text_buf->draw(get_canvas_item(), text_ofs, modulate); } else { - items.write[i].text_buf->set_align(HALIGN_LEFT); + if (fixed_column_width > 0) { + size2.x = MIN(size2.x, fixed_column_width); + } + + if (icon_mode == ICON_MODE_TOP) { + text_ofs.x += (items[i].rect_cache.size.width - size2.x) / 2; + } else { + text_ofs.y += (items[i].rect_cache.size.height - size2.y) / 2; + } + + text_ofs += base_ofs; + text_ofs += items[i].rect_cache.position; + + if (rtl) { + text_ofs.x = size.width - text_ofs.x - max_len; + } + + items.write[i].text_buf->set_width(width - text_ofs.x); + + if (rtl) { + items.write[i].text_buf->set_alignment(HORIZONTAL_ALIGNMENT_RIGHT); + } else { + 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 (width - text_ofs.x > 0) { + items[i].text_buf->draw(get_canvas_item(), text_ofs, modulate); + } } + } + + 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; - 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 (rtl) { + r.position.x = size.width - r.position.x - r.size.x; } - items[i].text_buf->draw(get_canvas_item(), text_ofs, modulate); + draw_style_box(cursor, r); } } - 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; - - if (rtl) { - r.position.x = size.width - r.position.x - r.size.x; + int first_visible_separator = 0; + { + // do a binary search to find the first separator that is below clip_position.y + int lo = 0; + int hi = separators.size(); + while (lo < hi) { + const int mid = (lo + hi) / 2; + if (separators[mid] < clip.position.y) { + lo = mid + 1; + } else { + hi = mid; + } } - - draw_style_box(cursor, r); + first_visible_separator = lo; } - } - int first_visible_separator = 0; - { - // do a binary search to find the first separator that is below clip_position.y - int lo = 0; - int hi = separators.size(); - while (lo < hi) { - const int mid = (lo + hi) / 2; - if (separators[mid] < clip.position.y) { - lo = mid + 1; - } else { - hi = mid; + for (int i = first_visible_separator; i < separators.size(); i++) { + if (separators[i] > clip.position.y + clip.size.y) { + break; // done } - } - first_visible_separator = lo; - } - for (int i = first_visible_separator; i < separators.size(); i++) { - if (separators[i] > clip.position.y + clip.size.y) { - break; // done + const int y = base_ofs.y + separators[i]; + draw_line(Vector2(bg->get_margin(SIDE_LEFT), y), Vector2(width, y), guide_color); } - - const int y = base_ofs.y + separators[i]; - draw_line(Vector2(bg->get_margin(SIDE_LEFT), y), Vector2(width, y), guide_color); - } + } break; } } @@ -1359,10 +1414,10 @@ String ItemList::get_tooltip(const Point2 &p_pos) const { if (!items[closest].tooltip_enabled) { return ""; } - if (items[closest].tooltip != "") { + if (!items[closest].tooltip.is_empty()) { return items[closest].tooltip; } - if (items[closest].text != "") { + if (!items[closest].text.is_empty()) { return items[closest].text; } } @@ -1491,6 +1546,9 @@ bool ItemList::_set(const StringName &p_name, const Variant &p_value) { } else if (components[1] == "disabled") { set_item_disabled(item_index, p_value); return true; + } else if (components[1] == "selectable") { + set_item_selectable(item_index, p_value); + return true; } } #ifndef DISABLE_DEPRECATED @@ -1527,6 +1585,9 @@ bool ItemList::_get(const StringName &p_name, Variant &r_ret) const { } else if (components[1] == "disabled") { r_ret = is_item_disabled(item_index); return true; + } else if (components[1] == "selectable") { + r_ret = is_item_selectable(item_index); + return true; } } return false; @@ -1540,6 +1601,10 @@ void ItemList::_get_property_list(List<PropertyInfo> *p_list) const { pi.usage &= ~(get_item_icon(i).is_null() ? PROPERTY_USAGE_STORAGE : 0); p_list->push_back(pi); + pi = PropertyInfo(Variant::BOOL, vformat("item_%d/selectable", i)); + pi.usage &= ~(is_item_selectable(i) ? PROPERTY_USAGE_STORAGE : 0); + p_list->push_back(pi); + pi = PropertyInfo(Variant::BOOL, vformat("item_%d/disabled", i)); pi.usage &= ~(!is_item_disabled(i) ? PROPERTY_USAGE_STORAGE : 0); p_list->push_back(pi); @@ -1605,7 +1670,7 @@ void ItemList::_bind_methods() { ClassDB::bind_method(D_METHOD("move_item", "from_idx", "to_idx"), &ItemList::move_item); - ClassDB::bind_method(D_METHOD("set_item_count"), &ItemList::set_item_count); + ClassDB::bind_method(D_METHOD("set_item_count", "count"), &ItemList::set_item_count); ClassDB::bind_method(D_METHOD("get_item_count"), &ItemList::get_item_count); ClassDB::bind_method(D_METHOD("remove_item", "idx"), &ItemList::remove_item); @@ -1651,7 +1716,7 @@ void ItemList::_bind_methods() { ClassDB::bind_method(D_METHOD("ensure_current_is_visible"), &ItemList::ensure_current_is_visible); - ClassDB::bind_method(D_METHOD("get_v_scroll"), &ItemList::get_v_scroll); + ClassDB::bind_method(D_METHOD("get_v_scroll_bar"), &ItemList::get_v_scroll_bar); ClassDB::bind_method(D_METHOD("set_text_overrun_behavior", "overrun_behavior"), &ItemList::set_text_overrun_behavior); ClassDB::bind_method(D_METHOD("get_text_overrun_behavior"), &ItemList::get_text_overrun_behavior); @@ -1662,7 +1727,7 @@ void ItemList::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::INT, "max_text_lines", PROPERTY_HINT_RANGE, "1,10,1,or_greater"), "set_max_text_lines", "get_max_text_lines"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "auto_height"), "set_auto_height", "has_auto_height"); 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_ARRAY_COUNT("Items", "items_count", "set_item_count", "get_item_count", "item_"); + ADD_ARRAY_COUNT("Items", "item_count", "set_item_count", "get_item_count", "item_"); ADD_GROUP("Columns", ""); ADD_PROPERTY(PropertyInfo(Variant::INT, "max_columns", PROPERTY_HINT_RANGE, "0,10,1,or_greater"), "set_max_columns", "get_max_columns"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "same_column_width"), "set_same_column_width", "is_same_column_width"); diff --git a/scene/gui/item_list.h b/scene/gui/item_list.h index e780179e7b..96735678c1 100644 --- a/scene/gui/item_list.h +++ b/scene/gui/item_list.h @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ @@ -62,7 +62,7 @@ private: String language; TextDirection text_direction = TEXT_DIRECTION_AUTO; - bool selectable = false; + bool selectable = true; bool selected = false; bool disabled = false; bool tooltip_enabled = true; @@ -99,7 +99,7 @@ private: SelectMode select_mode = SELECT_SINGLE; IconMode icon_mode = ICON_MODE_LEFT; VScrollBar *scroll_bar; - TextParagraph::OverrunBehavior text_overrun_behavior = TextParagraph::OVERRUN_NO_TRIMMING; + TextParagraph::OverrunBehavior text_overrun_behavior = TextParagraph::OVERRUN_TRIM_ELLIPSIS; uint64_t search_time_msec = 0; String search_string; @@ -255,7 +255,7 @@ public: void set_autoscroll_to_bottom(const bool p_enable); - VScrollBar *get_v_scroll() { return scroll_bar; } + VScrollBar *get_v_scroll_bar() { return scroll_bar; } ItemList(); ~ItemList(); diff --git a/scene/gui/label.cpp b/scene/gui/label.cpp index b8cb618171..419901d5ea 100644 --- a/scene/gui/label.cpp +++ b/scene/gui/label.cpp @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ @@ -44,7 +44,7 @@ void Label::set_autowrap_mode(Label::AutowrapMode p_mode) { update(); if (clip || overrun_behavior != OVERRUN_NO_TRIMMING) { - minimum_size_changed(); + update_minimum_size(); } } @@ -82,8 +82,11 @@ void Label::_shape() { Ref<StyleBox> style = get_theme_stylebox(SNAME("normal"), SNAME("Label")); int width = (get_size().width - style->get_minimum_size().width); - if (dirty) { - TS->shaped_text_clear(text_rid); + if (dirty || font_dirty) { + String lang = (!language.is_empty()) ? language : TranslationServer::get_singleton()->get_tool_locale(); + if (dirty) { + TS->shaped_text_clear(text_rid); + } if (text_direction == Control::TEXT_DIRECTION_INHERITED) { TS->shaped_text_set_direction(text_rid, is_layout_rtl() ? TextServer::DIRECTION_RTL : TextServer::DIRECTION_LTR); } else { @@ -92,13 +95,21 @@ void Label::_shape() { const Ref<Font> &font = get_theme_font(SNAME("font")); int font_size = get_theme_font_size(SNAME("font_size")); ERR_FAIL_COND(font.is_null()); - String text = (uppercase) ? xl_text.to_upper() : xl_text; - if (visible_chars >= 0) { + String text = (uppercase) ? TS->string_to_upper(xl_text, lang) : xl_text; + if (visible_chars >= 0 && visible_chars_behavior == VC_CHARS_BEFORE_SHAPING) { text = text.substr(0, visible_chars); } - TS->shaped_text_add_string(text_rid, text, font->get_rids(), font_size, opentype_features, (language != "") ? language : TranslationServer::get_singleton()->get_tool_locale()); + if (dirty) { + TS->shaped_text_add_string(text_rid, text, font->get_rids(), font_size, opentype_features, lang); + } else { + int spans = TS->shaped_get_span_count(text_rid); + for (int i = 0; i < spans; i++) { + TS->shaped_set_span_update_font(text_rid, i, font->get_rids(), font_size, opentype_features); + } + } TS->shaped_text_set_bidi_override(text_rid, structured_text_parser(st_parser, st_args, text)); dirty = false; + font_dirty = false; lines_dirty = true; } @@ -175,7 +186,7 @@ void Label::_shape() { if (lines_hidden) { overrun_flags |= TextServer::OVERRUN_ENFORCE_ELLIPSIS; } - if (align == ALIGN_FILL) { + if (horizontal_alignment == HORIZONTAL_ALIGNMENT_FILL) { for (int i = 0; i < lines_rid.size(); i++) { if (i < visible_lines - 1 || lines_rid.size() == 1) { TS->shaped_text_fit_to_width(lines_rid[i], width); @@ -183,15 +194,13 @@ void Label::_shape() { TS->shaped_text_overrun_trim_to_width(lines_rid[visible_lines - 1], width, overrun_flags); } } - } else if (lines_hidden) { TS->shaped_text_overrun_trim_to_width(lines_rid[visible_lines - 1], width, overrun_flags); } - } else { // Autowrap disabled. for (int i = 0; i < lines_rid.size(); i++) { - if (align == ALIGN_FILL) { + if (horizontal_alignment == HORIZONTAL_ALIGNMENT_FILL) { TS->shaped_text_fit_to_width(lines_rid[i], width); overrun_flags |= TextServer::OVERRUN_JUSTIFICATION_AWARE; TS->shaped_text_overrun_trim_to_width(lines_rid[i], width, overrun_flags); @@ -207,7 +216,7 @@ void Label::_shape() { _update_visible(); if (autowrap_mode == AUTOWRAP_OFF || !clip || overrun_behavior == OVERRUN_NO_TRIMMING) { - minimum_size_changed(); + update_minimum_size(); } } @@ -243,11 +252,9 @@ inline void draw_glyph_outline(const Glyph &p_gl, const RID &p_canvas, const Col if (p_gl.font_rid != RID()) { if (p_font_shadow_color.a > 0) { TS->font_draw_glyph(p_gl.font_rid, p_canvas, p_gl.font_size, p_ofs + Vector2(p_gl.x_off, p_gl.y_off) + shadow_ofs, p_gl.index, p_font_shadow_color); - if (p_shadow_outline_size > 0) { - TS->font_draw_glyph_outline(p_gl.font_rid, p_canvas, p_gl.font_size, p_shadow_outline_size, p_ofs + Vector2(p_gl.x_off, p_gl.y_off) + Vector2(-shadow_ofs.x, shadow_ofs.y), p_gl.index, p_font_shadow_color); - TS->font_draw_glyph_outline(p_gl.font_rid, p_canvas, p_gl.font_size, p_shadow_outline_size, p_ofs + Vector2(p_gl.x_off, p_gl.y_off) + Vector2(shadow_ofs.x, -shadow_ofs.y), p_gl.index, p_font_shadow_color); - TS->font_draw_glyph_outline(p_gl.font_rid, p_canvas, p_gl.font_size, p_shadow_outline_size, p_ofs + Vector2(p_gl.x_off, p_gl.y_off) + Vector2(-shadow_ofs.x, -shadow_ofs.y), p_gl.index, p_font_shadow_color); - } + } + if (p_font_shadow_color.a > 0 && p_shadow_outline_size > 0) { + TS->font_draw_glyph_outline(p_gl.font_rid, p_canvas, p_gl.font_size, p_shadow_outline_size, p_ofs + Vector2(p_gl.x_off, p_gl.y_off) + shadow_ofs, p_gl.index, p_font_shadow_color); } if (p_font_outline_color.a != 0.0 && p_outline_size > 0) { TS->font_draw_glyph_outline(p_gl.font_rid, p_canvas, p_gl.font_size, p_outline_size, p_ofs + Vector2(p_gl.x_off, p_gl.y_off), p_gl.index, p_font_outline_color); @@ -256,253 +263,288 @@ inline void draw_glyph_outline(const Glyph &p_gl, const RID &p_canvas, const Col } void Label::_notification(int p_what) { - if (p_what == NOTIFICATION_TRANSLATION_CHANGED) { - String new_text = atr(text); - if (new_text == xl_text) { - return; // Nothing new. - } - xl_text = new_text; - if (percent_visible < 1) { - visible_chars = get_total_character_count() * percent_visible; - } - dirty = true; + switch (p_what) { + case NOTIFICATION_TRANSLATION_CHANGED: { + String new_text = atr(text); + if (new_text == xl_text) { + return; // Nothing new. + } + xl_text = new_text; + if (percent_visible < 1) { + visible_chars = get_total_character_count() * percent_visible; + } + dirty = true; - update(); - } + update(); + } break; - if (p_what == NOTIFICATION_LAYOUT_DIRECTION_CHANGED) { - update(); - } + case NOTIFICATION_LAYOUT_DIRECTION_CHANGED: { + update(); + } break; - if (p_what == NOTIFICATION_DRAW) { - if (clip) { - RenderingServer::get_singleton()->canvas_item_set_clip(get_canvas_item(), true); - } + case NOTIFICATION_DRAW: { + if (clip) { + RenderingServer::get_singleton()->canvas_item_set_clip(get_canvas_item(), true); + } - if (dirty || lines_dirty) { - _shape(); - } + if (dirty || font_dirty || lines_dirty) { + _shape(); + } - RID ci = get_canvas_item(); - - Size2 string_size; - Size2 size = get_size(); - Ref<StyleBox> style = get_theme_stylebox(SNAME("normal")); - Ref<Font> font = get_theme_font(SNAME("font")); - Color font_color = get_theme_color(SNAME("font_color")); - Color font_shadow_color = get_theme_color(SNAME("font_shadow_color")); - Point2 shadow_ofs(get_theme_constant(SNAME("shadow_offset_x")), get_theme_constant(SNAME("shadow_offset_y"))); - int line_spacing = get_theme_constant(SNAME("line_spacing")); - Color font_outline_color = get_theme_color(SNAME("font_outline_color")); - int outline_size = get_theme_constant(SNAME("outline_size")); - int shadow_outline_size = get_theme_constant(SNAME("shadow_outline_size")); - bool rtl = TS->shaped_text_get_direction(text_rid); - bool rtl_layout = is_layout_rtl(); - - style->draw(ci, Rect2(Point2(0, 0), get_size())); - - float total_h = 0.0; - int lines_visible = 0; - - // Get number of lines to fit to the height. - for (int64_t i = lines_skipped; i < lines_rid.size(); i++) { - total_h += TS->shaped_text_get_size(lines_rid[i]).y + font->get_spacing(TextServer::SPACING_TOP) + font->get_spacing(TextServer::SPACING_BOTTOM) + line_spacing; - if (total_h > (get_size().height - style->get_minimum_size().height + line_spacing)) { - break; + RID ci = get_canvas_item(); + + Size2 string_size; + Size2 size = get_size(); + Ref<StyleBox> style = get_theme_stylebox(SNAME("normal")); + Ref<Font> font = get_theme_font(SNAME("font")); + Color font_color = get_theme_color(SNAME("font_color")); + Color font_shadow_color = get_theme_color(SNAME("font_shadow_color")); + Point2 shadow_ofs(get_theme_constant(SNAME("shadow_offset_x")), get_theme_constant(SNAME("shadow_offset_y"))); + int line_spacing = get_theme_constant(SNAME("line_spacing")); + Color font_outline_color = get_theme_color(SNAME("font_outline_color")); + int outline_size = get_theme_constant(SNAME("outline_size")); + int shadow_outline_size = get_theme_constant(SNAME("shadow_outline_size")); + bool rtl = (TS->shaped_text_get_inferred_direction(text_rid) == TextServer::DIRECTION_RTL); + bool rtl_layout = is_layout_rtl(); + + style->draw(ci, Rect2(Point2(0, 0), get_size())); + + float total_h = 0.0; + int lines_visible = 0; + + // Get number of lines to fit to the height. + for (int64_t i = lines_skipped; i < lines_rid.size(); i++) { + total_h += TS->shaped_text_get_size(lines_rid[i]).y + font->get_spacing(TextServer::SPACING_TOP) + font->get_spacing(TextServer::SPACING_BOTTOM) + line_spacing; + if (total_h > (get_size().height - style->get_minimum_size().height + line_spacing)) { + break; + } + lines_visible++; } - lines_visible++; - } - if (max_lines_visible >= 0 && lines_visible > max_lines_visible) { - lines_visible = max_lines_visible; - } + if (max_lines_visible >= 0 && lines_visible > max_lines_visible) { + lines_visible = max_lines_visible; + } - int last_line = MIN(lines_rid.size(), lines_visible + lines_skipped); + int last_line = MIN(lines_rid.size(), lines_visible + lines_skipped); + bool trim_chars = (visible_chars >= 0) && (visible_chars_behavior == VC_CHARS_AFTER_SHAPING); + bool trim_glyphs_ltr = (visible_chars >= 0) && ((visible_chars_behavior == VC_GLYPHS_LTR) || ((visible_chars_behavior == VC_GLYPHS_AUTO) && !rtl_layout)); + bool trim_glyphs_rtl = (visible_chars >= 0) && ((visible_chars_behavior == VC_GLYPHS_RTL) || ((visible_chars_behavior == VC_GLYPHS_AUTO) && rtl_layout)); + + // Get real total height. + int total_glyphs = 0; + total_h = 0; + for (int64_t i = lines_skipped; i < last_line; i++) { + total_h += TS->shaped_text_get_size(lines_rid[i]).y + font->get_spacing(TextServer::SPACING_TOP) + font->get_spacing(TextServer::SPACING_BOTTOM) + 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 processed_glyphs = 0; + total_h += style->get_margin(SIDE_TOP) + style->get_margin(SIDE_BOTTOM); + + int vbegin = 0, vsep = 0; + if (lines_visible > 0) { + switch (vertical_alignment) { + case VERTICAL_ALIGNMENT_TOP: { + // Nothing. + } break; + case VERTICAL_ALIGNMENT_CENTER: { + vbegin = (size.y - (total_h - line_spacing)) / 2; + vsep = 0; - // Get real total height. - total_h = 0; - for (int64_t i = lines_skipped; i < last_line; i++) { - total_h += TS->shaped_text_get_size(lines_rid[i]).y + font->get_spacing(TextServer::SPACING_TOP) + font->get_spacing(TextServer::SPACING_BOTTOM) + line_spacing; - } - total_h += style->get_margin(SIDE_TOP) + style->get_margin(SIDE_BOTTOM); - - int vbegin = 0, vsep = 0; - if (lines_visible > 0) { - switch (valign) { - case VALIGN_TOP: { - // Nothing. - } break; - case VALIGN_CENTER: { - vbegin = (size.y - (total_h - line_spacing)) / 2; - vsep = 0; - - } break; - case VALIGN_BOTTOM: { - vbegin = size.y - (total_h - line_spacing); - vsep = 0; - - } break; - case VALIGN_FILL: { - vbegin = 0; - if (lines_visible > 1) { - vsep = (size.y - (total_h - line_spacing)) / (lines_visible - 1); - } else { + } break; + case VERTICAL_ALIGNMENT_BOTTOM: { + vbegin = size.y - (total_h - line_spacing); vsep = 0; - } - } break; - } - } + } break; + case VERTICAL_ALIGNMENT_FILL: { + vbegin = 0; + if (lines_visible > 1) { + vsep = (size.y - (total_h - line_spacing)) / (lines_visible - 1); + } else { + vsep = 0; + } - Vector2 ofs; - ofs.y = style->get_offset().y + vbegin; - for (int i = lines_skipped; i < last_line; i++) { - Size2 line_size = TS->shaped_text_get_size(lines_rid[i]); - ofs.x = 0; - ofs.y += TS->shaped_text_get_ascent(lines_rid[i]) + font->get_spacing(TextServer::SPACING_TOP); - switch (align) { - case ALIGN_FILL: - if (rtl && autowrap_mode != AUTOWRAP_OFF) { - ofs.x = int(size.width - style->get_margin(SIDE_RIGHT) - line_size.width); - } else { - ofs.x = style->get_offset().x; - } - break; - case ALIGN_LEFT: { - if (rtl_layout) { - ofs.x = int(size.width - style->get_margin(SIDE_RIGHT) - line_size.width); - } else { - ofs.x = style->get_offset().x; - } - } break; - case ALIGN_CENTER: { - ofs.x = int(size.width - line_size.width) / 2; - } break; - case ALIGN_RIGHT: { - if (rtl_layout) { - ofs.x = style->get_offset().x; - } else { - ofs.x = int(size.width - style->get_margin(SIDE_RIGHT) - line_size.width); - } - } break; + } break; + } } - const Glyph *glyphs = TS->shaped_text_get_glyphs(lines_rid[i]); - int gl_size = TS->shaped_text_get_glyph_count(lines_rid[i]); - - int ellipsis_pos = TS->shaped_text_get_ellipsis_pos(lines_rid[i]); - int trim_pos = TS->shaped_text_get_trim_pos(lines_rid[i]); - - const Glyph *ellipsis_glyphs = TS->shaped_text_get_ellipsis_glyphs(lines_rid[i]); - int ellipsis_gl_size = TS->shaped_text_get_ellipsis_glyph_count(lines_rid[i]); + Vector2 ofs; + ofs.y = style->get_offset().y + vbegin; + for (int i = lines_skipped; i < last_line; i++) { + Size2 line_size = TS->shaped_text_get_size(lines_rid[i]); + ofs.x = 0; + ofs.y += TS->shaped_text_get_ascent(lines_rid[i]) + font->get_spacing(TextServer::SPACING_TOP); + switch (horizontal_alignment) { + case HORIZONTAL_ALIGNMENT_FILL: + if (rtl && autowrap_mode != AUTOWRAP_OFF) { + ofs.x = int(size.width - style->get_margin(SIDE_RIGHT) - line_size.width); + } else { + ofs.x = style->get_offset().x; + } + break; + case HORIZONTAL_ALIGNMENT_LEFT: { + if (rtl_layout) { + ofs.x = int(size.width - style->get_margin(SIDE_RIGHT) - line_size.width); + } else { + ofs.x = style->get_offset().x; + } + } break; + case HORIZONTAL_ALIGNMENT_CENTER: { + ofs.x = int(size.width - line_size.width) / 2; + } break; + case HORIZONTAL_ALIGNMENT_RIGHT: { + if (rtl_layout) { + ofs.x = style->get_offset().x; + } else { + ofs.x = int(size.width - style->get_margin(SIDE_RIGHT) - line_size.width); + } + } break; + } - // Draw outline. Note: Do not merge this into the single loop with the main text, to prevent overlaps. - if (font_shadow_color.a > 0 || (font_outline_color.a != 0.0 && outline_size > 0)) { - Vector2 offset = ofs; - // Draw RTL ellipsis string when necessary. - if (rtl && ellipsis_pos >= 0) { - for (int gl_idx = ellipsis_gl_size - 1; gl_idx >= 0; gl_idx--) { - for (int j = 0; j < ellipsis_glyphs[gl_idx].repeat; j++) { - //Draw glyph outlines and shadow. - draw_glyph_outline(ellipsis_glyphs[gl_idx], ci, font_color, font_shadow_color, font_outline_color, shadow_outline_size, outline_size, offset, shadow_ofs); - offset.x += ellipsis_glyphs[gl_idx].advance; + const Glyph *glyphs = TS->shaped_text_get_glyphs(lines_rid[i]); + int gl_size = TS->shaped_text_get_glyph_count(lines_rid[i]); + + int ellipsis_pos = TS->shaped_text_get_ellipsis_pos(lines_rid[i]); + int trim_pos = TS->shaped_text_get_trim_pos(lines_rid[i]); + + const Glyph *ellipsis_glyphs = TS->shaped_text_get_ellipsis_glyphs(lines_rid[i]); + int ellipsis_gl_size = TS->shaped_text_get_ellipsis_glyph_count(lines_rid[i]); + + // Draw outline. Note: Do not merge this into the single loop with the main text, to prevent overlaps. + int processed_glyphs_ol = processed_glyphs; + if ((outline_size > 0 && font_outline_color.a != 0) || (font_shadow_color.a != 0)) { + Vector2 offset = ofs; + // Draw RTL ellipsis string when necessary. + if (rtl && ellipsis_pos >= 0) { + for (int gl_idx = ellipsis_gl_size - 1; gl_idx >= 0; gl_idx--) { + for (int j = 0; j < ellipsis_glyphs[gl_idx].repeat; j++) { + bool skip = (trim_chars && ellipsis_glyphs[gl_idx].end > visible_chars) || (trim_glyphs_ltr && (processed_glyphs_ol >= visible_glyphs)) || (trim_glyphs_rtl && (processed_glyphs_ol < total_glyphs - visible_glyphs)); + //Draw glyph outlines and shadow. + if (!skip) { + draw_glyph_outline(ellipsis_glyphs[gl_idx], ci, font_color, font_shadow_color, font_outline_color, shadow_outline_size, outline_size, offset, shadow_ofs); + } + processed_glyphs_ol++; + offset.x += ellipsis_glyphs[gl_idx].advance; + } } } - } - // Draw main text. - for (int j = 0; j < gl_size; j++) { - for (int k = 0; k < glyphs[j].repeat; k++) { + // Draw main text. + for (int j = 0; j < gl_size; j++) { // Trim when necessary. if (trim_pos >= 0) { if (rtl) { - if (j < trim_pos && (glyphs[j].flags & TextServer::GRAPHEME_IS_VIRTUAL) != TextServer::GRAPHEME_IS_VIRTUAL) { + if (j < trim_pos) { continue; } } else { - if (j >= trim_pos && (glyphs[j].flags & TextServer::GRAPHEME_IS_VIRTUAL) != TextServer::GRAPHEME_IS_VIRTUAL) { + if (j >= trim_pos) { break; } } } + for (int k = 0; k < glyphs[j].repeat; k++) { + bool skip = (trim_chars && glyphs[j].end > visible_chars) || (trim_glyphs_ltr && (processed_glyphs_ol >= visible_glyphs)) || (trim_glyphs_rtl && (processed_glyphs_ol < total_glyphs - visible_glyphs)); - // Draw glyph outlines and shadow. - draw_glyph_outline(glyphs[j], ci, font_color, font_shadow_color, font_outline_color, shadow_outline_size, outline_size, offset, shadow_ofs); - offset.x += glyphs[j].advance; + // Draw glyph outlines and shadow. + if (!skip) { + draw_glyph_outline(glyphs[j], ci, font_color, font_shadow_color, font_outline_color, shadow_outline_size, outline_size, offset, shadow_ofs); + } + processed_glyphs_ol++; + offset.x += glyphs[j].advance; + } } - } - // Draw LTR ellipsis string when necessary. - if (!rtl && ellipsis_pos >= 0) { - for (int gl_idx = 0; gl_idx < ellipsis_gl_size; gl_idx++) { - for (int j = 0; j < ellipsis_glyphs[gl_idx].repeat; j++) { - //Draw glyph outlines and shadow. - draw_glyph_outline(ellipsis_glyphs[gl_idx], ci, font_color, font_shadow_color, font_outline_color, shadow_outline_size, outline_size, offset, shadow_ofs); - offset.x += ellipsis_glyphs[gl_idx].advance; + // Draw LTR ellipsis string when necessary. + if (!rtl && ellipsis_pos >= 0) { + for (int gl_idx = 0; gl_idx < ellipsis_gl_size; gl_idx++) { + for (int j = 0; j < ellipsis_glyphs[gl_idx].repeat; j++) { + bool skip = (trim_chars && ellipsis_glyphs[gl_idx].end > visible_chars) || (trim_glyphs_ltr && (processed_glyphs_ol >= visible_glyphs)) || (trim_glyphs_rtl && (processed_glyphs_ol < total_glyphs - visible_glyphs)); + //Draw glyph outlines and shadow. + if (!skip) { + draw_glyph_outline(ellipsis_glyphs[gl_idx], ci, font_color, font_shadow_color, font_outline_color, shadow_outline_size, outline_size, offset, shadow_ofs); + } + processed_glyphs_ol++; + offset.x += ellipsis_glyphs[gl_idx].advance; + } } } } - } - // Draw main text. Note: Do not merge this into the single loop with the outline, to prevent overlaps. + // Draw main text. Note: Do not merge this into the single loop with the outline, to prevent overlaps. - // Draw RTL ellipsis string when necessary. - if (rtl && ellipsis_pos >= 0) { - for (int gl_idx = ellipsis_gl_size - 1; gl_idx >= 0; gl_idx--) { - for (int j = 0; j < ellipsis_glyphs[gl_idx].repeat; j++) { - //Draw glyph outlines and shadow. - draw_glyph(ellipsis_glyphs[gl_idx], ci, font_color, ofs); - ofs.x += ellipsis_glyphs[gl_idx].advance; + // Draw RTL ellipsis string when necessary. + if (rtl && ellipsis_pos >= 0) { + for (int gl_idx = ellipsis_gl_size - 1; gl_idx >= 0; gl_idx--) { + for (int j = 0; j < ellipsis_glyphs[gl_idx].repeat; j++) { + bool skip = (trim_chars && ellipsis_glyphs[gl_idx].end > visible_chars) || (trim_glyphs_ltr && (processed_glyphs >= visible_glyphs)) || (trim_glyphs_rtl && (processed_glyphs < total_glyphs - visible_glyphs)); + //Draw glyph outlines and shadow. + if (!skip) { + draw_glyph(ellipsis_glyphs[gl_idx], ci, font_color, ofs); + } + processed_glyphs++; + ofs.x += ellipsis_glyphs[gl_idx].advance; + } } } - } - // Draw main text. - for (int j = 0; j < gl_size; j++) { - for (int k = 0; k < glyphs[j].repeat; k++) { + // Draw main text. + for (int j = 0; j < gl_size; j++) { // Trim when necessary. if (trim_pos >= 0) { if (rtl) { - if (j < trim_pos && (glyphs[j].flags & TextServer::GRAPHEME_IS_VIRTUAL) != TextServer::GRAPHEME_IS_VIRTUAL) { + if (j < trim_pos) { continue; } } else { - if (j >= trim_pos && (glyphs[j].flags & TextServer::GRAPHEME_IS_VIRTUAL) != TextServer::GRAPHEME_IS_VIRTUAL) { + if (j >= trim_pos) { break; } } } + for (int k = 0; k < glyphs[j].repeat; k++) { + bool skip = (trim_chars && glyphs[j].end > visible_chars) || (trim_glyphs_ltr && (processed_glyphs >= visible_glyphs)) || (trim_glyphs_rtl && (processed_glyphs < total_glyphs - visible_glyphs)); - // Draw glyph outlines and shadow. - draw_glyph(glyphs[j], ci, font_color, ofs); - ofs.x += glyphs[j].advance; + // Draw glyph outlines and shadow. + if (!skip) { + draw_glyph(glyphs[j], ci, font_color, ofs); + } + processed_glyphs++; + ofs.x += glyphs[j].advance; + } } - } - // Draw LTR ellipsis string when necessary. - if (!rtl && ellipsis_pos >= 0) { - for (int gl_idx = 0; gl_idx < ellipsis_gl_size; gl_idx++) { - for (int j = 0; j < ellipsis_glyphs[gl_idx].repeat; j++) { - //Draw glyph outlines and shadow. - draw_glyph(ellipsis_glyphs[gl_idx], ci, font_color, ofs); - ofs.x += ellipsis_glyphs[gl_idx].advance; + // Draw LTR ellipsis string when necessary. + if (!rtl && ellipsis_pos >= 0) { + for (int gl_idx = 0; gl_idx < ellipsis_gl_size; gl_idx++) { + for (int j = 0; j < ellipsis_glyphs[gl_idx].repeat; j++) { + bool skip = (trim_chars && ellipsis_glyphs[gl_idx].end > visible_chars) || (trim_glyphs_ltr && (processed_glyphs >= visible_glyphs)) || (trim_glyphs_rtl && (processed_glyphs < total_glyphs - visible_glyphs)); + //Draw glyph outlines and shadow. + if (!skip) { + draw_glyph(ellipsis_glyphs[gl_idx], ci, font_color, ofs); + } + processed_glyphs++; + ofs.x += ellipsis_glyphs[gl_idx].advance; + } } } + ofs.y += TS->shaped_text_get_descent(lines_rid[i]) + vsep + line_spacing + font->get_spacing(TextServer::SPACING_BOTTOM); } - ofs.y += TS->shaped_text_get_descent(lines_rid[i]) + vsep + line_spacing + font->get_spacing(TextServer::SPACING_BOTTOM); - } - } + } break; - if (p_what == NOTIFICATION_THEME_CHANGED) { - dirty = true; - update(); - } - if (p_what == NOTIFICATION_RESIZED) { - lines_dirty = true; + case NOTIFICATION_THEME_CHANGED: { + font_dirty = true; + update(); + } break; + + case NOTIFICATION_RESIZED: { + lines_dirty = true; + } break; } } Size2 Label::get_minimum_size() const { // don't want to mutable everything - if (dirty || lines_dirty) { + if (dirty || font_dirty || lines_dirty) { const_cast<Label *>(this)->_shape(); } @@ -526,7 +568,7 @@ int Label::get_line_count() const { if (!is_inside_tree()) { return 1; } - if (dirty || lines_dirty) { + if (dirty || font_dirty || lines_dirty) { const_cast<Label *>(this)->_shape(); } @@ -558,29 +600,29 @@ int Label::get_visible_line_count() const { return lines_visible; } -void Label::set_align(Align p_align) { - ERR_FAIL_INDEX((int)p_align, 4); - if (align != p_align) { - if (align == ALIGN_FILL || p_align == ALIGN_FILL) { +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. } - align = p_align; + horizontal_alignment = p_alignment; } update(); } -Label::Align Label::get_align() const { - return align; +HorizontalAlignment Label::get_horizontal_alignment() const { + return horizontal_alignment; } -void Label::set_valign(VAlign p_align) { - ERR_FAIL_INDEX((int)p_align, 4); - valign = p_align; +void Label::set_vertical_alignment(VerticalAlignment p_alignment) { + ERR_FAIL_INDEX((int)p_alignment, 4); + vertical_alignment = p_alignment; update(); } -Label::VAlign Label::get_valign() const { - return valign; +VerticalAlignment Label::get_vertical_alignment() const { + return vertical_alignment; } void Label::set_text(const String &p_string) { @@ -594,14 +636,14 @@ void Label::set_text(const String &p_string) { visible_chars = get_total_character_count() * percent_visible; } update(); - minimum_size_changed(); + update_minimum_size(); } void Label::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; - dirty = true; + font_dirty = true; update(); } } @@ -609,7 +651,7 @@ void Label::set_text_direction(Control::TextDirection p_text_direction) { void Label::set_structured_text_bidi_override(Control::StructuredTextParser p_parser) { if (st_parser != p_parser) { st_parser = p_parser; - dirty = true; + font_dirty = true; update(); } } @@ -620,7 +662,7 @@ Control::StructuredTextParser Label::get_structured_text_bidi_override() const { void Label::set_structured_text_bidi_override_options(Array p_args) { st_args = p_args; - dirty = true; + font_dirty = true; update(); } @@ -634,7 +676,7 @@ Control::TextDirection Label::get_text_direction() const { void Label::clear_opentype_features() { opentype_features.clear(); - dirty = true; + font_dirty = true; update(); } @@ -642,7 +684,7 @@ void Label::set_opentype_feature(const String &p_name, int p_value) { int32_t tag = TS->name_to_tag(p_name); if (!opentype_features.has(tag) || (int)opentype_features[tag] != p_value) { opentype_features[tag] = p_value; - dirty = true; + font_dirty = true; update(); } } @@ -670,7 +712,7 @@ String Label::get_language() const { void Label::set_clip_text(bool p_clip) { clip = p_clip; update(); - minimum_size_changed(); + update_minimum_size(); } bool Label::is_clipping_text() const { @@ -684,7 +726,7 @@ void Label::set_text_overrun_behavior(Label::OverrunBehavior p_behavior) { } update(); if (clip || overrun_behavior != OVERRUN_NO_TRIMMING) { - minimum_size_changed(); + update_minimum_size(); } } @@ -704,7 +746,9 @@ void Label::set_visible_characters(int p_amount) { } else { percent_visible = 1.0; } - dirty = true; + if (visible_chars_behavior == VC_CHARS_BEFORE_SHAPING) { + dirty = true; + } update(); } } @@ -722,7 +766,9 @@ void Label::set_percent_visible(float p_percent) { visible_chars = get_total_character_count() * p_percent; percent_visible = p_percent; } - dirty = true; + if (visible_chars_behavior == VC_CHARS_BEFORE_SHAPING) { + dirty = true; + } update(); } } @@ -731,6 +777,18 @@ float Label::get_percent_visible() const { return percent_visible; } +Label::VisibleCharactersBehavior Label::get_visible_characters_behavior() const { + return visible_chars_behavior; +} + +void Label::set_visible_characters_behavior(Label::VisibleCharactersBehavior p_behavior) { + if (visible_chars_behavior != p_behavior) { + visible_chars_behavior = p_behavior; + dirty = true; + update(); + } +} + void Label::set_lines_skipped(int p_lines) { ERR_FAIL_COND(p_lines < 0); lines_skipped = p_lines; @@ -753,7 +811,7 @@ int Label::get_max_lines_visible() const { } int Label::get_total_character_count() const { - if (dirty || lines_dirty) { + if (dirty || font_dirty || lines_dirty) { const_cast<Label *>(this)->_shape(); } @@ -765,17 +823,17 @@ bool Label::_set(const StringName &p_name, const Variant &p_value) { if (str.begins_with("opentype_features/")) { String name = str.get_slicec('/', 1); int32_t tag = TS->name_to_tag(name); - double value = p_value; + int value = p_value; if (value == -1) { if (opentype_features.has(tag)) { opentype_features.erase(tag); - dirty = true; + font_dirty = true; update(); } } else { - if ((double)opentype_features[tag] != value) { + if (!opentype_features.has(tag) || (int)opentype_features[tag] != value) { opentype_features[tag] = value; - dirty = true; + font_dirty = true; update(); } } @@ -805,16 +863,16 @@ bool Label::_get(const StringName &p_name, Variant &r_ret) const { void Label::_get_property_list(List<PropertyInfo> *p_list) const { for (const Variant *ftr = opentype_features.next(nullptr); ftr != nullptr; ftr = opentype_features.next(ftr)) { String name = TS->tag_to_name(*ftr); - p_list->push_back(PropertyInfo(Variant::FLOAT, "opentype_features/" + name)); + p_list->push_back(PropertyInfo(Variant::INT, "opentype_features/" + name)); } p_list->push_back(PropertyInfo(Variant::NIL, "opentype_features/_new", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_EDITOR)); } void Label::_bind_methods() { - ClassDB::bind_method(D_METHOD("set_align", "align"), &Label::set_align); - ClassDB::bind_method(D_METHOD("get_align"), &Label::get_align); - ClassDB::bind_method(D_METHOD("set_valign", "valign"), &Label::set_valign); - ClassDB::bind_method(D_METHOD("get_valign"), &Label::get_valign); + ClassDB::bind_method(D_METHOD("set_horizontal_alignment", "alignment"), &Label::set_horizontal_alignment); + ClassDB::bind_method(D_METHOD("get_horizontal_alignment"), &Label::get_horizontal_alignment); + ClassDB::bind_method(D_METHOD("set_vertical_alignment", "alignment"), &Label::set_vertical_alignment); + ClassDB::bind_method(D_METHOD("get_vertical_alignment"), &Label::get_vertical_alignment); ClassDB::bind_method(D_METHOD("set_text", "text"), &Label::set_text); ClassDB::bind_method(D_METHOD("get_text"), &Label::get_text); ClassDB::bind_method(D_METHOD("set_text_direction", "direction"), &Label::set_text_direction); @@ -838,6 +896,8 @@ void Label::_bind_methods() { ClassDB::bind_method(D_METHOD("get_total_character_count"), &Label::get_total_character_count); ClassDB::bind_method(D_METHOD("set_visible_characters", "amount"), &Label::set_visible_characters); 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_lines_skipped", "lines_skipped"), &Label::set_lines_skipped); @@ -849,16 +909,6 @@ void Label::_bind_methods() { ClassDB::bind_method(D_METHOD("set_structured_text_bidi_override_options", "args"), &Label::set_structured_text_bidi_override_options); ClassDB::bind_method(D_METHOD("get_structured_text_bidi_override_options"), &Label::get_structured_text_bidi_override_options); - BIND_ENUM_CONSTANT(ALIGN_LEFT); - BIND_ENUM_CONSTANT(ALIGN_CENTER); - BIND_ENUM_CONSTANT(ALIGN_RIGHT); - BIND_ENUM_CONSTANT(ALIGN_FILL); - - BIND_ENUM_CONSTANT(VALIGN_TOP); - BIND_ENUM_CONSTANT(VALIGN_CENTER); - BIND_ENUM_CONSTANT(VALIGN_BOTTOM); - BIND_ENUM_CONSTANT(VALIGN_FILL); - BIND_ENUM_CONSTANT(AUTOWRAP_OFF); BIND_ENUM_CONSTANT(AUTOWRAP_ARBITRARY); BIND_ENUM_CONSTANT(AUTOWRAP_WORD); @@ -870,19 +920,31 @@ void Label::_bind_methods() { BIND_ENUM_CONSTANT(OVERRUN_TRIM_ELLIPSIS); BIND_ENUM_CONSTANT(OVERRUN_TRIM_WORD_ELLIPSIS); + BIND_ENUM_CONSTANT(VC_CHARS_BEFORE_SHAPING); + BIND_ENUM_CONSTANT(VC_CHARS_AFTER_SHAPING); + BIND_ENUM_CONSTANT(VC_GLYPHS_AUTO); + BIND_ENUM_CONSTANT(VC_GLYPHS_LTR); + BIND_ENUM_CONSTANT(VC_GLYPHS_RTL); + ADD_PROPERTY(PropertyInfo(Variant::STRING, "text", PROPERTY_HINT_MULTILINE_TEXT, "", PROPERTY_USAGE_DEFAULT_INTL), "set_text", "get_text"); - 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"), "set_language", "get_language"); - ADD_PROPERTY(PropertyInfo(Variant::INT, "align", PROPERTY_HINT_ENUM, "Left,Center,Right,Fill"), "set_align", "get_align"); - ADD_PROPERTY(PropertyInfo(Variant::INT, "valign", PROPERTY_HINT_ENUM, "Top,Center,Bottom,Fill"), "set_valign", "get_valign"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "horizontal_alignment", PROPERTY_HINT_ENUM, "Left,Center,Right,Fill"), "set_horizontal_alignment", "get_horizontal_alignment"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "vertical_alignment", PROPERTY_HINT_ENUM, "Top,Center,Bottom,Fill"), "set_vertical_alignment", "get_vertical_alignment"); 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::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_PROPERTY(PropertyInfo(Variant::INT, "visible_characters", PROPERTY_HINT_RANGE, "-1,128000,1"), "set_visible_characters", "get_visible_characters"); - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "percent_visible", PROPERTY_HINT_RANGE, "0,1,0.001"), "set_percent_visible", "get_percent_visible"); 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. + 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_GROUP("Locale", ""); + 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"); + ADD_GROUP("Structured Text", "structured_text_"); ADD_PROPERTY(PropertyInfo(Variant::INT, "structured_text_bidi_override", PROPERTY_HINT_ENUM, "Default,URI,File,Email,List,None,Custom"), "set_structured_text_bidi_override", "get_structured_text_bidi_override"); ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "structured_text_bidi_override_options"), "set_structured_text_bidi_override_options", "get_structured_text_bidi_override_options"); diff --git a/scene/gui/label.h b/scene/gui/label.h index 8b48eb9670..0b931b3084 100644 --- a/scene/gui/label.h +++ b/scene/gui/label.h @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ @@ -37,20 +37,6 @@ class Label : public Control { GDCLASS(Label, Control); public: - enum Align { - ALIGN_LEFT, - ALIGN_CENTER, - ALIGN_RIGHT, - ALIGN_FILL - }; - - enum VAlign { - VALIGN_TOP, - VALIGN_CENTER, - VALIGN_BOTTOM, - VALIGN_FILL - }; - enum AutowrapMode { AUTOWRAP_OFF, AUTOWRAP_ARBITRARY, @@ -66,9 +52,17 @@ public: OVERRUN_TRIM_WORD_ELLIPSIS, }; + enum VisibleCharactersBehavior { + VC_CHARS_BEFORE_SHAPING, + VC_CHARS_AFTER_SHAPING, + VC_GLYPHS_AUTO, + VC_GLYPHS_LTR, + VC_GLYPHS_RTL, + }; + private: - Align align = ALIGN_LEFT; - VAlign valign = VALIGN_TOP; + HorizontalAlignment horizontal_alignment = HORIZONTAL_ALIGNMENT_LEFT; + VerticalAlignment vertical_alignment = VERTICAL_ALIGNMENT_TOP; String text; String xl_text; AutowrapMode autowrap_mode = AUTOWRAP_OFF; @@ -79,6 +73,7 @@ private: bool lines_dirty = true; bool dirty = true; + bool font_dirty = true; RID text_rid; Vector<RID> lines_rid; @@ -90,6 +85,7 @@ private: float percent_visible = 1.0; + VisibleCharactersBehavior visible_chars_behavior = VC_CHARS_BEFORE_SHAPING; int visible_chars = -1; int lines_skipped = 0; int max_lines_visible = -1; @@ -109,11 +105,11 @@ protected: public: virtual Size2 get_minimum_size() const override; - void set_align(Align p_align); - Align get_align() const; + void set_horizontal_alignment(HorizontalAlignment p_alignment); + HorizontalAlignment get_horizontal_alignment() const; - void set_valign(VAlign p_align); - VAlign get_valign() const; + void set_vertical_alignment(VerticalAlignment p_alignment); + VerticalAlignment get_vertical_alignment() const; void set_text(const String &p_string); String get_text() const; @@ -140,6 +136,9 @@ public: void set_uppercase(bool p_uppercase); bool is_uppercase() const; + VisibleCharactersBehavior get_visible_characters_behavior() const; + void set_visible_characters_behavior(VisibleCharactersBehavior p_behavior); + void set_visible_characters(int p_amount); int get_visible_characters() const; int get_total_character_count() const; @@ -167,9 +166,8 @@ public: ~Label(); }; -VARIANT_ENUM_CAST(Label::Align); -VARIANT_ENUM_CAST(Label::VAlign); VARIANT_ENUM_CAST(Label::AutowrapMode); VARIANT_ENUM_CAST(Label::OverrunBehavior); +VARIANT_ENUM_CAST(Label::VisibleCharactersBehavior); #endif diff --git a/scene/gui/line_edit.cpp b/scene/gui/line_edit.cpp index 8c33d306a1..da39a3d387 100644 --- a/scene/gui/line_edit.cpp +++ b/scene/gui/line_edit.cpp @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ @@ -225,17 +225,17 @@ void LineEdit::gui_input(const Ref<InputEvent> &p_event) { // Ignore mouse clicks in IME input mode. return; } - if (b->is_pressed() && b->get_button_index() == MOUSE_BUTTON_RIGHT && context_menu_enabled) { + if (b->is_pressed() && b->get_button_index() == MouseButton::RIGHT && context_menu_enabled) { _ensure_menu(); - menu->set_position(get_screen_transform().xform(get_local_mouse_position())); - menu->set_size(Vector2(1, 1)); + menu->set_position(get_screen_position() + get_local_mouse_position()); + menu->reset_size(); menu->popup(); grab_focus(); accept_event(); return; } - if (is_middle_mouse_paste_enabled() && b->is_pressed() && b->get_button_index() == MOUSE_BUTTON_MIDDLE && is_editable() && DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_CLIPBOARD_PRIMARY)) { + if (is_middle_mouse_paste_enabled() && b->is_pressed() && b->get_button_index() == MouseButton::MIDDLE && is_editable() && DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_CLIPBOARD_PRIMARY)) { String paste_buffer = DisplayServer::get_singleton()->clipboard_get_primary().strip_escapes(); deselect(); @@ -254,7 +254,7 @@ void LineEdit::gui_input(const Ref<InputEvent> &p_event) { return; } - if (b->get_button_index() != MOUSE_BUTTON_LEFT) { + if (b->get_button_index() != MouseButton::LEFT) { return; } @@ -268,7 +268,9 @@ void LineEdit::gui_input(const Ref<InputEvent> &p_event) { return; } - shift_selection_check_pre(b->is_shift_pressed()); + if (b->is_shift_pressed()) { + shift_selection_check_pre(true); + } set_caret_at_pixel_pos(b->get_position().x); @@ -320,7 +322,7 @@ void LineEdit::gui_input(const Ref<InputEvent> &p_event) { deselect(); selection.start_column = caret_column; selection.creating = true; - } else if (selection.enabled) { + } else if (selection.enabled && !selection.double_click) { selection.drag_attempt = true; } } @@ -328,7 +330,7 @@ void LineEdit::gui_input(const Ref<InputEvent> &p_event) { update(); } else { - if (selection.enabled && !pass && b->get_button_index() == MOUSE_BUTTON_LEFT && DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_CLIPBOARD_PRIMARY)) { + if (selection.enabled && !pass && b->get_button_index() == MouseButton::LEFT && DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_CLIPBOARD_PRIMARY)) { DisplayServer::get_singleton()->clipboard_set_primary(text.substr(selection.begin, selection.end - selection.begin)); } if (!text.is_empty() && is_editable() && clear_button_enabled) { @@ -345,6 +347,9 @@ void LineEdit::gui_input(const Ref<InputEvent> &p_event) { } selection.creating = false; selection.double_click = false; + if (!drag_action) { + selection.drag_attempt = false; + } show_virtual_keyboard(); } @@ -363,12 +368,17 @@ void LineEdit::gui_input(const Ref<InputEvent> &p_event) { } } - if (m->get_button_mask() & MOUSE_BUTTON_LEFT) { + if ((m->get_button_mask() & MouseButton::MASK_LEFT) != MouseButton::NONE) { if (selection.creating) { set_caret_at_pixel_pos(m->get_position().x); selection_fill_at_caret(); } } + + if (drag_action && can_drop_data(m->get_position(), get_viewport()->gui_get_drag_data())) { + drag_caret_force_displayed = true; + set_caret_at_pixel_pos(m->get_position().x); + } } Ref<InputEventKey> k = p_event; @@ -382,8 +392,8 @@ void LineEdit::gui_input(const Ref<InputEvent> &p_event) { 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); - menu->set_position(get_global_transform().xform(pos)); - menu->set_size(Vector2(1, 1)); + menu->set_position(get_screen_position() + pos); + menu->reset_size(); menu->popup(); menu->grab_focus(); } @@ -538,17 +548,17 @@ void LineEdit::gui_input(const Ref<InputEvent> &p_event) { } } -void LineEdit::set_align(Align p_align) { - ERR_FAIL_INDEX((int)p_align, 4); - if (align != p_align) { - align = p_align; +void LineEdit::set_horizontal_alignment(HorizontalAlignment p_alignment) { + ERR_FAIL_INDEX((int)p_alignment, 4); + if (alignment != p_alignment) { + alignment = p_alignment; _shape(); } update(); } -LineEdit::Align LineEdit::get_align() const { - return align; +HorizontalAlignment LineEdit::get_horizontal_alignment() const { + return alignment; } Variant LineEdit::get_drag_data(const Point2 &p_point) { @@ -569,22 +579,50 @@ bool LineEdit::can_drop_data(const Point2 &p_point, const Variant &p_data) const return drop_override; } - return p_data.get_type() == Variant::STRING; + return is_editable() && p_data.get_type() == Variant::STRING; } void LineEdit::drop_data(const Point2 &p_point, const Variant &p_data) { Control::drop_data(p_point, p_data); - if (p_data.get_type() == Variant::STRING) { + if (p_data.get_type() == Variant::STRING && is_editable()) { set_caret_at_pixel_pos(p_point.x); - int selected = selection.end - selection.begin; - - text.erase(selection.begin, selected); - _shape(); + int caret_column_tmp = caret_column; + bool is_inside_sel = selection.enabled && caret_column >= selection.begin && caret_column <= selection.end; + if (Input::get_singleton()->is_key_pressed(Key::CTRL)) { + is_inside_sel = selection.enabled && caret_column > selection.begin && caret_column < selection.end; + } + if (selection.drag_attempt) { + selection.drag_attempt = false; + if (!is_inside_sel) { + if (!Input::get_singleton()->is_key_pressed(Key::CTRL)) { + if (caret_column_tmp > selection.end) { + caret_column_tmp = caret_column_tmp - (selection.end - selection.begin); + } + selection_delete(); + } - insert_text_at_caret(p_data); - selection.begin = caret_column - selected; - selection.end = caret_column; + set_caret_column(caret_column_tmp); + insert_text_at_caret(p_data); + } + } else if (selection.enabled && caret_column >= selection.begin && caret_column <= selection.end) { + caret_column_tmp = selection.begin; + selection_delete(); + set_caret_column(caret_column_tmp); + insert_text_at_caret(p_data); + grab_focus(); + } else { + insert_text_at_caret(p_data); + grab_focus(); + } + select(caret_column_tmp, caret_column); + if (!text_changed_dirty) { + if (is_inside_tree()) { + MessageQueue::get_singleton()->push_call(this, "_text_changed"); + } + text_changed_dirty = true; + } + update(); } } @@ -609,8 +647,8 @@ void LineEdit::_notification(int 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_DEF("text_editor/appearance/caret/caret_blink", false)); - set_caret_blink_speed(EDITOR_DEF("text_editor/appearance/caret/caret_blink_speed", 0.65)); + 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")); 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)); @@ -618,31 +656,37 @@ void LineEdit::_notification(int p_what) { } } break; #endif + case NOTIFICATION_RESIZED: { _fit_to_width(); scroll_offset = 0; set_caret_column(get_caret_column()); } break; + case NOTIFICATION_LAYOUT_DIRECTION_CHANGED: case NOTIFICATION_THEME_CHANGED: { _shape(); update(); } break; + case NOTIFICATION_TRANSLATION_CHANGED: { placeholder_translated = atr(placeholder); _shape(); update(); } break; + case NOTIFICATION_WM_WINDOW_FOCUS_IN: { window_has_focus = true; draw_caret = true; update(); } break; + case NOTIFICATION_WM_WINDOW_FOCUS_OUT: { window_has_focus = false; draw_caret = false; update(); } break; + case NOTIFICATION_DRAW: { if ((!has_focus() && !(menu && menu->has_focus()) && !caret_force_displayed) || !window_has_focus) { draw_caret = false; @@ -664,7 +708,9 @@ void LineEdit::_notification(int p_what) { } Ref<Font> font = get_theme_font(SNAME("font")); - style->draw(ci, Rect2(Point2(), size)); + if (!flat) { + style->draw(ci, Rect2(Point2(), size)); + } if (has_focus()) { get_theme_stylebox(SNAME("focus"))->draw(ci, Rect2(Point2(), size)); @@ -675,23 +721,23 @@ void LineEdit::_notification(int p_what) { float text_width = TS->shaped_text_get_size(text_rid).x; float text_height = TS->shaped_text_get_size(text_rid).y + font->get_spacing(TextServer::SPACING_TOP) + font->get_spacing(TextServer::SPACING_BOTTOM); - switch (align) { - case ALIGN_FILL: - case ALIGN_LEFT: { + switch (alignment) { + case HORIZONTAL_ALIGNMENT_FILL: + case HORIZONTAL_ALIGNMENT_LEFT: { if (rtl) { x_ofs = MAX(style->get_margin(SIDE_LEFT), int(size.width - style->get_margin(SIDE_RIGHT) - (text_width))); } else { x_ofs = style->get_offset().x; } } break; - case ALIGN_CENTER: { + case HORIZONTAL_ALIGNMENT_CENTER: { if (scroll_offset != 0) { x_ofs = style->get_offset().x; } else { x_ofs = MAX(style->get_margin(SIDE_LEFT), int(size.width - (text_width)) / 2); } } break; - case ALIGN_RIGHT: { + case HORIZONTAL_ALIGNMENT_RIGHT: { if (rtl) { x_ofs = style->get_offset().x; } else { @@ -712,7 +758,7 @@ void LineEdit::_notification(int p_what) { // Draw placeholder color. if (using_placeholder) { - font_color.a *= placeholder_alpha; + font_color = get_theme_color(SNAME("font_placeholder_color")); } bool display_clear_icon = !using_placeholder && is_editable() && clear_button_enabled; @@ -729,7 +775,7 @@ void LineEdit::_notification(int p_what) { 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 (align == ALIGN_CENTER) { + if (alignment == HORIZONTAL_ALIGNMENT_CENTER) { if (scroll_offset == 0) { x_ofs = MAX(style->get_margin(SIDE_LEFT), int(size.width - text_width - r_icon->get_width() - style->get_margin(SIDE_RIGHT) * 2) / 2); } @@ -740,8 +786,6 @@ void LineEdit::_notification(int p_what) { ofs_max -= r_icon->get_width(); } - int caret_width = Math::round(1 * get_theme_default_base_scale()); - // Draw selections rects. Vector2 ofs = Point2(x_ofs + scroll_offset, y_ofs); if (selection.enabled) { @@ -802,7 +846,10 @@ void LineEdit::_notification(int p_what) { // Draw carets. ofs.x = x_ofs + scroll_offset; - if (draw_caret) { + 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()); + if (ime_text.length() == 0) { // Normal caret. CaretInfo caret = TS->shaped_text_get_carets(text_rid, caret_column); @@ -883,6 +930,7 @@ void LineEdit::_notification(int p_what) { } } } break; + case NOTIFICATION_FOCUS_ENTER: { if (!caret_force_displayed) { if (caret_blink_enabled) { @@ -902,6 +950,7 @@ void LineEdit::_notification(int p_what) { show_virtual_keyboard(); } break; + case NOTIFICATION_FOCUS_EXIT: { if (caret_blink_enabled && !caret_force_displayed) { caret_blink_timer->stop(); @@ -920,10 +969,11 @@ void LineEdit::_notification(int p_what) { DisplayServer::get_singleton()->virtual_keyboard_hide(); } - if (deselect_on_focus_loss_enabled) { + if (deselect_on_focus_loss_enabled && !selection.drag_attempt) { deselect(); } } break; + case MainLoop::NOTIFICATION_OS_IME_UPDATE: { if (has_focus()) { ime_text = DisplayServer::get_singleton()->ime_get_text(); @@ -934,6 +984,27 @@ void LineEdit::_notification(int p_what) { update(); } } break; + + case NOTIFICATION_DRAG_BEGIN: { + drag_action = true; + } break; + + case NOTIFICATION_DRAG_END: { + if (is_drag_successful()) { + if (selection.drag_attempt) { + selection.drag_attempt = false; + if (is_editable() && !Input::get_singleton()->is_key_pressed(Key::CTRL)) { + selection_delete(); + } else if (deselect_on_focus_loss_enabled) { + deselect(); + } + } + } else { + selection.drag_attempt = false; + } + drag_action = false; + drag_caret_force_displayed = false; + } break; } } @@ -958,7 +1029,7 @@ void LineEdit::paste_text() { // Strip escape characters like \n and \t as they can't be displayed on LineEdit. String paste_buffer = DisplayServer::get_singleton()->clipboard_get().strip_escapes(); - if (paste_buffer != "") { + if (!paste_buffer.is_empty()) { int prev_len = text.length(); if (selection.enabled) { selection_delete(); @@ -998,6 +1069,9 @@ void LineEdit::undo() { } else if (undo_stack_pos == undo_stack.front()) { return; } + + deselect(); + undo_stack_pos = undo_stack_pos->prev(); TextOperation op = undo_stack_pos->get(); text = op.text; @@ -1019,6 +1093,9 @@ void LineEdit::redo() { if (undo_stack_pos == undo_stack.back()) { return; } + + deselect(); + undo_stack_pos = undo_stack_pos->next(); TextOperation op = undo_stack_pos->get(); text = op.text; @@ -1050,23 +1127,23 @@ void LineEdit::set_caret_at_pixel_pos(int p_x) { int x_ofs = 0; float text_width = TS->shaped_text_get_size(text_rid).x; - switch (align) { - case ALIGN_FILL: - case ALIGN_LEFT: { + switch (alignment) { + case HORIZONTAL_ALIGNMENT_FILL: + case HORIZONTAL_ALIGNMENT_LEFT: { if (rtl) { x_ofs = MAX(style->get_margin(SIDE_LEFT), int(get_size().width - style->get_margin(SIDE_RIGHT) - (text_width))); } else { x_ofs = style->get_offset().x; } } break; - case ALIGN_CENTER: { + case HORIZONTAL_ALIGNMENT_CENTER: { if (scroll_offset != 0) { x_ofs = style->get_offset().x; } else { x_ofs = MAX(style->get_margin(SIDE_LEFT), int(get_size().width - (text_width)) / 2); } } break; - case ALIGN_RIGHT: { + case HORIZONTAL_ALIGNMENT_RIGHT: { if (rtl) { x_ofs = style->get_offset().x; } else { @@ -1079,7 +1156,7 @@ void LineEdit::set_caret_at_pixel_pos(int p_x) { 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; - if (align == ALIGN_CENTER) { + if (alignment == HORIZONTAL_ALIGNMENT_CENTER) { if (scroll_offset == 0) { 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); } @@ -1098,23 +1175,23 @@ Vector2i LineEdit::get_caret_pixel_pos() { int x_ofs = 0; float text_width = TS->shaped_text_get_size(text_rid).x; - switch (align) { - case ALIGN_FILL: - case ALIGN_LEFT: { + switch (alignment) { + case HORIZONTAL_ALIGNMENT_FILL: + case HORIZONTAL_ALIGNMENT_LEFT: { if (rtl) { x_ofs = MAX(style->get_margin(SIDE_LEFT), int(get_size().width - style->get_margin(SIDE_RIGHT) - (text_width))); } else { x_ofs = style->get_offset().x; } } break; - case ALIGN_CENTER: { + case HORIZONTAL_ALIGNMENT_CENTER: { if (scroll_offset != 0) { x_ofs = style->get_offset().x; } else { x_ofs = MAX(style->get_margin(SIDE_LEFT), int(get_size().width - (text_width)) / 2); } } break; - case ALIGN_RIGHT: { + case HORIZONTAL_ALIGNMENT_RIGHT: { if (rtl) { x_ofs = style->get_offset().x; } else { @@ -1127,7 +1204,7 @@ Vector2i LineEdit::get_caret_pixel_pos() { 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; - if (align == ALIGN_CENTER) { + if (alignment == HORIZONTAL_ALIGNMENT_CENTER) { if (scroll_offset == 0) { 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); } @@ -1242,7 +1319,7 @@ void LineEdit::delete_char() { return; } - text.erase(caret_column - 1, 1); + text = text.left(caret_column - 1) + text.substr(caret_column); _shape(); set_caret_column(get_caret_column() - 1); @@ -1254,7 +1331,7 @@ void LineEdit::delete_text(int p_from_column, int p_to_column) { ERR_FAIL_COND_MSG(p_from_column < 0 || p_from_column > p_to_column || p_to_column > text.length(), vformat("Positional parameters (from: %d, to: %d) are inverted or outside the text length (%d).", p_from_column, p_to_column, text.length())); - text.erase(p_from_column, p_to_column - p_from_column); + text = text.left(p_from_column) + text.substr(p_to_column); _shape(); caret_column -= CLAMP(caret_column - p_from_column, 0, p_to_column - p_from_column); @@ -1411,15 +1488,6 @@ String LineEdit::get_placeholder() const { return placeholder; } -void LineEdit::set_placeholder_alpha(float p_alpha) { - placeholder_alpha = p_alpha; - update(); -} - -float LineEdit::get_placeholder_alpha() const { - return placeholder_alpha; -} - void LineEdit::set_caret_column(int p_column) { if (p_column > (int)text.length()) { p_column = text.length(); @@ -1443,23 +1511,23 @@ void LineEdit::set_caret_column(int p_column) { int x_ofs = 0; float text_width = TS->shaped_text_get_size(text_rid).x; - switch (align) { - case ALIGN_FILL: - case ALIGN_LEFT: { + switch (alignment) { + case HORIZONTAL_ALIGNMENT_FILL: + case HORIZONTAL_ALIGNMENT_LEFT: { if (rtl) { x_ofs = MAX(style->get_margin(SIDE_LEFT), int(get_size().width - style->get_margin(SIDE_RIGHT) - (text_width))); } else { x_ofs = style->get_offset().x; } } break; - case ALIGN_CENTER: { + case HORIZONTAL_ALIGNMENT_CENTER: { if (scroll_offset != 0) { x_ofs = style->get_offset().x; } else { x_ofs = MAX(style->get_margin(SIDE_LEFT), int(get_size().width - (text_width)) / 2); } } break; - case ALIGN_RIGHT: { + case HORIZONTAL_ALIGNMENT_RIGHT: { if (rtl) { x_ofs = style->get_offset().x; } else { @@ -1473,7 +1541,7 @@ void LineEdit::set_caret_column(int p_column) { 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; - if (align == ALIGN_CENTER) { + if (alignment == HORIZONTAL_ALIGNMENT_CENTER) { if (scroll_offset == 0) { 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); } @@ -1550,7 +1618,7 @@ Size2 LineEdit::get_minimum_size() const { Size2 min_size; // Minimum size of text. - int em_space_size = font->get_char_size('M', 0, font_size).x; + float em_space_size = font->get_char_size('M', 0, font_size).x; min_size.width = get_theme_constant(SNAME("minimum_character_width")) * em_space_size; if (expand_to_text_length) { @@ -1653,7 +1721,7 @@ void LineEdit::set_editable(bool p_editable) { editable = p_editable; - minimum_size_changed(); + update_minimum_size(); update(); } @@ -1876,14 +1944,14 @@ PopupMenu *LineEdit::get_menu() const { void LineEdit::_editor_settings_changed() { #ifdef TOOLS_ENABLED - set_caret_blink_enabled(EDITOR_DEF("text_editor/appearance/caret/caret_blink", false)); - set_caret_blink_speed(EDITOR_DEF("text_editor/appearance/caret/caret_blink_speed", 0.65)); + 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")); #endif } void LineEdit::set_expand_to_text_length_enabled(bool p_enabled) { expand_to_text_length = p_enabled; - minimum_size_changed(); + update_minimum_size(); set_caret_column(caret_column); } @@ -1897,7 +1965,7 @@ void LineEdit::set_clear_button_enabled(bool p_enabled) { } clear_button_enabled = p_enabled; _fit_to_width(); - minimum_size_changed(); + update_minimum_size(); update(); } @@ -1958,7 +2026,7 @@ void LineEdit::set_right_icon(const Ref<Texture2D> &p_icon) { } right_icon = p_icon; _fit_to_width(); - minimum_size_changed(); + update_minimum_size(); update(); } @@ -1966,6 +2034,17 @@ Ref<Texture2D> LineEdit::get_right_icon() { return right_icon; } +void LineEdit::set_flat(bool p_enabled) { + if (flat != p_enabled) { + flat = p_enabled; + update(); + } +} + +bool LineEdit::is_flat() const { + return flat; +} + void LineEdit::_text_changed() { _emit_text_change(); _clear_redo(); @@ -2002,7 +2081,7 @@ void LineEdit::_shape() { const Ref<Font> &font = get_theme_font(SNAME("font")); int font_size = get_theme_font_size(SNAME("font_size")); ERR_FAIL_COND(font.is_null()); - TS->shaped_text_add_string(text_rid, t, font->get_rids(), font_size, opentype_features, (language != "") ? language : TranslationServer::get_singleton()->get_tool_locale()); + TS->shaped_text_add_string(text_rid, t, font->get_rids(), font_size, opentype_features, (!language.is_empty()) ? language : TranslationServer::get_singleton()->get_tool_locale()); TS->shaped_text_set_bidi_override(text_rid, structured_text_parser(st_parser, st_args, t)); full_width = TS->shaped_text_get_size(text_rid).x; @@ -2011,12 +2090,12 @@ void LineEdit::_shape() { Size2 size = TS->shaped_text_get_size(text_rid); if ((expand_to_text_length && old_size.x != size.x) || (old_size.y != size.y)) { - minimum_size_changed(); + update_minimum_size(); } } void LineEdit::_fit_to_width() { - if (align == ALIGN_FILL) { + if (alignment == HORIZONTAL_ALIGNMENT_FILL) { Ref<StyleBox> style = get_theme_stylebox(SNAME("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(); @@ -2058,25 +2137,25 @@ void LineEdit::_create_undo_state() { undo_stack.push_back(op); } -int LineEdit::_get_menu_action_accelerator(const String &p_action) { +Key LineEdit::_get_menu_action_accelerator(const String &p_action) { const List<Ref<InputEvent>> *events = InputMap::get_singleton()->action_get_events(p_action); if (!events) { - return 0; + return Key::NONE; } // Use first event in the list for the accelerator. const List<Ref<InputEvent>>::Element *first_event = events->front(); if (!first_event) { - return 0; + return Key::NONE; } const Ref<InputEventKey> event = first_event->get(); if (event.is_null()) { - return 0; + return Key::NONE; } // Use physical keycode if non-zero - if (event->get_physical_keycode() != 0) { + if (event->get_physical_keycode() != Key::NONE) { return event->get_physical_keycode_with_modifiers(); } else { return event->get_keycode_with_modifiers(); @@ -2088,7 +2167,7 @@ bool LineEdit::_set(const StringName &p_name, const Variant &p_value) { if (str.begins_with("opentype_features/")) { String name = str.get_slicec('/', 1); int32_t tag = TS->name_to_tag(name); - double value = p_value; + int value = p_value; if (value == -1) { if (opentype_features.has(tag)) { opentype_features.erase(tag); @@ -2096,7 +2175,7 @@ bool LineEdit::_set(const StringName &p_name, const Variant &p_value) { update(); } } else { - if ((double)opentype_features[tag] != value) { + if (!opentype_features.has(tag) || (int)opentype_features[tag] != value) { opentype_features[tag] = value; _shape(); update(); @@ -2128,22 +2207,22 @@ bool LineEdit::_get(const StringName &p_name, Variant &r_ret) const { void LineEdit::_get_property_list(List<PropertyInfo> *p_list) const { for (const Variant *ftr = opentype_features.next(nullptr); ftr != nullptr; ftr = opentype_features.next(ftr)) { String name = TS->tag_to_name(*ftr); - p_list->push_back(PropertyInfo(Variant::FLOAT, "opentype_features/" + name)); + p_list->push_back(PropertyInfo(Variant::INT, "opentype_features/" + name)); } p_list->push_back(PropertyInfo(Variant::NIL, "opentype_features/_new", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_EDITOR)); } void LineEdit::_validate_property(PropertyInfo &property) const { if (!caret_blink_enabled && property.name == "caret_blink_speed") { - property.usage = PROPERTY_USAGE_NOEDITOR; + property.usage = PROPERTY_USAGE_NO_EDITOR; } } void LineEdit::_bind_methods() { ClassDB::bind_method(D_METHOD("_text_changed"), &LineEdit::_text_changed); - ClassDB::bind_method(D_METHOD("set_align", "align"), &LineEdit::set_align); - ClassDB::bind_method(D_METHOD("get_align"), &LineEdit::get_align); + ClassDB::bind_method(D_METHOD("set_horizontal_alignment", "alignment"), &LineEdit::set_horizontal_alignment); + ClassDB::bind_method(D_METHOD("get_horizontal_alignment"), &LineEdit::get_horizontal_alignment); ClassDB::bind_method(D_METHOD("clear"), &LineEdit::clear); ClassDB::bind_method(D_METHOD("select", "from", "to"), &LineEdit::select, DEFVAL(0), DEFVAL(-1)); @@ -2169,8 +2248,6 @@ void LineEdit::_bind_methods() { ClassDB::bind_method(D_METHOD("get_structured_text_bidi_override_options"), &LineEdit::get_structured_text_bidi_override_options); ClassDB::bind_method(D_METHOD("set_placeholder", "text"), &LineEdit::set_placeholder); ClassDB::bind_method(D_METHOD("get_placeholder"), &LineEdit::get_placeholder); - ClassDB::bind_method(D_METHOD("set_placeholder_alpha", "alpha"), &LineEdit::set_placeholder_alpha); - ClassDB::bind_method(D_METHOD("get_placeholder_alpha"), &LineEdit::get_placeholder_alpha); ClassDB::bind_method(D_METHOD("set_caret_column", "position"), &LineEdit::set_caret_column); ClassDB::bind_method(D_METHOD("get_caret_column"), &LineEdit::get_caret_column); ClassDB::bind_method(D_METHOD("get_scroll_offset"), &LineEdit::get_scroll_offset); @@ -2214,16 +2291,13 @@ void LineEdit::_bind_methods() { ClassDB::bind_method(D_METHOD("is_deselect_on_focus_loss_enabled"), &LineEdit::is_deselect_on_focus_loss_enabled); ClassDB::bind_method(D_METHOD("set_right_icon", "icon"), &LineEdit::set_right_icon); ClassDB::bind_method(D_METHOD("get_right_icon"), &LineEdit::get_right_icon); + ClassDB::bind_method(D_METHOD("set_flat", "enabled"), &LineEdit::set_flat); + ClassDB::bind_method(D_METHOD("is_flat"), &LineEdit::is_flat); ADD_SIGNAL(MethodInfo("text_changed", PropertyInfo(Variant::STRING, "new_text"))); ADD_SIGNAL(MethodInfo("text_change_rejected", PropertyInfo(Variant::STRING, "rejected_substring"))); ADD_SIGNAL(MethodInfo("text_submitted", PropertyInfo(Variant::STRING, "new_text"))); - BIND_ENUM_CONSTANT(ALIGN_LEFT); - BIND_ENUM_CONSTANT(ALIGN_CENTER); - BIND_ENUM_CONSTANT(ALIGN_RIGHT); - BIND_ENUM_CONSTANT(ALIGN_FILL); - BIND_ENUM_CONSTANT(MENU_CUT); BIND_ENUM_CONSTANT(MENU_COPY); BIND_ENUM_CONSTANT(MENU_PASTE); @@ -2255,7 +2329,8 @@ void LineEdit::_bind_methods() { BIND_ENUM_CONSTANT(MENU_MAX); ADD_PROPERTY(PropertyInfo(Variant::STRING, "text"), "set_text", "get_text"); - ADD_PROPERTY(PropertyInfo(Variant::INT, "align", PROPERTY_HINT_ENUM, "Left,Center,Right,Fill"), "set_align", "get_align"); + ADD_PROPERTY(PropertyInfo(Variant::STRING, "placeholder_text"), "set_placeholder", "get_placeholder"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "alignment", PROPERTY_HINT_ENUM, "Left,Center,Right,Fill"), "set_horizontal_alignment", "get_horizontal_alignment"); ADD_PROPERTY(PropertyInfo(Variant::INT, "max_length", PROPERTY_HINT_RANGE, "0,1000,1,or_greater"), "set_max_length", "get_max_length"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "editable"), "set_editable", "is_editable"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "secret"), "set_secret", "is_secret"); @@ -2269,15 +2344,13 @@ void LineEdit::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::BOOL, "selecting_enabled"), "set_selecting_enabled", "is_selecting_enabled"); 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::OBJECT, "right_icon", PROPERTY_HINT_RESOURCE_TYPE, "Texture"), "set_right_icon", "get_right_icon"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "flat"), "set_flat", "is_flat"); 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"), "set_language", "get_language"); + ADD_PROPERTY(PropertyInfo(Variant::STRING, "language", PROPERTY_HINT_LOCALE_ID, ""), "set_language", "get_language"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "draw_control_chars"), "set_draw_control_chars", "get_draw_control_chars"); ADD_GROUP("Structured Text", "structured_text_"); ADD_PROPERTY(PropertyInfo(Variant::INT, "structured_text_bidi_override", PROPERTY_HINT_ENUM, "Default,URI,File,Email,List,None,Custom"), "set_structured_text_bidi_override", "get_structured_text_bidi_override"); ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "structured_text_bidi_override_options"), "set_structured_text_bidi_override_options", "get_structured_text_bidi_override_options"); - ADD_GROUP("Placeholder", "placeholder_"); - ADD_PROPERTY(PropertyInfo(Variant::STRING, "placeholder_text"), "set_placeholder", "get_placeholder"); - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "placeholder_alpha", PROPERTY_HINT_RANGE, "0,1,0.001"), "set_placeholder_alpha", "get_placeholder_alpha"); ADD_GROUP("Caret", "caret_"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "caret_blink"), "set_caret_blink_enabled", "is_caret_blink_enabled"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "caret_blink_speed", PROPERTY_HINT_RANGE, "0.1,10,0.01"), "set_caret_blink_speed", "get_caret_blink_speed"); @@ -2329,21 +2402,21 @@ void LineEdit::_ensure_menu() { // Reorganize context menu. menu->clear(); if (editable) { - menu->add_item(RTR("Cut"), MENU_CUT, is_shortcut_keys_enabled() ? _get_menu_action_accelerator("ui_cut") : 0); + menu->add_item(RTR("Cut"), MENU_CUT, is_shortcut_keys_enabled() ? _get_menu_action_accelerator("ui_cut") : Key::NONE); } - menu->add_item(RTR("Copy"), MENU_COPY, is_shortcut_keys_enabled() ? _get_menu_action_accelerator("ui_copy") : 0); + menu->add_item(RTR("Copy"), MENU_COPY, is_shortcut_keys_enabled() ? _get_menu_action_accelerator("ui_copy") : Key::NONE); if (editable) { - menu->add_item(RTR("Paste"), MENU_PASTE, is_shortcut_keys_enabled() ? _get_menu_action_accelerator("ui_paste") : 0); + menu->add_item(RTR("Paste"), MENU_PASTE, is_shortcut_keys_enabled() ? _get_menu_action_accelerator("ui_paste") : Key::NONE); } menu->add_separator(); if (is_selecting_enabled()) { - menu->add_item(RTR("Select All"), MENU_SELECT_ALL, is_shortcut_keys_enabled() ? _get_menu_action_accelerator("ui_text_select_all") : 0); + menu->add_item(RTR("Select All"), MENU_SELECT_ALL, is_shortcut_keys_enabled() ? _get_menu_action_accelerator("ui_text_select_all") : Key::NONE); } if (editable) { menu->add_item(RTR("Clear"), MENU_CLEAR); menu->add_separator(); - menu->add_item(RTR("Undo"), MENU_UNDO, is_shortcut_keys_enabled() ? _get_menu_action_accelerator("ui_undo") : 0); - menu->add_item(RTR("Redo"), MENU_REDO, is_shortcut_keys_enabled() ? _get_menu_action_accelerator("ui_redo") : 0); + menu->add_item(RTR("Undo"), MENU_UNDO, is_shortcut_keys_enabled() ? _get_menu_action_accelerator("ui_undo") : Key::NONE); + menu->add_item(RTR("Redo"), MENU_REDO, is_shortcut_keys_enabled() ? _get_menu_action_accelerator("ui_redo") : Key::NONE); } menu->add_separator(); menu->add_submenu_item(RTR("Text Writing Direction"), "DirMenu"); @@ -2364,7 +2437,7 @@ void LineEdit::_ensure_menu() { } } -LineEdit::LineEdit() { +LineEdit::LineEdit(const String &p_placeholder) { text_rid = TS->create_shaped_text(); _create_undo_state(); @@ -2379,6 +2452,8 @@ LineEdit::LineEdit() { caret_blink_timer->connect("timeout", callable_mp(this, &LineEdit::_toggle_draw_caret)); set_caret_blink_enabled(false); + set_placeholder(p_placeholder); + set_editable(true); // Initialise to opposite first, so we get past the early-out in set_editable. } diff --git a/scene/gui/line_edit.h b/scene/gui/line_edit.h index 3364e02e01..444c9a1c50 100644 --- a/scene/gui/line_edit.h +++ b/scene/gui/line_edit.h @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ @@ -38,13 +38,6 @@ class LineEdit : public Control { GDCLASS(LineEdit, Control); public: - enum Align { - ALIGN_LEFT, - ALIGN_CENTER, - ALIGN_RIGHT, - ALIGN_FILL - }; - enum MenuItems { MENU_CUT, MENU_COPY, @@ -78,7 +71,7 @@ public: }; private: - Align align = ALIGN_LEFT; + HorizontalAlignment alignment = HORIZONTAL_ALIGNMENT_LEFT; bool editable = false; bool pass = false; @@ -89,7 +82,6 @@ private: String placeholder; String placeholder_translated; String secret_character = "*"; - float placeholder_alpha = 0.6; String ime_text; Point2 ime_selection; @@ -104,7 +96,7 @@ private: PopupMenu *menu_dir = nullptr; PopupMenu *menu_ctl = nullptr; - bool caret_mid_grapheme_enabled = false; + bool caret_mid_grapheme_enabled = true; int caret_column = 0; int scroll_offset = 0; @@ -129,7 +121,11 @@ private: bool middle_mouse_paste_enabled = true; + bool drag_action = false; + bool drag_caret_force_displayed = false; + Ref<Texture2D> right_icon; + bool flat = false; struct Selection { int begin = 0; @@ -169,7 +165,7 @@ private: void _clear_redo(); void _create_undo_state(); - int _get_menu_action_accelerator(const String &p_action); + Key _get_menu_action_accelerator(const String &p_action); void _shape(); void _fit_to_width(); @@ -214,8 +210,8 @@ protected: void _validate_property(PropertyInfo &property) const override; public: - void set_align(Align p_align); - Align get_align() const; + void set_horizontal_alignment(HorizontalAlignment p_alignment); + HorizontalAlignment get_horizontal_alignment() const; virtual Variant get_drag_data(const Point2 &p_point) override; virtual bool can_drop_data(const Point2 &p_point, const Variant &p_data) const override; @@ -265,9 +261,6 @@ public: void set_placeholder(String p_text); String get_placeholder() const; - void set_placeholder_alpha(float p_alpha); - float get_placeholder_alpha() const; - void set_caret_column(int p_column); int get_caret_column() const; @@ -332,15 +325,17 @@ public: void set_right_icon(const Ref<Texture2D> &p_icon); Ref<Texture2D> get_right_icon(); + void set_flat(bool p_enabled); + bool is_flat() const; + virtual bool is_text_field() const override; void show_virtual_keyboard(); - LineEdit(); + LineEdit(const String &p_placeholder = String()); ~LineEdit(); }; -VARIANT_ENUM_CAST(LineEdit::Align); VARIANT_ENUM_CAST(LineEdit::MenuItems); #endif diff --git a/scene/gui/link_button.cpp b/scene/gui/link_button.cpp index c3201186ea..dc4f09d22d 100644 --- a/scene/gui/link_button.cpp +++ b/scene/gui/link_button.cpp @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ @@ -29,6 +29,7 @@ /*************************************************************************/ #include "link_button.h" + #include "core/string/translation.h" void LinkButton::_shape() { @@ -42,7 +43,7 @@ void LinkButton::_shape() { text_buf->set_direction((TextServer::Direction)text_direction); } TS->shaped_text_set_bidi_override(text_buf->get_rid(), structured_text_parser(st_parser, st_args, xl_text)); - text_buf->add_string(xl_text, font, font_size, opentype_features, (language != "") ? language : TranslationServer::get_singleton()->get_tool_locale()); + text_buf->add_string(xl_text, font, font_size, opentype_features, (!language.is_empty()) ? language : TranslationServer::get_singleton()->get_tool_locale()); } void LinkButton::set_text(const String &p_text) { @@ -52,7 +53,7 @@ void LinkButton::set_text(const String &p_text) { text = p_text; xl_text = atr(text); _shape(); - minimum_size_changed(); + update_minimum_size(); update(); } @@ -148,18 +149,20 @@ void LinkButton::_notification(int p_what) { case NOTIFICATION_TRANSLATION_CHANGED: { xl_text = atr(text); _shape(); - - minimum_size_changed(); + update_minimum_size(); update(); } break; + case NOTIFICATION_LAYOUT_DIRECTION_CHANGED: { update(); } break; + case NOTIFICATION_THEME_CHANGED: { _shape(); - minimum_size_changed(); + update_minimum_size(); update(); } break; + case NOTIFICATION_DRAW: { RID ci = get_canvas_item(); Size2 size = get_size(); @@ -230,7 +233,6 @@ void LinkButton::_notification(int p_what) { draw_line(Vector2(0, y), Vector2(width, y), color, text_buf->get_line_underline_thickness()); } } - } break; } } @@ -240,7 +242,7 @@ bool LinkButton::_set(const StringName &p_name, const Variant &p_value) { if (str.begins_with("opentype_features/")) { String name = str.get_slicec('/', 1); int32_t tag = TS->name_to_tag(name); - double value = p_value; + int value = p_value; if (value == -1) { if (opentype_features.has(tag)) { opentype_features.erase(tag); @@ -248,7 +250,7 @@ bool LinkButton::_set(const StringName &p_name, const Variant &p_value) { update(); } } else { - if ((double)opentype_features[tag] != value) { + if (!opentype_features.has(tag) || (int)opentype_features[tag] != value) { opentype_features[tag] = value; _shape(); update(); @@ -280,7 +282,7 @@ bool LinkButton::_get(const StringName &p_name, Variant &r_ret) const { void LinkButton::_get_property_list(List<PropertyInfo> *p_list) const { for (const Variant *ftr = opentype_features.next(nullptr); ftr != nullptr; ftr = opentype_features.next(ftr)) { String name = TS->tag_to_name(*ftr); - p_list->push_back(PropertyInfo(Variant::FLOAT, "opentype_features/" + name)); + p_list->push_back(PropertyInfo(Variant::INT, "opentype_features/" + name)); } p_list->push_back(PropertyInfo(Variant::NIL, "opentype_features/_new", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_EDITOR)); } @@ -308,15 +310,17 @@ void LinkButton::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::STRING, "text"), "set_text", "get_text"); 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"), "set_language", "get_language"); + ADD_PROPERTY(PropertyInfo(Variant::STRING, "language", PROPERTY_HINT_LOCALE_ID, ""), "set_language", "get_language"); ADD_PROPERTY(PropertyInfo(Variant::INT, "underline", PROPERTY_HINT_ENUM, "Always,On Hover,Never"), "set_underline_mode", "get_underline_mode"); ADD_GROUP("Structured Text", "structured_text_"); ADD_PROPERTY(PropertyInfo(Variant::INT, "structured_text_bidi_override", PROPERTY_HINT_ENUM, "Default,URI,File,Email,List,None,Custom"), "set_structured_text_bidi_override", "get_structured_text_bidi_override"); ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "structured_text_bidi_override_options"), "set_structured_text_bidi_override_options", "get_structured_text_bidi_override_options"); } -LinkButton::LinkButton() { +LinkButton::LinkButton(const String &p_text) { text_buf.instantiate(); set_focus_mode(FOCUS_NONE); set_default_cursor_shape(CURSOR_POINTING_HAND); + + set_text(p_text); } diff --git a/scene/gui/link_button.h b/scene/gui/link_button.h index 231543c63c..f996558f32 100644 --- a/scene/gui/link_button.h +++ b/scene/gui/link_button.h @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ @@ -32,7 +32,6 @@ #define LINKBUTTON_H #include "scene/gui/base_button.h" -#include "scene/resources/bit_map.h" #include "scene/resources/text_line.h" class LinkButton : public BaseButton { @@ -91,7 +90,7 @@ public: void set_underline_mode(UnderlineMode p_underline_mode); UnderlineMode get_underline_mode() const; - LinkButton(); + LinkButton(const String &p_text = String()); }; VARIANT_ENUM_CAST(LinkButton::UnderlineMode); diff --git a/scene/gui/margin_container.cpp b/scene/gui/margin_container.cpp index 50b4d192a9..fac37a8634 100644 --- a/scene/gui/margin_container.cpp +++ b/scene/gui/margin_container.cpp @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ @@ -65,6 +65,24 @@ Size2 MarginContainer::get_minimum_size() const { return max; } +Vector<int> MarginContainer::get_allowed_size_flags_horizontal() const { + Vector<int> flags; + flags.append(SIZE_FILL); + flags.append(SIZE_SHRINK_BEGIN); + flags.append(SIZE_SHRINK_CENTER); + flags.append(SIZE_SHRINK_END); + return flags; +} + +Vector<int> MarginContainer::get_allowed_size_flags_vertical() const { + Vector<int> flags; + flags.append(SIZE_FILL); + flags.append(SIZE_SHRINK_BEGIN); + flags.append(SIZE_SHRINK_CENTER); + flags.append(SIZE_SHRINK_END); + return flags; +} + void MarginContainer::_notification(int p_what) { switch (p_what) { case NOTIFICATION_SORT_CHILDREN: { @@ -89,8 +107,9 @@ void MarginContainer::_notification(int p_what) { fit_child_in_rect(c, Rect2(margin_left, margin_top, w, h)); } } break; + case NOTIFICATION_THEME_CHANGED: { - minimum_size_changed(); + update_minimum_size(); } break; } } diff --git a/scene/gui/margin_container.h b/scene/gui/margin_container.h index b782976ada..f8a3c5bb11 100644 --- a/scene/gui/margin_container.h +++ b/scene/gui/margin_container.h @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ @@ -42,6 +42,9 @@ protected: public: virtual Size2 get_minimum_size() const override; + virtual Vector<int> get_allowed_size_flags_horizontal() const override; + virtual Vector<int> get_allowed_size_flags_vertical() const override; + MarginContainer(); }; diff --git a/scene/gui/menu_button.cpp b/scene/gui/menu_button.cpp index ceb2092e3a..7e724e4d71 100644 --- a/scene/gui/menu_button.cpp +++ b/scene/gui/menu_button.cpp @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ @@ -36,7 +36,7 @@ void MenuButton::unhandled_key_input(const Ref<InputEvent> &p_event) { ERR_FAIL_COND(p_event.is_null()); - if (!_is_focus_owner_in_shorcut_context()) { + if (!_is_focus_owner_in_shortcut_context()) { return; } @@ -98,7 +98,13 @@ void MenuButton::pressed() { popup->set_position(gp); popup->set_parent_rect(Rect2(Point2(gp - popup->get_position()), size)); - popup->take_mouse_focus(); + // 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); + } + popup->popup(); } @@ -110,14 +116,6 @@ PopupMenu *MenuButton::get_popup() const { return popup; } -void MenuButton::_set_items(const Array &p_items) { - popup->set("items", p_items); -} - -Array MenuButton::_get_items() const { - return popup->get("items"); -} - void MenuButton::set_switch_on_hover(bool p_enabled) { switch_on_hover = p_enabled; } @@ -126,13 +124,28 @@ bool MenuButton::is_switch_on_hover() { return switch_on_hover; } +void MenuButton::set_item_count(int p_count) { + ERR_FAIL_COND(p_count < 0); + popup->set_item_count(p_count); + notify_property_list_changed(); +} + +int MenuButton::get_item_count() const { + return popup->get_item_count(); +} + void MenuButton::_notification(int p_what) { switch (p_what) { + case NOTIFICATION_LAYOUT_DIRECTION_CHANGED: { + popup->set_layout_direction((Window::LayoutDirection)get_layout_direction()); + } break; + case NOTIFICATION_VISIBILITY_CHANGED: { if (!is_visible_in_tree()) { popup->hide(); } } break; + case NOTIFICATION_INTERNAL_PROCESS: { Vector2i mouse_pos = DisplayServer::get_singleton()->mouse_get_position() - mouse_pos_adjusted; MenuButton *menu_btn_other = Object::cast_to<MenuButton>(get_viewport()->gui_find_control(mouse_pos)); @@ -146,16 +159,66 @@ void MenuButton::_notification(int p_what) { } } +bool MenuButton::_set(const StringName &p_name, const Variant &p_value) { + Vector<String> components = String(p_name).split("/", true, 2); + if (components.size() >= 2 && components[0] == "popup") { + bool valid; + popup->set(String(p_name).trim_prefix("popup/"), p_value, &valid); + return valid; + } + return false; +} + +bool MenuButton::_get(const StringName &p_name, Variant &r_ret) const { + Vector<String> components = String(p_name).split("/", true, 2); + if (components.size() >= 2 && components[0] == "popup") { + bool valid; + r_ret = popup->get(String(p_name).trim_prefix("popup/"), &valid); + return valid; + } + return false; +} + +void MenuButton::_get_property_list(List<PropertyInfo> *p_list) const { + for (int i = 0; i < popup->get_item_count(); i++) { + p_list->push_back(PropertyInfo(Variant::STRING, vformat("popup/item_%d/text", i))); + + PropertyInfo pi = PropertyInfo(Variant::OBJECT, vformat("popup/item_%d/icon", i), PROPERTY_HINT_RESOURCE_TYPE, "Texture2D"); + pi.usage &= ~(popup->get_item_icon(i).is_null() ? PROPERTY_USAGE_STORAGE : 0); + p_list->push_back(pi); + + pi = PropertyInfo(Variant::INT, vformat("popup/item_%d/checkable", i), PROPERTY_HINT_ENUM, "No,As checkbox,As radio button"); + pi.usage &= ~(!popup->is_item_checkable(i) ? PROPERTY_USAGE_STORAGE : 0); + p_list->push_back(pi); + + pi = PropertyInfo(Variant::BOOL, vformat("popup/item_%d/checked", i)); + pi.usage &= ~(!popup->is_item_checked(i) ? PROPERTY_USAGE_STORAGE : 0); + p_list->push_back(pi); + + pi = PropertyInfo(Variant::INT, vformat("popup/item_%d/id", i), PROPERTY_HINT_RANGE, "0,10,1,or_greater"); + p_list->push_back(pi); + + pi = PropertyInfo(Variant::BOOL, vformat("popup/item_%d/disabled", i)); + pi.usage &= ~(!popup->is_item_disabled(i) ? PROPERTY_USAGE_STORAGE : 0); + p_list->push_back(pi); + + pi = PropertyInfo(Variant::BOOL, vformat("popup/item_%d/separator", i)); + pi.usage &= ~(!popup->is_item_separator(i) ? PROPERTY_USAGE_STORAGE : 0); + p_list->push_back(pi); + } +} + void MenuButton::_bind_methods() { ClassDB::bind_method(D_METHOD("get_popup"), &MenuButton::get_popup); - ClassDB::bind_method(D_METHOD("_set_items"), &MenuButton::_set_items); - ClassDB::bind_method(D_METHOD("_get_items"), &MenuButton::_get_items); ClassDB::bind_method(D_METHOD("set_switch_on_hover", "enable"), &MenuButton::set_switch_on_hover); ClassDB::bind_method(D_METHOD("is_switch_on_hover"), &MenuButton::is_switch_on_hover); ClassDB::bind_method(D_METHOD("set_disable_shortcuts", "disabled"), &MenuButton::set_disable_shortcuts); - ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "items", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR | PROPERTY_USAGE_INTERNAL), "_set_items", "_get_items"); + ClassDB::bind_method(D_METHOD("set_item_count", "count"), &MenuButton::set_item_count); + ClassDB::bind_method(D_METHOD("get_item_count"), &MenuButton::get_item_count); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "switch_on_hover"), "set_switch_on_hover", "is_switch_on_hover"); + ADD_ARRAY_COUNT("Items", "item_count", "set_item_count", "get_item_count", "popup/item_"); ADD_SIGNAL(MethodInfo("about_to_popup")); } @@ -164,7 +227,8 @@ void MenuButton::set_disable_shortcuts(bool p_disabled) { disable_shortcuts = p_disabled; } -MenuButton::MenuButton() { +MenuButton::MenuButton(const String &p_text) : + Button(p_text) { set_flat(true); set_toggle_mode(true); set_disable_shortcuts(false); diff --git a/scene/gui/menu_button.h b/scene/gui/menu_button.h index 730495b65d..9cfb780255 100644 --- a/scene/gui/menu_button.h +++ b/scene/gui/menu_button.h @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ @@ -44,15 +44,15 @@ class MenuButton : public Button { Vector2i mouse_pos_adjusted; - Array _get_items() const; - void _set_items(const Array &p_items); - virtual void gui_input(const Ref<InputEvent> &p_event) override; void _popup_visibility_changed(bool p_visible); protected: 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; static void _bind_methods(); virtual void unhandled_key_input(const Ref<InputEvent> &p_event) override; @@ -64,7 +64,10 @@ public: bool is_switch_on_hover(); void set_disable_shortcuts(bool p_disabled); - MenuButton(); + void set_item_count(int p_count); + int get_item_count() const; + + MenuButton(const String &p_text = String()); ~MenuButton(); }; diff --git a/scene/gui/nine_patch_rect.cpp b/scene/gui/nine_patch_rect.cpp index 8bf25ac915..4f34ece86f 100644 --- a/scene/gui/nine_patch_rect.cpp +++ b/scene/gui/nine_patch_rect.cpp @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ @@ -34,18 +34,20 @@ #include "servers/rendering_server.h" void NinePatchRect::_notification(int p_what) { - if (p_what == NOTIFICATION_DRAW) { - if (texture.is_null()) { - return; - } + switch (p_what) { + case NOTIFICATION_DRAW: { + if (texture.is_null()) { + return; + } - Rect2 rect = Rect2(Point2(), get_size()); - Rect2 src_rect = region_rect; + Rect2 rect = Rect2(Point2(), get_size()); + Rect2 src_rect = region_rect; - texture->get_rect_region(rect, src_rect, rect, src_rect); + texture->get_rect_region(rect, src_rect, rect, src_rect); - RID ci = get_canvas_item(); - RS::get_singleton()->canvas_item_add_nine_patch(ci, rect, src_rect, texture->get_rid(), Vector2(margin[SIDE_LEFT], margin[SIDE_TOP]), Vector2(margin[SIDE_RIGHT], margin[SIDE_BOTTOM]), RS::NinePatchAxisMode(axis_h), RS::NinePatchAxisMode(axis_v), draw_center); + RID ci = get_canvas_item(); + RS::get_singleton()->canvas_item_add_nine_patch(ci, rect, src_rect, texture->get_rid(), Vector2(margin[SIDE_LEFT], margin[SIDE_TOP]), Vector2(margin[SIDE_RIGHT], margin[SIDE_BOTTOM]), RS::NinePatchAxisMode(axis_h), RS::NinePatchAxisMode(axis_v), draw_center); + } break; } } @@ -93,11 +95,7 @@ void NinePatchRect::set_texture(const Ref<Texture2D> &p_tex) { } texture = p_tex; update(); - /* - if (texture.is_valid()) - texture->set_flags(texture->get_flags()&(~Texture::FLAG_REPEAT)); //remove repeat from texture, it looks bad in sprites - */ - minimum_size_changed(); + update_minimum_size(); emit_signal(SceneStringNames::get_singleton()->texture_changed); } @@ -109,7 +107,7 @@ void NinePatchRect::set_patch_margin(Side p_side, int p_size) { ERR_FAIL_INDEX((int)p_side, 4); margin[p_side] = p_size; update(); - minimum_size_changed(); + update_minimum_size(); } int NinePatchRect::get_patch_margin(Side p_side) const { diff --git a/scene/gui/nine_patch_rect.h b/scene/gui/nine_patch_rect.h index f9a3f31fe5..23aebbb782 100644 --- a/scene/gui/nine_patch_rect.h +++ b/scene/gui/nine_patch_rect.h @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ diff --git a/scene/gui/option_button.cpp b/scene/gui/option_button.cpp index c00c040048..b3804e73d9 100644 --- a/scene/gui/option_button.cpp +++ b/scene/gui/option_button.cpp @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ @@ -32,6 +32,8 @@ #include "core/string/print_string.h" +static const int NONE_SELECTED = -1; + Size2 OptionButton::get_minimum_size() const { Size2 minsize = Button::get_minimum_size(); @@ -89,8 +91,12 @@ void OptionButton::_notification(int p_what) { } arrow->draw(ci, ofs, clr); } break; + case NOTIFICATION_TRANSLATION_CHANGED: - case NOTIFICATION_LAYOUT_DIRECTION_CHANGED: + case NOTIFICATION_LAYOUT_DIRECTION_CHANGED: { + popup->set_layout_direction((Window::LayoutDirection)get_layout_direction()); + [[fallthrough]]; + } case NOTIFICATION_THEME_CHANGED: { if (has_theme_icon(SNAME("arrow"))) { if (is_layout_rtl()) { @@ -102,6 +108,7 @@ void OptionButton::_notification(int p_what) { } } } break; + case NOTIFICATION_VISIBILITY_CHANGED: { if (!is_visible_in_tree()) { popup->hide(); @@ -110,6 +117,65 @@ void OptionButton::_notification(int p_what) { } } +bool OptionButton::_set(const StringName &p_name, const Variant &p_value) { + Vector<String> components = String(p_name).split("/", true, 2); + if (components.size() >= 2 && components[0] == "popup") { + String property = components[2]; + if (property != "text" && property != "icon" && property != "id" && property != "disabled" && property != "separator") { + return false; + } + + bool valid; + popup->set(String(p_name).trim_prefix("popup/"), p_value, &valid); + + int idx = components[1].get_slice("_", 1).to_int(); + if (idx == current) { + // Force refreshing currently displayed item. + current = NONE_SELECTED; + _select(idx, false); + } + + return valid; + } + return false; +} + +bool OptionButton::_get(const StringName &p_name, Variant &r_ret) const { + Vector<String> components = String(p_name).split("/", true, 2); + if (components.size() >= 2 && components[0] == "popup") { + String property = components[2]; + if (property != "text" && property != "icon" && property != "id" && property != "disabled" && property != "separator") { + return false; + } + + bool valid; + r_ret = popup->get(String(p_name).trim_prefix("popup/"), &valid); + return valid; + } + return false; +} + +void OptionButton::_get_property_list(List<PropertyInfo> *p_list) const { + for (int i = 0; i < popup->get_item_count(); i++) { + p_list->push_back(PropertyInfo(Variant::STRING, vformat("popup/item_%d/text", i))); + + PropertyInfo pi = PropertyInfo(Variant::OBJECT, vformat("popup/item_%d/icon", i), PROPERTY_HINT_RESOURCE_TYPE, "Texture2D"); + pi.usage &= ~(popup->get_item_icon(i).is_null() ? PROPERTY_USAGE_STORAGE : 0); + p_list->push_back(pi); + + pi = PropertyInfo(Variant::INT, vformat("popup/item_%d/id", i), PROPERTY_HINT_RANGE, "0,10,1,or_greater"); + p_list->push_back(pi); + + pi = PropertyInfo(Variant::BOOL, vformat("popup/item_%d/disabled", i)); + pi.usage &= ~(!popup->is_item_disabled(i) ? PROPERTY_USAGE_STORAGE : 0); + p_list->push_back(pi); + + pi = PropertyInfo(Variant::BOOL, vformat("popup/item_%d/separator", i)); + pi.usage &= ~(!popup->is_item_separator(i) ? PROPERTY_USAGE_STORAGE : 0); + p_list->push_back(pi); + } +} + void OptionButton::_focused(int p_which) { emit_signal(SNAME("item_focused"), p_which); } @@ -122,6 +188,17 @@ void OptionButton::pressed() { 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); + } else { + popup->scroll_to_item(current > -1 ? current : 0); + } + } + popup->popup(); } @@ -176,6 +253,10 @@ Ref<Texture2D> OptionButton::get_item_icon(int p_idx) const { } int OptionButton::get_item_id(int p_idx) const { + if (p_idx == NONE_SELECTED) { + return NONE_SELECTED; + } + return popup->get_item_id(p_idx); } @@ -191,6 +272,25 @@ bool OptionButton::is_item_disabled(int p_idx) const { return popup->is_item_disabled(p_idx); } +void OptionButton::set_item_count(int p_count) { + ERR_FAIL_COND(p_count < 0); + + int count_old = get_item_count(); + if (p_count == count_old) { + return; + } + + popup->set_item_count(p_count); + + if (p_count > count_old) { + for (int i = count_old; i < p_count; i++) { + popup->set_item_as_radio_checkable(i, true); + } + } + + notify_property_list_changed(); +} + int OptionButton::get_item_count() const { return popup->get_item_count(); } @@ -202,26 +302,33 @@ void OptionButton::add_separator() { void OptionButton::clear() { popup->clear(); set_text(""); - current = -1; + current = NONE_SELECTED; } void OptionButton::_select(int p_which, bool p_emit) { - if (p_which < 0) { - return; - } if (p_which == current) { return; } - ERR_FAIL_INDEX(p_which, popup->get_item_count()); + if (p_which == NONE_SELECTED) { + for (int i = 0; i < popup->get_item_count(); i++) { + popup->set_item_checked(i, false); + } - for (int i = 0; i < popup->get_item_count(); i++) { - popup->set_item_checked(i, i == p_which); - } + current = NONE_SELECTED; + set_text(""); + set_icon(nullptr); + } else { + ERR_FAIL_INDEX(p_which, popup->get_item_count()); + + for (int i = 0; i < popup->get_item_count(); i++) { + popup->set_item_checked(i, i == p_which); + } - current = p_which; - set_text(popup->get_item_text(current)); - set_icon(popup->get_item_icon(current)); + current = p_which; + set_text(popup->get_item_text(current)); + set_icon(popup->get_item_icon(current)); + } if (is_inside_tree() && p_emit) { emit_signal(SNAME("item_selected"), current); @@ -229,7 +336,7 @@ void OptionButton::_select(int p_which, bool p_emit) { } void OptionButton::_select_int(int p_which) { - if (p_which < 0 || p_which >= popup->get_item_count()) { + if (p_which < NONE_SELECTED || p_which >= popup->get_item_count()) { return; } _select(p_which, false); @@ -244,10 +351,6 @@ int OptionButton::get_selected() const { } int OptionButton::get_selected_id() const { - int idx = get_selected(); - if (idx < 0) { - return 0; - } return get_item_id(current); } @@ -261,44 +364,15 @@ Variant OptionButton::get_selected_metadata() const { void OptionButton::remove_item(int p_idx) { popup->remove_item(p_idx); + if (current == p_idx) { + _select(NONE_SELECTED); + } } PopupMenu *OptionButton::get_popup() const { return popup; } -Array OptionButton::_get_items() const { - Array items; - for (int i = 0; i < get_item_count(); i++) { - items.push_back(get_item_text(i)); - items.push_back(get_item_icon(i)); - items.push_back(is_item_disabled(i)); - items.push_back(get_item_id(i)); - items.push_back(get_item_metadata(i)); - } - - return items; -} - -void OptionButton::_set_items(const Array &p_items) { - ERR_FAIL_COND(p_items.size() % 5); - clear(); - - for (int i = 0; i < p_items.size(); i += 5) { - String text = p_items[i + 0]; - Ref<Texture2D> icon = p_items[i + 1]; - bool disabled = p_items[i + 2]; - int id = p_items[i + 3]; - Variant meta = p_items[i + 4]; - - int idx = get_item_count(); - add_item(text, id); - set_item_icon(idx, icon); - set_item_disabled(idx, disabled); - set_item_metadata(idx, meta); - } -} - void OptionButton::get_translatable_strings(List<String> *p_strings) const { popup->get_translatable_strings(p_strings); } @@ -317,7 +391,6 @@ void OptionButton::_bind_methods() { ClassDB::bind_method(D_METHOD("get_item_index", "id"), &OptionButton::get_item_index); ClassDB::bind_method(D_METHOD("get_item_metadata", "idx"), &OptionButton::get_item_metadata); ClassDB::bind_method(D_METHOD("is_item_disabled", "idx"), &OptionButton::is_item_disabled); - ClassDB::bind_method(D_METHOD("get_item_count"), &OptionButton::get_item_count); ClassDB::bind_method(D_METHOD("add_separator"), &OptionButton::add_separator); ClassDB::bind_method(D_METHOD("clear"), &OptionButton::clear); ClassDB::bind_method(D_METHOD("select", "idx"), &OptionButton::select); @@ -329,19 +402,20 @@ void OptionButton::_bind_methods() { ClassDB::bind_method(D_METHOD("get_popup"), &OptionButton::get_popup); - ClassDB::bind_method(D_METHOD("_set_items"), &OptionButton::_set_items); - ClassDB::bind_method(D_METHOD("_get_items"), &OptionButton::_get_items); + ClassDB::bind_method(D_METHOD("set_item_count", "count"), &OptionButton::set_item_count); + ClassDB::bind_method(D_METHOD("get_item_count"), &OptionButton::get_item_count); - ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "items", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR | PROPERTY_USAGE_INTERNAL), "_set_items", "_get_items"); - // "selected" property must come after "items", otherwise GH-10213 occurs. + // "selected" property must come after "item_count", otherwise GH-10213 occurs. + ADD_ARRAY_COUNT("Items", "item_count", "set_item_count", "get_item_count", "popup/item_"); ADD_PROPERTY(PropertyInfo(Variant::INT, "selected"), "_select_int", "get_selected"); ADD_SIGNAL(MethodInfo("item_selected", PropertyInfo(Variant::INT, "index"))); ADD_SIGNAL(MethodInfo("item_focused", PropertyInfo(Variant::INT, "index"))); } -OptionButton::OptionButton() { +OptionButton::OptionButton(const String &p_text) : + Button(p_text) { set_toggle_mode(true); - set_text_align(ALIGN_LEFT); + 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()); diff --git a/scene/gui/option_button.h b/scene/gui/option_button.h index 953337ecce..5352fe18a6 100644 --- a/scene/gui/option_button.h +++ b/scene/gui/option_button.h @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ @@ -45,14 +45,14 @@ class OptionButton : public Button { void _select(int p_which, bool p_emit = false); void _select_int(int p_which); - Array _get_items() const; - void _set_items(const Array &p_items); - virtual void pressed() override; protected: Size2 get_minimum_size() const 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; static void _bind_methods(); public: @@ -76,6 +76,7 @@ public: Variant get_item_metadata(int p_idx) const; bool is_item_disabled(int p_idx) const; + void set_item_count(int p_count); int get_item_count() const; void add_separator(); @@ -93,7 +94,7 @@ public: virtual void get_translatable_strings(List<String> *p_strings) const override; - OptionButton(); + OptionButton(const String &p_text = String()); ~OptionButton(); }; diff --git a/scene/gui/panel.cpp b/scene/gui/panel.cpp index e8e7e3d997..1ac6cf57ab 100644 --- a/scene/gui/panel.cpp +++ b/scene/gui/panel.cpp @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ @@ -30,35 +30,16 @@ #include "panel.h" -#include "core/string/print_string.h" - void Panel::_notification(int p_what) { - if (p_what == NOTIFICATION_DRAW) { - RID ci = get_canvas_item(); - Ref<StyleBox> style = mode == MODE_BACKGROUND ? get_theme_stylebox(SNAME("panel")) : get_theme_stylebox(SNAME("panel_fg")); - style->draw(ci, Rect2(Point2(), get_size())); + 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())); + } break; } } -void Panel::set_mode(Mode p_mode) { - mode = p_mode; - update(); -} - -Panel::Mode Panel::get_mode() const { - return mode; -} - -void Panel::_bind_methods() { - ClassDB::bind_method(D_METHOD("set_mode", "mode"), &Panel::set_mode); - ClassDB::bind_method(D_METHOD("get_mode"), &Panel::get_mode); - - ADD_PROPERTY(PropertyInfo(Variant::INT, "mode", PROPERTY_HINT_ENUM, "Background,Foreground"), "set_mode", "get_mode"); - - BIND_ENUM_CONSTANT(MODE_BACKGROUND); - BIND_ENUM_CONSTANT(MODE_FOREGROUND); -} - Panel::Panel() { // Has visible stylebox, so stop by default. set_mouse_filter(MOUSE_FILTER_STOP); diff --git a/scene/gui/panel.h b/scene/gui/panel.h index 84fd6aaead..5d2e912680 100644 --- a/scene/gui/panel.h +++ b/scene/gui/panel.h @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ @@ -36,26 +36,11 @@ class Panel : public Control { GDCLASS(Panel, Control); -public: - enum Mode { - MODE_BACKGROUND, - MODE_FOREGROUND, - }; - -private: - Mode mode = MODE_BACKGROUND; - protected: void _notification(int p_what); - static void _bind_methods(); public: - void set_mode(Mode p_mode); - Mode get_mode() const; - Panel(); }; -VARIANT_ENUM_CAST(Panel::Mode) - #endif // PANEL_H diff --git a/scene/gui/panel_container.cpp b/scene/gui/panel_container.cpp index d910e1e882..fe01712a89 100644 --- a/scene/gui/panel_container.cpp +++ b/scene/gui/panel_container.cpp @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ @@ -60,47 +60,67 @@ Size2 PanelContainer::get_minimum_size() const { return ms; } -void PanelContainer::_notification(int p_what) { - if (p_what == 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")); - } +Vector<int> PanelContainer::get_allowed_size_flags_horizontal() const { + Vector<int> flags; + flags.append(SIZE_FILL); + flags.append(SIZE_SHRINK_BEGIN); + flags.append(SIZE_SHRINK_CENTER); + flags.append(SIZE_SHRINK_END); + return flags; +} - style->draw(ci, Rect2(Point2(), get_size())); - } +Vector<int> PanelContainer::get_allowed_size_flags_vertical() const { + Vector<int> flags; + flags.append(SIZE_FILL); + flags.append(SIZE_SHRINK_BEGIN); + flags.append(SIZE_SHRINK_CENTER); + flags.append(SIZE_SHRINK_END); + return flags; +} - if (p_what == NOTIFICATION_SORT_CHILDREN) { - Ref<StyleBox> style; +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")); - } + 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(); - } + style->draw(ci, Rect2(Point2(), get_size())); + } break; - for (int i = 0; i < get_child_count(); i++) { - Control *c = Object::cast_to<Control>(get_child(i)); - if (!c || !c->is_visible_in_tree()) { - continue; + 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")); } - if (c->is_set_as_top_level()) { - continue; + + Size2 size = get_size(); + Point2 ofs; + if (style.is_valid()) { + size -= style->get_minimum_size(); + ofs += style->get_offset(); } - fit_child_in_rect(c, Rect2(ofs, size)); - } + for (int i = 0; i < get_child_count(); i++) { + Control *c = Object::cast_to<Control>(get_child(i)); + if (!c || !c->is_visible_in_tree()) { + continue; + } + if (c->is_set_as_top_level()) { + continue; + } + + fit_child_in_rect(c, Rect2(ofs, size)); + } + } break; } } diff --git a/scene/gui/panel_container.h b/scene/gui/panel_container.h index f27ca7fad7..8f07ce38eb 100644 --- a/scene/gui/panel_container.h +++ b/scene/gui/panel_container.h @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ @@ -42,6 +42,9 @@ protected: public: virtual Size2 get_minimum_size() const override; + virtual Vector<int> get_allowed_size_flags_horizontal() const override; + virtual Vector<int> get_allowed_size_flags_vertical() const override; + PanelContainer(); }; diff --git a/scene/gui/popup.cpp b/scene/gui/popup.cpp index be05fd5a60..b9e3e7814e 100644 --- a/scene/gui/popup.cpp +++ b/scene/gui/popup.cpp @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ @@ -36,32 +36,36 @@ void Popup::_input_from_window(const Ref<InputEvent> &p_event) { Ref<InputEventKey> key = p_event; - if (key.is_valid() && key->is_pressed() && key->get_keycode() == KEY_ESCAPE) { + if (key.is_valid() && key->is_pressed() && key->get_keycode() == Key::ESCAPE) { _close_pressed(); } } void Popup::_initialize_visible_parents() { - visible_parents.clear(); - - Window *parent_window = this; - while (parent_window) { - parent_window = parent_window->get_parent_visible_window(); - if (parent_window) { - visible_parents.push_back(parent_window); - parent_window->connect("focus_entered", callable_mp(this, &Popup::_parent_focused)); - parent_window->connect("tree_exited", callable_mp(this, &Popup::_deinitialize_visible_parents)); + if (is_embedded()) { + visible_parents.clear(); + + Window *parent_window = this; + while (parent_window) { + parent_window = parent_window->get_parent_visible_window(); + if (parent_window) { + visible_parents.push_back(parent_window); + parent_window->connect("focus_entered", callable_mp(this, &Popup::_parent_focused)); + parent_window->connect("tree_exited", callable_mp(this, &Popup::_deinitialize_visible_parents)); + } } } } void Popup::_deinitialize_visible_parents() { - for (uint32_t i = 0; i < visible_parents.size(); ++i) { - visible_parents[i]->disconnect("focus_entered", callable_mp(this, &Popup::_parent_focused)); - visible_parents[i]->disconnect("tree_exited", callable_mp(this, &Popup::_deinitialize_visible_parents)); - } + if (is_embedded()) { + for (uint32_t i = 0; i < visible_parents.size(); ++i) { + visible_parents[i]->disconnect("focus_entered", callable_mp(this, &Popup::_parent_focused)); + visible_parents[i]->disconnect("tree_exited", callable_mp(this, &Popup::_deinitialize_visible_parents)); + } - visible_parents.clear(); + visible_parents.clear(); + } } void Popup::_notification(int p_what) { @@ -74,19 +78,19 @@ void Popup::_notification(int p_what) { emit_signal(SNAME("popup_hide")); popped_up = false; } - } break; + case NOTIFICATION_WM_WINDOW_FOCUS_IN: { if (has_focus()) { popped_up = true; } } break; + case NOTIFICATION_EXIT_TREE: { _deinitialize_visible_parents(); } break; - case NOTIFICATION_WM_CLOSE_REQUEST: { - _close_pressed(); - } break; + + case NOTIFICATION_WM_CLOSE_REQUEST: case NOTIFICATION_APPLICATION_FOCUS_OUT: { _close_pressed(); } break; @@ -94,7 +98,7 @@ void Popup::_notification(int p_what) { } void Popup::_parent_focused() { - if (popped_up && close_on_parent_focus) { + if (popped_up && get_flag(FLAG_POPUP)) { _close_pressed(); } } @@ -105,27 +109,9 @@ void Popup::_close_pressed() { _deinitialize_visible_parents(); call_deferred(SNAME("hide")); - - emit_signal(SNAME("cancelled")); -} - -void Popup::set_as_minsize() { - set_size(get_contents_minimum_size()); -} - -void Popup::set_close_on_parent_focus(bool p_close) { - close_on_parent_focus = p_close; -} - -bool Popup::get_close_on_parent_focus() { - return close_on_parent_focus; } void Popup::_bind_methods() { - ClassDB::bind_method(D_METHOD("set_close_on_parent_focus", "close"), &Popup::set_close_on_parent_focus); - ClassDB::bind_method(D_METHOD("get_close_on_parent_focus"), &Popup::get_close_on_parent_focus); - ADD_PROPERTY(PropertyInfo(Variant::BOOL, "close_on_parent_focus"), "set_close_on_parent_focus", "get_close_on_parent_focus"); - ADD_SIGNAL(MethodInfo("popup_hide")); } @@ -186,6 +172,7 @@ Popup::Popup() { set_transient(true); set_flag(FLAG_BORDERLESS, true); set_flag(FLAG_RESIZE_DISABLED, true); + set_flag(FLAG_POPUP, true); connect("window_input", callable_mp(this, &Popup::_input_from_window)); } @@ -243,13 +230,20 @@ void PopupPanel::_update_child_rects() { } void PopupPanel::_notification(int p_what) { - if (p_what == NOTIFICATION_THEME_CHANGED) { - panel->add_theme_style_override("panel", get_theme_stylebox(SNAME("panel"), get_class_name())); - } else if (p_what == NOTIFICATION_READY || p_what == NOTIFICATION_ENTER_TREE) { - panel->add_theme_style_override("panel", get_theme_stylebox(SNAME("panel"), get_class_name())); - _update_child_rects(); - } else if (p_what == NOTIFICATION_WM_SIZE_CHANGED) { - _update_child_rects(); + switch (p_what) { + 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())); + _update_child_rects(); + } break; + + case NOTIFICATION_WM_SIZE_CHANGED: { + _update_child_rects(); + } break; } } diff --git a/scene/gui/popup.h b/scene/gui/popup.h index 8458a75eef..c45f4ddc24 100644 --- a/scene/gui/popup.h +++ b/scene/gui/popup.h @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ @@ -42,7 +42,6 @@ class Popup : public Window { LocalVector<Window *> visible_parents; bool popped_up = false; - bool close_on_parent_focus = true; void _input_from_window(const Ref<InputEvent> &p_event); @@ -59,11 +58,6 @@ protected: static void _bind_methods(); public: - void set_as_minsize(); - - void set_close_on_parent_focus(bool p_close); - bool get_close_on_parent_focus(); - Popup(); ~Popup(); }; diff --git a/scene/gui/popup_menu.cpp b/scene/gui/popup_menu.cpp index c0a559e624..4220066b20 100644 --- a/scene/gui/popup_menu.cpp +++ b/scene/gui/popup_menu.cpp @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ @@ -30,6 +30,7 @@ #include "popup_menu.h" +#include "core/config/project_settings.h" #include "core/input/input.h" #include "core/os/keyboard.h" #include "core/os/os.h" @@ -39,7 +40,7 @@ String PopupMenu::_get_accel_text(const Item &p_item) const { if (p_item.shortcut.is_valid()) { return p_item.shortcut->get_as_text(); - } else if (p_item.accel) { + } else if (p_item.accel != Key::NONE) { return keycode_get_string(p_item.accel); } return String(); @@ -50,7 +51,7 @@ Size2 PopupMenu::_get_contents_minimum_size() const { int hseparation = get_theme_constant(SNAME("hseparation")); Size2 minsize = get_theme_stylebox(SNAME("panel"))->get_minimum_size(); // Accounts for margin in the margin container - minsize.x += scroll_container->get_v_scrollbar()->get_size().width * 2; // Adds a buffer so that the scrollbar does not render over the top of content + 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; @@ -67,20 +68,20 @@ Size2 PopupMenu::_get_contents_minimum_size() const { size.width += items[i].h_ofs; - if (items[i].checkable_type) { + if (items[i].checkable_type && !items[i].separator) { has_check = true; } size.width += items[i].text_buf->get_size().x; size.height += vseparation; - if (items[i].accel || (items[i].shortcut.is_valid() && items[i].shortcut->has_valid_event())) { + if (items[i].accel != Key::NONE || (items[i].shortcut.is_valid() && items[i].shortcut->has_valid_event())) { int accel_w = hseparation * 2; accel_w += items[i].accel_text_buf->get_size().x; accel_max_w = MAX(accel_w, accel_max_w); } - if (items[i].submenu != "") { + if (!items[i].submenu.is_empty()) { size.width += get_theme_icon(SNAME("submenu"))->get_width(); } @@ -108,10 +109,9 @@ Size2 PopupMenu::_get_contents_minimum_size() const { int PopupMenu::_get_item_height(int p_item) const { ERR_FAIL_INDEX_V(p_item, items.size(), 0); - ERR_FAIL_COND_V(p_item < 0, 0); int icon_height = items[p_item].get_icon_size().height; - if (items[p_item].checkable_type) { + 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())); } @@ -141,23 +141,6 @@ int PopupMenu::_get_items_total_height() const { return items_total_height - vsep; } -void PopupMenu::_scroll_to_item(int p_item) { - ERR_FAIL_INDEX(p_item, items.size()); - ERR_FAIL_COND(p_item < 0); - - // Scroll item into view (upwards) - if (items[p_item]._ofs_cache < -control->get_position().y) { - int amnt_over = items[p_item]._ofs_cache + control->get_position().y; - scroll_container->set_v_scroll(scroll_container->get_v_scroll() + amnt_over); - } - - // Scroll item into view (downwards) - if (items[p_item]._ofs_cache + items[p_item]._height_cache > -control->get_position().y + scroll_container->get_size().height) { - int amnt_over = items[p_item]._ofs_cache + items[p_item]._height_cache + control->get_position().y - scroll_container->get_size().height; - scroll_container->set_v_scroll(scroll_container->get_v_scroll() + amnt_over); - } -} - int PopupMenu::_get_mouse_over(const Point2 &p_over) const { if (p_over.x < 0 || p_over.x >= get_size().width) { return -1; @@ -192,7 +175,7 @@ void PopupMenu::_activate_submenu(int p_over) { Popup *submenu_popup = Object::cast_to<Popup>(n); ERR_FAIL_COND_MSG(!submenu_popup, "Item subnode is not a Popup: " + items[p_over].submenu + "."); if (submenu_popup->is_visible()) { - return; //already visible! + return; // Already visible. } Ref<StyleBox> style = get_theme_stylebox(SNAME("panel")); @@ -203,44 +186,58 @@ void PopupMenu::_activate_submenu(int p_over) { float scroll_offset = control->get_position().y; - Point2 submenu_pos; + submenu_popup->reset_size(); // Shrink the popup size to its contents. Size2 submenu_size = submenu_popup->get_size(); + + Point2 submenu_pos; if (control->is_layout_rtl()) { submenu_pos = this_pos + Point2(-submenu_size.width, items[p_over]._ofs_cache + scroll_offset); } else { submenu_pos = this_pos + Point2(this_rect.size.width, items[p_over]._ofs_cache + scroll_offset); } - // Fix pos if going outside parent rect + // Fix pos if going outside parent rect. if (submenu_pos.x < get_parent_rect().position.x) { submenu_pos.x = this_pos.x + submenu_size.width; } - if (submenu_pos.x + submenu_size.width > get_parent_rect().size.width) { + if (submenu_pos.x + submenu_size.width > get_parent_rect().position.x + get_parent_rect().size.width) { submenu_pos.x = this_pos.x - submenu_size.width; } - submenu_popup->set_close_on_parent_focus(false); submenu_popup->set_position(submenu_pos); - submenu_popup->set_as_minsize(); // Shrink the popup size to its contents. - submenu_popup->popup(); - // Set autohide areas PopupMenu *submenu_pum = Object::cast_to<PopupMenu>(submenu_popup); - if (submenu_pum) { - submenu_pum->take_mouse_focus(); - // Make the position of the parent popup relative to submenu popup - this_rect.position = this_rect.position - submenu_pum->get_position(); - - // 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)); - - // 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; - submenu_pum->add_autohide_area(Rect2(this_rect.position.x, this_rect.position.y + from, this_rect.size.x, this_rect.size.y - from)); - } + if (!submenu_pum) { + submenu_popup->popup(); + return; + } + + // If not triggered by the mouse, start the popup with its first item selected. + if (submenu_pum->get_item_count() > 0 && Input::get_singleton()->is_action_just_pressed("ui_accept")) { + submenu_pum->set_current_index(0); + } + + submenu_pum->popup(); + + // 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.size.y = items[p_over]._height_cache; + DisplayServer::get_singleton()->window_set_popup_safe_rect(submenu_popup->get_window_id(), safe_area); + + // Make the position of the parent popup relative to submenu popup. + this_rect.position = this_rect.position - submenu_pum->get_position(); + + // 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)); + + // 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; + submenu_pum->add_autohide_area(Rect2(this_rect.position.x, this_rect.position.y + from, this_rect.size.x, this_rect.size.y - from)); } } @@ -266,7 +263,7 @@ void PopupMenu::gui_input(const Ref<InputEvent> &p_event) { if (!items[i].separator && !items[i].disabled) { mouse_over = i; emit_signal(SNAME("id_focused"), i); - _scroll_to_item(i); + scroll_to_item(i); control->update(); set_input_as_handled(); match_found = true; @@ -280,7 +277,7 @@ void PopupMenu::gui_input(const Ref<InputEvent> &p_event) { if (!items[i].separator && !items[i].disabled) { mouse_over = i; emit_signal(SNAME("id_focused"), i); - _scroll_to_item(i); + scroll_to_item(i); control->update(); set_input_as_handled(); break; @@ -298,7 +295,7 @@ void PopupMenu::gui_input(const Ref<InputEvent> &p_event) { if (!items[i].separator && !items[i].disabled) { mouse_over = i; emit_signal(SNAME("id_focused"), i); - _scroll_to_item(i); + scroll_to_item(i); control->update(); set_input_as_handled(); match_found = true; @@ -312,7 +309,7 @@ void PopupMenu::gui_input(const Ref<InputEvent> &p_event) { if (!items[i].separator && !items[i].disabled) { mouse_over = i; emit_signal(SNAME("id_focused"), i); - _scroll_to_item(i); + scroll_to_item(i); control->update(); set_input_as_handled(); break; @@ -326,13 +323,13 @@ void PopupMenu::gui_input(const Ref<InputEvent> &p_event) { 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 != "" && submenu_over != mouse_over) { + 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); 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 != "" && submenu_over != mouse_over) { + if (!items[mouse_over].submenu.is_empty() && submenu_over != mouse_over) { _activate_submenu(mouse_over); } else { activate_item(mouse_over); @@ -343,11 +340,11 @@ void PopupMenu::gui_input(const Ref<InputEvent> &p_event) { // Make an area which does not include v scrollbar, so that items are not activated when dragging scrollbar. Rect2 item_clickable_area = scroll_container->get_rect(); - if (scroll_container->get_v_scrollbar()->is_visible_in_tree()) { + if (scroll_container->get_v_scroll_bar()->is_visible_in_tree()) { if (is_layout_rtl()) { - item_clickable_area.position.x += scroll_container->get_v_scrollbar()->get_size().width; + item_clickable_area.position.x += scroll_container->get_v_scroll_bar()->get_size().width; } else { - item_clickable_area.size.width -= scroll_container->get_v_scrollbar()->get_size().width; + item_clickable_area.size.width -= scroll_container->get_v_scroll_bar()->get_size().width; } } @@ -358,20 +355,20 @@ void PopupMenu::gui_input(const Ref<InputEvent> &p_event) { return; } - int button_idx = b->get_button_index(); + MouseButton button_idx = b->get_button_index(); if (!b->is_pressed()) { // Activate the item on release of either the left mouse button or // any mouse button held down when the popup was opened. // This allows for opening the popup and triggering an action in a single mouse click. - if (button_idx == MOUSE_BUTTON_LEFT || (initial_button_mask & (1 << (button_idx - 1)))) { + if (button_idx == MouseButton::LEFT || (initial_button_mask & mouse_button_to_mask(button_idx)) != MouseButton::NONE) { bool was_during_grabbed_click = during_grabbed_click; during_grabbed_click = false; - initial_button_mask = 0; + initial_button_mask = MouseButton::NONE; // Disable clicks under a time threshold to avoid selection right when opening the popup. uint64_t now = OS::get_singleton()->get_ticks_msec(); uint64_t diff = now - popup_time_msec; - if (diff < 100) { + if (diff < 150) { return; } @@ -387,7 +384,7 @@ void PopupMenu::gui_input(const Ref<InputEvent> &p_event) { return; } - if (items[over].submenu != "") { + if (!items[over].submenu.is_empty()) { _activate_submenu(over); return; } @@ -419,7 +416,7 @@ void PopupMenu::gui_input(const Ref<InputEvent> &p_event) { return; } - if (items[over].submenu != "" && submenu_over != over) { + if (!items[over].submenu.is_empty() && submenu_over != over) { submenu_over = over; submenu_timer->start(); } @@ -462,7 +459,7 @@ void PopupMenu::gui_input(const Ref<InputEvent> &p_event) { if (items[i].text.findn(search_string) == 0) { mouse_over = i; emit_signal(SNAME("id_focused"), i); - _scroll_to_item(i); + scroll_to_item(i); control->update(); set_input_as_handled(); break; @@ -486,7 +483,7 @@ void PopupMenu::_draw_items() { 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) + // 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")) }; Ref<Texture2D> submenu; @@ -508,13 +505,17 @@ void PopupMenu::_draw_items() { 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_scrollbar()->is_visible_in_tree() ? scroll_container->get_v_scrollbar()->get_size().width : 0; + 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; // Find the widest icon and whether any items have a checkbox, and store the offsets for each. float icon_ofs = 0.0; bool has_check = false; for (int i = 0; i < items.size(); i++) { + if (items[i].separator) { + continue; + } + icon_ofs = MAX(items[i].get_icon_size().width, icon_ofs); if (items[i].checkable_type) { @@ -558,29 +559,33 @@ void PopupMenu::_draw_items() { if (items[i].separator) { int sep_h = separator->get_center_size().height + separator->get_minimum_size().height; int sep_ofs = Math::floor((h - sep_h) / 2.0); - if (text != String()) { - int text_size = items[i].text_buf->get_size().width; - int text_center = display_width / 2; - int text_left = text_center - text_size / 2; - int text_right = text_center + text_size / 2; - if (text_left > item_ofs.x) { - labeled_separator_left->draw(ci, Rect2(item_ofs + Point2(0, sep_ofs), Size2(MAX(0, text_left - item_ofs.x), sep_h))); + if (!text.is_empty() || !items[i].icon.is_null()) { + int content_size = items[i].text_buf->get_size().width; + if (!items[i].icon.is_null()) { + content_size += icon_size.width + hseparation; } - if (text_right < display_width) { - labeled_separator_right->draw(ci, Rect2(Point2(text_right, item_ofs.y + sep_ofs), Size2(MAX(0, display_width - text_right), sep_h))); + + 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) { + 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) { + labeled_separator_right->draw(ci, Rect2(Point2(content_right, item_ofs.y + sep_ofs), Size2(MAX(0, display_width - content_right), sep_h))); } } else { separator->draw(ci, Rect2(item_ofs + Point2(0, sep_ofs), Size2(display_width, sep_h))); } } - Color icon_color(1, 1, 1, items[i].disabled ? 0.5 : 1); + 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; // Checkboxes - if (items[i].checkable_type) { + 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(); 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); @@ -589,17 +594,29 @@ void PopupMenu::_draw_items() { } } + int separator_ofs = (display_width - items[i].text_buf->get_size().width) / 2; + // Icon if (!items[i].icon.is_null()) { - if (rtl) { - items[i].icon->draw(ci, Size2(control->get_size().width - item_ofs.x - check_ofs - icon_size.width, item_ofs.y) + Point2(0, Math::floor((h - icon_size.height) / 2.0)), icon_color); + if (items[i].separator) { + separator_ofs -= (icon_size.width + hseparation) / 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); + } else { + items[i].icon->draw(ci, item_ofs + Size2(separator_ofs, 0) + Point2(0, Math::floor((h - icon_size.height) / 2.0)), icon_color); + } } else { - items[i].icon->draw(ci, item_ofs + Size2(check_ofs, 0) + Point2(0, Math::floor((h - icon_size.height) / 2.0)), icon_color); + if (rtl) { + items[i].icon->draw(ci, Size2(control->get_size().width - item_ofs.x - check_ofs - icon_size.width, item_ofs.y) + Point2(0, Math::floor((h - icon_size.height) / 2.0)), icon_color); + } else { + items[i].icon->draw(ci, item_ofs + Size2(check_ofs, 0) + Point2(0, Math::floor((h - icon_size.height) / 2.0)), icon_color); + } } } - // Submenu arrow on right hand side - if (items[i].submenu != "") { + // 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); } else { @@ -611,9 +628,12 @@ void PopupMenu::_draw_items() { Color font_outline_color = get_theme_color(SNAME("font_outline_color")); int outline_size = get_theme_constant(SNAME("outline_size")); if (items[i].separator) { - if (text != String()) { - int center = (display_width - items[i].text_buf->get_size().width) / 2; - Vector2 text_pos = Point2(center, item_ofs.y + Math::floor((h - items[i].text_buf->get_size().y) / 2.0)); + 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; + } + if (outline_size > 0 && font_outline_color.a > 0) { items[i].text_buf->draw_outline(ci, text_pos, outline_size, font_outline_color); } @@ -637,7 +657,7 @@ void PopupMenu::_draw_items() { } // Accelerator / Shortcut - if (items[i].accel || (items[i].shortcut.is_valid() && items[i].shortcut->has_valid_event())) { + 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; } else { @@ -650,7 +670,7 @@ void PopupMenu::_draw_items() { items[i].accel_text_buf->draw(ci, text_pos, i == mouse_over ? font_hover_color : font_accelerator_color); } - // Cache the item vertical offset from the first item and the height + // Cache the item vertical offset from the first item and the height. items.write[i]._ofs_cache = ofs.y; items.write[i]._height_cache = h; @@ -701,7 +721,7 @@ void PopupMenu::_shape_item(int p_item) { } else { items.write[p_item].text_buf->set_direction((TextServer::Direction)items[p_item].text_direction); } - items.write[p_item].text_buf->add_string(items.write[p_item].xl_text, font, font_size, items[p_item].opentype_features, (items[p_item].language != "") ? items[p_item].language : TranslationServer::get_singleton()->get_tool_locale()); + items.write[p_item].text_buf->add_string(items.write[p_item].xl_text, font, font_size, items[p_item].opentype_features, !items[p_item].language.is_empty() ? items[p_item].language : TranslationServer::get_singleton()->get_tool_locale()); items.write[p_item].accel_text_buf->clear(); items.write[p_item].accel_text_buf->set_direction(is_layout_rtl() ? TextServer::DIRECTION_RTL : TextServer::DIRECTION_LTR); @@ -715,11 +735,12 @@ void PopupMenu::_notification(int p_what) { case NOTIFICATION_ENTER_TREE: { PopupMenu *pm = Object::cast_to<PopupMenu>(get_parent()); if (pm) { - // Inherit submenu's popup delay time from parent menu + // Inherit submenu's popup delay time from parent menu. float pm_delay = pm->get_submenu_popup_delay(); set_submenu_popup_delay(pm_delay); } } break; + case NOTIFICATION_THEME_CHANGED: case Control::NOTIFICATION_LAYOUT_DIRECTION_CHANGED: case NOTIFICATION_TRANSLATION_CHANGED: { @@ -732,23 +753,25 @@ void PopupMenu::_notification(int p_what) { child_controls_changed(); control->update(); } break; + case NOTIFICATION_WM_MOUSE_ENTER: { grab_focus(); } break; + case NOTIFICATION_WM_MOUSE_EXIT: { - if (mouse_over >= 0 && (items[mouse_over].submenu == "" || submenu_over != -1)) { + if (mouse_over >= 0 && (items[mouse_over].submenu.is_empty() || submenu_over != -1)) { mouse_over = -1; control->update(); } } break; + case NOTIFICATION_POST_POPUP: { initial_button_mask = Input::get_singleton()->get_mouse_button_mask(); during_grabbed_click = (bool)initial_button_mask; } break; - case NOTIFICATION_WM_SIZE_CHANGED: { - } break; + case NOTIFICATION_INTERNAL_PROCESS: { - //only used when using operating system windows + // Only used when using operating system windows. if (!is_embedded() && autohide_areas.size()) { Point2 mouse_pos = DisplayServer::get_singleton()->mouse_get_position(); mouse_pos -= get_position(); @@ -761,6 +784,7 @@ void PopupMenu::_notification(int p_what) { } } } break; + case NOTIFICATION_VISIBILITY_CHANGED: { if (!is_visible()) { if (mouse_over >= 0) { @@ -769,7 +793,7 @@ void PopupMenu::_notification(int p_what) { } for (int i = 0; i < items.size(); i++) { - if (items[i].submenu == "") { + if (items[i].submenu.is_empty()) { continue; } @@ -813,16 +837,17 @@ void PopupMenu::_notification(int p_what) { item.id = p_id == -1 ? items.size() : p_id; \ item.accel = p_accel; -void PopupMenu::add_item(const String &p_label, int p_id, uint32_t p_accel) { +void PopupMenu::add_item(const String &p_label, int p_id, Key p_accel) { Item item; ITEM_SETUP_WITH_ACCEL(p_label, p_id, p_accel); items.push_back(item); _shape_item(items.size() - 1); control->update(); child_controls_changed(); + notify_property_list_changed(); } -void PopupMenu::add_icon_item(const Ref<Texture2D> &p_icon, const String &p_label, int p_id, uint32_t p_accel) { +void PopupMenu::add_icon_item(const Ref<Texture2D> &p_icon, const String &p_label, int p_id, Key p_accel) { Item item; ITEM_SETUP_WITH_ACCEL(p_label, p_id, p_accel); item.icon = p_icon; @@ -830,9 +855,10 @@ void PopupMenu::add_icon_item(const Ref<Texture2D> &p_icon, const String &p_labe _shape_item(items.size() - 1); control->update(); child_controls_changed(); + notify_property_list_changed(); } -void PopupMenu::add_check_item(const String &p_label, int p_id, uint32_t p_accel) { +void PopupMenu::add_check_item(const String &p_label, int p_id, Key p_accel) { Item item; ITEM_SETUP_WITH_ACCEL(p_label, p_id, p_accel); item.checkable_type = Item::CHECKABLE_TYPE_CHECK_BOX; @@ -842,7 +868,7 @@ void PopupMenu::add_check_item(const String &p_label, int p_id, uint32_t p_accel child_controls_changed(); } -void PopupMenu::add_icon_check_item(const Ref<Texture2D> &p_icon, const String &p_label, int p_id, uint32_t p_accel) { +void PopupMenu::add_icon_check_item(const Ref<Texture2D> &p_icon, const String &p_label, int p_id, Key p_accel) { Item item; ITEM_SETUP_WITH_ACCEL(p_label, p_id, p_accel); item.icon = p_icon; @@ -853,7 +879,7 @@ void PopupMenu::add_icon_check_item(const Ref<Texture2D> &p_icon, const String & child_controls_changed(); } -void PopupMenu::add_radio_check_item(const String &p_label, int p_id, uint32_t p_accel) { +void PopupMenu::add_radio_check_item(const String &p_label, int p_id, Key p_accel) { Item item; ITEM_SETUP_WITH_ACCEL(p_label, p_id, p_accel); item.checkable_type = Item::CHECKABLE_TYPE_RADIO_BUTTON; @@ -863,7 +889,7 @@ void PopupMenu::add_radio_check_item(const String &p_label, int p_id, uint32_t p child_controls_changed(); } -void PopupMenu::add_icon_radio_check_item(const Ref<Texture2D> &p_icon, const String &p_label, int p_id, uint32_t p_accel) { +void PopupMenu::add_icon_radio_check_item(const Ref<Texture2D> &p_icon, const String &p_label, int p_id, Key p_accel) { Item item; ITEM_SETUP_WITH_ACCEL(p_label, p_id, p_accel); item.icon = p_icon; @@ -874,7 +900,7 @@ void PopupMenu::add_icon_radio_check_item(const Ref<Texture2D> &p_icon, const St child_controls_changed(); } -void PopupMenu::add_multistate_item(const String &p_label, int p_max_states, int p_default_state, int p_id, uint32_t p_accel) { +void PopupMenu::add_multistate_item(const String &p_label, int p_max_states, int p_default_state, int p_id, Key p_accel) { Item item; ITEM_SETUP_WITH_ACCEL(p_label, p_id, p_accel); item.max_states = p_max_states; @@ -973,6 +999,9 @@ void PopupMenu::add_submenu_item(const String &p_label, const String &p_submenu, /* Methods to modify existing items. */ void PopupMenu::set_item_text(int p_idx, const String &p_text) { + if (p_idx < 0) { + p_idx += get_item_count(); + } ERR_FAIL_INDEX(p_idx, items.size()); items.write[p_idx].text = p_text; items.write[p_idx].xl_text = atr(p_text); @@ -983,6 +1012,9 @@ void PopupMenu::set_item_text(int p_idx, const String &p_text) { } void PopupMenu::set_item_text_direction(int p_item, Control::TextDirection p_text_direction) { + if (p_item < 0) { + p_item += get_item_count(); + } ERR_FAIL_INDEX(p_item, items.size()); ERR_FAIL_COND((int)p_text_direction < -1 || (int)p_text_direction > 3); if (items[p_item].text_direction != p_text_direction) { @@ -993,6 +1025,9 @@ void PopupMenu::set_item_text_direction(int p_item, Control::TextDirection p_tex } void PopupMenu::clear_item_opentype_features(int p_item) { + if (p_item < 0) { + p_item += get_item_count(); + } ERR_FAIL_INDEX(p_item, items.size()); items.write[p_item].opentype_features.clear(); items.write[p_item].dirty = true; @@ -1000,6 +1035,9 @@ void PopupMenu::clear_item_opentype_features(int p_item) { } void PopupMenu::set_item_opentype_feature(int p_item, const String &p_name, int p_value) { + if (p_item < 0) { + p_item += get_item_count(); + } ERR_FAIL_INDEX(p_item, items.size()); int32_t tag = TS->name_to_tag(p_name); if (!items[p_item].opentype_features.has(tag) || (int)items[p_item].opentype_features[tag] != p_value) { @@ -1010,6 +1048,9 @@ void PopupMenu::set_item_opentype_feature(int p_item, const String &p_name, int } void PopupMenu::set_item_language(int p_item, const String &p_language) { + if (p_item < 0) { + p_item += get_item_count(); + } ERR_FAIL_INDEX(p_item, items.size()); if (items[p_item].language != p_language) { items.write[p_item].language = p_language; @@ -1019,6 +1060,9 @@ void PopupMenu::set_item_language(int p_item, const String &p_language) { } void PopupMenu::set_item_icon(int p_idx, const Ref<Texture2D> &p_icon) { + if (p_idx < 0) { + p_idx += get_item_count(); + } ERR_FAIL_INDEX(p_idx, items.size()); items.write[p_idx].icon = p_icon; @@ -1027,6 +1071,9 @@ void PopupMenu::set_item_icon(int p_idx, const Ref<Texture2D> &p_icon) { } void PopupMenu::set_item_checked(int p_idx, bool p_checked) { + if (p_idx < 0) { + p_idx += get_item_count(); + } ERR_FAIL_INDEX(p_idx, items.size()); items.write[p_idx].checked = p_checked; @@ -1036,6 +1083,9 @@ void PopupMenu::set_item_checked(int p_idx, bool p_checked) { } void PopupMenu::set_item_id(int p_idx, int p_id) { + if (p_idx < 0) { + p_idx += get_item_count(); + } ERR_FAIL_INDEX(p_idx, items.size()); items.write[p_idx].id = p_id; @@ -1043,7 +1093,10 @@ void PopupMenu::set_item_id(int p_idx, int p_id) { child_controls_changed(); } -void PopupMenu::set_item_accelerator(int p_idx, uint32_t p_accel) { +void PopupMenu::set_item_accelerator(int p_idx, Key p_accel) { + if (p_idx < 0) { + p_idx += get_item_count(); + } ERR_FAIL_INDEX(p_idx, items.size()); items.write[p_idx].accel = p_accel; items.write[p_idx].dirty = true; @@ -1053,6 +1106,9 @@ void PopupMenu::set_item_accelerator(int p_idx, uint32_t p_accel) { } void PopupMenu::set_item_metadata(int p_idx, const Variant &p_meta) { + if (p_idx < 0) { + p_idx += get_item_count(); + } ERR_FAIL_INDEX(p_idx, items.size()); items.write[p_idx].metadata = p_meta; control->update(); @@ -1060,6 +1116,9 @@ void PopupMenu::set_item_metadata(int p_idx, const Variant &p_meta) { } void PopupMenu::set_item_disabled(int p_idx, bool p_disabled) { + if (p_idx < 0) { + p_idx += get_item_count(); + } ERR_FAIL_INDEX(p_idx, items.size()); items.write[p_idx].disabled = p_disabled; control->update(); @@ -1067,6 +1126,9 @@ void PopupMenu::set_item_disabled(int p_idx, bool p_disabled) { } void PopupMenu::set_item_submenu(int p_idx, const String &p_submenu) { + if (p_idx < 0) { + p_idx += get_item_count(); + } ERR_FAIL_INDEX(p_idx, items.size()); items.write[p_idx].submenu = p_submenu; control->update(); @@ -1119,8 +1181,8 @@ Ref<Texture2D> PopupMenu::get_item_icon(int p_idx) const { return items[p_idx].icon; } -uint32_t PopupMenu::get_item_accelerator(int p_idx) const { - ERR_FAIL_INDEX_V(p_idx, items.size(), 0); +Key PopupMenu::get_item_accelerator(int p_idx) const { + ERR_FAIL_INDEX_V(p_idx, items.size(), Key::NONE); return items[p_idx].accel; } @@ -1175,6 +1237,9 @@ int PopupMenu::get_item_state(int p_idx) const { } void PopupMenu::set_item_as_separator(int p_idx, bool p_separator) { + if (p_idx < 0) { + p_idx += get_item_count(); + } ERR_FAIL_INDEX(p_idx, items.size()); items.write[p_idx].separator = p_separator; control->update(); @@ -1186,24 +1251,36 @@ bool PopupMenu::is_item_separator(int p_idx) const { } void PopupMenu::set_item_as_checkable(int p_idx, bool p_checkable) { + if (p_idx < 0) { + p_idx += get_item_count(); + } ERR_FAIL_INDEX(p_idx, items.size()); items.write[p_idx].checkable_type = p_checkable ? Item::CHECKABLE_TYPE_CHECK_BOX : Item::CHECKABLE_TYPE_NONE; control->update(); } void PopupMenu::set_item_as_radio_checkable(int p_idx, bool p_radio_checkable) { + if (p_idx < 0) { + p_idx += get_item_count(); + } ERR_FAIL_INDEX(p_idx, items.size()); items.write[p_idx].checkable_type = p_radio_checkable ? Item::CHECKABLE_TYPE_RADIO_BUTTON : Item::CHECKABLE_TYPE_NONE; control->update(); } void PopupMenu::set_item_tooltip(int p_idx, const String &p_tooltip) { + if (p_idx < 0) { + p_idx += get_item_count(); + } ERR_FAIL_INDEX(p_idx, items.size()); items.write[p_idx].tooltip = p_tooltip; control->update(); } void PopupMenu::set_item_shortcut(int p_idx, const Ref<Shortcut> &p_shortcut, bool p_global) { + if (p_idx < 0) { + p_idx += get_item_count(); + } ERR_FAIL_INDEX(p_idx, items.size()); if (items[p_idx].shortcut.is_valid()) { _unref_shortcut(items[p_idx].shortcut); @@ -1220,6 +1297,9 @@ void PopupMenu::set_item_shortcut(int p_idx, const Ref<Shortcut> &p_shortcut, bo } void PopupMenu::set_item_h_offset(int p_idx, int p_offset) { + 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(); @@ -1227,12 +1307,18 @@ void PopupMenu::set_item_h_offset(int p_idx, int p_offset) { } void PopupMenu::set_item_multistate(int p_idx, int p_state) { + if (p_idx < 0) { + p_idx += get_item_count(); + } ERR_FAIL_INDEX(p_idx, items.size()); items.write[p_idx].state = p_state; control->update(); } void PopupMenu::set_item_shortcut_disabled(int p_idx, bool p_disabled) { + if (p_idx < 0) { + p_idx += get_item_count(); + } ERR_FAIL_INDEX(p_idx, items.size()); items.write[p_idx].shortcut_is_disabled = p_disabled; control->update(); @@ -1267,34 +1353,71 @@ bool PopupMenu::is_item_shortcut_disabled(int p_idx) const { return items[p_idx].shortcut_is_disabled; } +void PopupMenu::set_current_index(int p_idx) { + ERR_FAIL_INDEX(p_idx, items.size()); + mouse_over = p_idx; + scroll_to_item(mouse_over); + control->update(); +} + int PopupMenu::get_current_index() const { return mouse_over; } +void PopupMenu::set_item_count(int p_count) { + ERR_FAIL_COND(p_count < 0); + int prev_size = items.size(); + items.resize(p_count); + + if (prev_size < p_count) { + for (int i = prev_size; i < p_count; i++) { + items.write[i].id = i; + } + } + + control->update(); + child_controls_changed(); + notify_property_list_changed(); +} + int PopupMenu::get_item_count() const { return items.size(); } +void PopupMenu::scroll_to_item(int p_item) { + ERR_FAIL_INDEX(p_item, items.size()); + + // Scroll item into view (upwards). + if (items[p_item]._ofs_cache - scroll_container->get_v_scroll() < -control->get_position().y) { + scroll_container->set_v_scroll(items[p_item]._ofs_cache + control->get_position().y); + } + + // Scroll item into view (downwards). + if (items[p_item]._ofs_cache + items[p_item]._height_cache - scroll_container->get_v_scroll() > -control->get_position().y + scroll_container->get_size().height) { + scroll_container->set_v_scroll(items[p_item]._ofs_cache + items[p_item]._height_cache + control->get_position().y); + } +} + bool PopupMenu::activate_item_by_event(const Ref<InputEvent> &p_event, bool p_for_global_only) { - Key code = KEY_NONE; + Key code = Key::NONE; Ref<InputEventKey> k = p_event; if (k.is_valid()) { code = k->get_keycode(); - if (code == KEY_NONE) { + if (code == Key::NONE) { code = (Key)k->get_unicode(); } if (k->is_ctrl_pressed()) { - code |= KEY_MASK_CTRL; + code |= KeyModifierMask::CTRL; } if (k->is_alt_pressed()) { - code |= KEY_MASK_ALT; + code |= KeyModifierMask::ALT; } if (k->is_meta_pressed()) { - code |= KEY_MASK_META; + code |= KeyModifierMask::META; } if (k->is_shift_pressed()) { - code |= KEY_MASK_SHIFT; + code |= KeyModifierMask::SHIFT; } } @@ -1308,12 +1431,12 @@ bool PopupMenu::activate_item_by_event(const Ref<InputEvent> &p_event, bool p_fo return true; } - if (code != 0 && items[i].accel == code) { + if (code != Key::NONE && items[i].accel == code) { activate_item(i); return true; } - if (items[i].submenu != "") { + if (!items[i].submenu.is_empty()) { Node *n = get_node(items[i].submenu); if (!n) { continue; @@ -1378,12 +1501,12 @@ void PopupMenu::activate_item(int p_item) { need_hide = false; } - emit_signal(SNAME("id_pressed"), id); - emit_signal(SNAME("index_pressed"), p_item); - if (need_hide) { hide(); } + + emit_signal(SNAME("id_pressed"), id); + emit_signal(SNAME("index_pressed"), p_item); } void PopupMenu::remove_item(int p_idx) { @@ -1393,7 +1516,7 @@ void PopupMenu::remove_item(int p_idx) { _unref_shortcut(items[p_idx].shortcut); } - items.remove(p_idx); + items.remove_at(p_idx); control->update(); child_controls_changed(); } @@ -1402,7 +1525,7 @@ void PopupMenu::add_separator(const String &p_text, int p_id) { Item sep; sep.separator = true; sep.id = p_id; - if (p_text != String()) { + if (!p_text.is_empty()) { sep.text = p_text; sep.xl_text = atr(p_text); } @@ -1420,27 +1543,7 @@ void PopupMenu::clear() { mouse_over = -1; control->update(); child_controls_changed(); -} - -Array PopupMenu::_get_items() const { - Array items; - for (int i = 0; i < get_item_count(); i++) { - items.push_back(get_item_text(i)); - items.push_back(get_item_icon(i)); - // For compatibility, use false/true for no/checkbox and integers for other values - int ct = this->items[i].checkable_type; - items.push_back(Variant(ct <= Item::CHECKABLE_TYPE_CHECK_BOX ? is_item_checkable(i) : ct)); - items.push_back(is_item_checked(i)); - items.push_back(is_item_disabled(i)); - - items.push_back(get_item_id(i)); - items.push_back(get_item_accelerator(i)); - items.push_back(get_item_metadata(i)); - items.push_back(get_item_submenu(i)); - items.push_back(is_item_separator(i)); - } - - return items; + notify_property_list_changed(); } void PopupMenu::_ref_shortcut(Ref<Shortcut> p_sc) { @@ -1461,45 +1564,6 @@ void PopupMenu::_unref_shortcut(Ref<Shortcut> p_sc) { } } -void PopupMenu::_set_items(const Array &p_items) { - ERR_FAIL_COND(p_items.size() % 10); - clear(); - - for (int i = 0; i < p_items.size(); i += 10) { - String text = p_items[i + 0]; - Ref<Texture2D> icon = p_items[i + 1]; - // For compatibility, use false/true for no/checkbox and integers for other values - bool checkable = p_items[i + 2]; - bool radio_checkable = (int)p_items[i + 2] == Item::CHECKABLE_TYPE_RADIO_BUTTON; - bool checked = p_items[i + 3]; - bool disabled = p_items[i + 4]; - - int id = p_items[i + 5]; - int accel = p_items[i + 6]; - Variant meta = p_items[i + 7]; - String subm = p_items[i + 8]; - bool sep = p_items[i + 9]; - - int idx = get_item_count(); - add_item(text, id); - set_item_icon(idx, icon); - if (checkable) { - if (radio_checkable) { - set_item_as_radio_checkable(idx, true); - } else { - set_item_as_checkable(idx, true); - } - } - set_item_checked(idx, checked); - set_item_disabled(idx, disabled); - set_item_id(idx, id); - set_item_metadata(idx, meta); - set_item_as_separator(idx, sep); - set_item_accelerator(idx, accel); - set_item_submenu(idx, subm); - } -} - // Hide on item selection determines whether or not the popup will close after item selection void PopupMenu::set_hide_on_item_selection(bool p_enabled) { hide_on_item_selection = p_enabled; @@ -1559,7 +1623,7 @@ void PopupMenu::set_parent_rect(const Rect2 &p_rect) { void PopupMenu::get_translatable_strings(List<String> *p_strings) const { for (int i = 0; i < items.size(); i++) { - if (items[i].xl_text != "") { + if (!items[i].xl_text.is_empty()) { p_strings->push_back(items[i].xl_text); } } @@ -1581,6 +1645,145 @@ void PopupMenu::take_mouse_focus() { } } +bool PopupMenu::_set(const StringName &p_name, const Variant &p_value) { + Vector<String> components = String(p_name).split("/", true, 2); + if (components.size() >= 2 && components[0].begins_with("item_") && components[0].trim_prefix("item_").is_valid_int()) { + int item_index = components[0].trim_prefix("item_").to_int(); + String property = components[1]; + if (property == "text") { + set_item_text(item_index, p_value); + return true; + } else if (property == "icon") { + set_item_icon(item_index, p_value); + return true; + } else if (property == "checkable") { + bool radio_checkable = (int)p_value == Item::CHECKABLE_TYPE_RADIO_BUTTON; + if (radio_checkable) { + set_item_as_radio_checkable(item_index, true); + } else { + bool checkable = p_value; + set_item_as_checkable(item_index, checkable); + } + return true; + } else if (property == "checked") { + set_item_checked(item_index, p_value); + return true; + } else if (property == "id") { + set_item_id(item_index, p_value); + return true; + } else if (property == "disabled") { + set_item_disabled(item_index, p_value); + return true; + } else if (property == "separator") { + set_item_as_separator(item_index, p_value); + return true; + } + } +#ifndef DISABLE_DEPRECATED + // Compatibility. + if (p_name == "items") { + Array arr = p_value; + ERR_FAIL_COND_V(arr.size() % 10, false); + clear(); + + for (int i = 0; i < arr.size(); i += 10) { + String text = arr[i + 0]; + Ref<Texture2D> icon = arr[i + 1]; + // For compatibility, use false/true for no/checkbox and integers for other values + bool checkable = arr[i + 2]; + bool radio_checkable = (int)arr[i + 2] == Item::CHECKABLE_TYPE_RADIO_BUTTON; + bool checked = arr[i + 3]; + bool disabled = arr[i + 4]; + + int id = arr[i + 5]; + int accel = arr[i + 6]; + Variant meta = arr[i + 7]; + String subm = arr[i + 8]; + bool sep = arr[i + 9]; + + int idx = get_item_count(); + add_item(text, id); + set_item_icon(idx, icon); + if (checkable) { + if (radio_checkable) { + set_item_as_radio_checkable(idx, true); + } else { + set_item_as_checkable(idx, true); + } + } + set_item_checked(idx, checked); + set_item_disabled(idx, disabled); + set_item_id(idx, id); + set_item_metadata(idx, meta); + set_item_as_separator(idx, sep); + set_item_accelerator(idx, (Key)accel); + set_item_submenu(idx, subm); + } + } +#endif + return false; +} + +bool PopupMenu::_get(const StringName &p_name, Variant &r_ret) const { + Vector<String> components = String(p_name).split("/", true, 2); + if (components.size() >= 2 && components[0].begins_with("item_") && components[0].trim_prefix("item_").is_valid_int()) { + int item_index = components[0].trim_prefix("item_").to_int(); + String property = components[1]; + if (property == "text") { + r_ret = get_item_text(item_index); + return true; + } else if (property == "icon") { + r_ret = get_item_icon(item_index); + return true; + } else if (property == "checkable") { + r_ret = this->items[item_index].checkable_type; + return true; + } else if (property == "checked") { + r_ret = is_item_checked(item_index); + return true; + } else if (property == "id") { + r_ret = get_item_id(item_index); + return true; + } else if (property == "disabled") { + r_ret = is_item_disabled(item_index); + return true; + } else if (property == "separator") { + r_ret = is_item_separator(item_index); + return true; + } + } + return false; +} + +void PopupMenu::_get_property_list(List<PropertyInfo> *p_list) const { + for (int i = 0; i < items.size(); i++) { + p_list->push_back(PropertyInfo(Variant::STRING, vformat("item_%d/text", i))); + + PropertyInfo pi = PropertyInfo(Variant::OBJECT, vformat("item_%d/icon", i), PROPERTY_HINT_RESOURCE_TYPE, "Texture2D"); + pi.usage &= ~(get_item_icon(i).is_null() ? PROPERTY_USAGE_STORAGE : 0); + p_list->push_back(pi); + + pi = PropertyInfo(Variant::INT, vformat("item_%d/checkable", i), PROPERTY_HINT_ENUM, "No,As checkbox,As radio button"); + pi.usage &= ~(!is_item_checkable(i) ? PROPERTY_USAGE_STORAGE : 0); + p_list->push_back(pi); + + pi = PropertyInfo(Variant::BOOL, vformat("item_%d/checked", i)); + pi.usage &= ~(!is_item_checked(i) ? PROPERTY_USAGE_STORAGE : 0); + p_list->push_back(pi); + + pi = PropertyInfo(Variant::INT, vformat("item_%d/id", i), PROPERTY_HINT_RANGE, "0,10,1,or_greater"); + p_list->push_back(pi); + + pi = PropertyInfo(Variant::BOOL, vformat("item_%d/disabled", i)); + pi.usage &= ~(!is_item_disabled(i) ? PROPERTY_USAGE_STORAGE : 0); + p_list->push_back(pi); + + pi = PropertyInfo(Variant::BOOL, vformat("item_%d/separator", i)); + pi.usage &= ~(!is_item_separator(i) ? PROPERTY_USAGE_STORAGE : 0); + p_list->push_back(pi); + } +} + void PopupMenu::_bind_methods() { ClassDB::bind_method(D_METHOD("add_item", "label", "id", "accel"), &PopupMenu::add_item, DEFVAL(-1), DEFVAL(0)); ClassDB::bind_method(D_METHOD("add_icon_item", "texture", "label", "id", "accel"), &PopupMenu::add_icon_item, DEFVAL(-1), DEFVAL(0)); @@ -1600,59 +1803,60 @@ void PopupMenu::_bind_methods() { ClassDB::bind_method(D_METHOD("add_submenu_item", "label", "submenu", "id"), &PopupMenu::add_submenu_item, DEFVAL(-1)); - ClassDB::bind_method(D_METHOD("set_item_text", "idx", "text"), &PopupMenu::set_item_text); - ClassDB::bind_method(D_METHOD("set_item_text_direction", "idx", "direction"), &PopupMenu::set_item_text_direction); - ClassDB::bind_method(D_METHOD("set_item_opentype_feature", "idx", "tag", "value"), &PopupMenu::set_item_opentype_feature); - ClassDB::bind_method(D_METHOD("set_item_language", "idx", "language"), &PopupMenu::set_item_language); - ClassDB::bind_method(D_METHOD("set_item_icon", "idx", "icon"), &PopupMenu::set_item_icon); - ClassDB::bind_method(D_METHOD("set_item_checked", "idx", "checked"), &PopupMenu::set_item_checked); - ClassDB::bind_method(D_METHOD("set_item_id", "idx", "id"), &PopupMenu::set_item_id); - ClassDB::bind_method(D_METHOD("set_item_accelerator", "idx", "accel"), &PopupMenu::set_item_accelerator); - ClassDB::bind_method(D_METHOD("set_item_metadata", "idx", "metadata"), &PopupMenu::set_item_metadata); - ClassDB::bind_method(D_METHOD("set_item_disabled", "idx", "disabled"), &PopupMenu::set_item_disabled); - ClassDB::bind_method(D_METHOD("set_item_submenu", "idx", "submenu"), &PopupMenu::set_item_submenu); - ClassDB::bind_method(D_METHOD("set_item_as_separator", "idx", "enable"), &PopupMenu::set_item_as_separator); - ClassDB::bind_method(D_METHOD("set_item_as_checkable", "idx", "enable"), &PopupMenu::set_item_as_checkable); - ClassDB::bind_method(D_METHOD("set_item_as_radio_checkable", "idx", "enable"), &PopupMenu::set_item_as_radio_checkable); - ClassDB::bind_method(D_METHOD("set_item_tooltip", "idx", "tooltip"), &PopupMenu::set_item_tooltip); - ClassDB::bind_method(D_METHOD("set_item_shortcut", "idx", "shortcut", "global"), &PopupMenu::set_item_shortcut, DEFVAL(false)); - ClassDB::bind_method(D_METHOD("set_item_multistate", "idx", "state"), &PopupMenu::set_item_multistate); - ClassDB::bind_method(D_METHOD("set_item_shortcut_disabled", "idx", "disabled"), &PopupMenu::set_item_shortcut_disabled); - - ClassDB::bind_method(D_METHOD("toggle_item_checked", "idx"), &PopupMenu::toggle_item_checked); - ClassDB::bind_method(D_METHOD("toggle_item_multistate", "idx"), &PopupMenu::toggle_item_multistate); - - ClassDB::bind_method(D_METHOD("get_item_text", "idx"), &PopupMenu::get_item_text); - ClassDB::bind_method(D_METHOD("get_item_text_direction", "idx"), &PopupMenu::get_item_text_direction); - ClassDB::bind_method(D_METHOD("get_item_opentype_feature", "idx", "tag"), &PopupMenu::get_item_opentype_feature); - ClassDB::bind_method(D_METHOD("clear_item_opentype_features", "idx"), &PopupMenu::clear_item_opentype_features); - ClassDB::bind_method(D_METHOD("get_item_language", "idx"), &PopupMenu::get_item_language); - ClassDB::bind_method(D_METHOD("get_item_icon", "idx"), &PopupMenu::get_item_icon); - ClassDB::bind_method(D_METHOD("is_item_checked", "idx"), &PopupMenu::is_item_checked); - ClassDB::bind_method(D_METHOD("get_item_id", "idx"), &PopupMenu::get_item_id); + ClassDB::bind_method(D_METHOD("set_item_text", "index", "text"), &PopupMenu::set_item_text); + ClassDB::bind_method(D_METHOD("set_item_text_direction", "index", "direction"), &PopupMenu::set_item_text_direction); + ClassDB::bind_method(D_METHOD("set_item_opentype_feature", "index", "tag", "value"), &PopupMenu::set_item_opentype_feature); + ClassDB::bind_method(D_METHOD("set_item_language", "index", "language"), &PopupMenu::set_item_language); + ClassDB::bind_method(D_METHOD("set_item_icon", "index", "icon"), &PopupMenu::set_item_icon); + ClassDB::bind_method(D_METHOD("set_item_checked", "index", "checked"), &PopupMenu::set_item_checked); + ClassDB::bind_method(D_METHOD("set_item_id", "index", "id"), &PopupMenu::set_item_id); + ClassDB::bind_method(D_METHOD("set_item_accelerator", "index", "accel"), &PopupMenu::set_item_accelerator); + ClassDB::bind_method(D_METHOD("set_item_metadata", "index", "metadata"), &PopupMenu::set_item_metadata); + ClassDB::bind_method(D_METHOD("set_item_disabled", "index", "disabled"), &PopupMenu::set_item_disabled); + ClassDB::bind_method(D_METHOD("set_item_submenu", "index", "submenu"), &PopupMenu::set_item_submenu); + ClassDB::bind_method(D_METHOD("set_item_as_separator", "index", "enable"), &PopupMenu::set_item_as_separator); + ClassDB::bind_method(D_METHOD("set_item_as_checkable", "index", "enable"), &PopupMenu::set_item_as_checkable); + 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_multistate", "index", "state"), &PopupMenu::set_item_multistate); + ClassDB::bind_method(D_METHOD("set_item_shortcut_disabled", "index", "disabled"), &PopupMenu::set_item_shortcut_disabled); + + ClassDB::bind_method(D_METHOD("toggle_item_checked", "index"), &PopupMenu::toggle_item_checked); + ClassDB::bind_method(D_METHOD("toggle_item_multistate", "index"), &PopupMenu::toggle_item_multistate); + + ClassDB::bind_method(D_METHOD("get_item_text", "index"), &PopupMenu::get_item_text); + ClassDB::bind_method(D_METHOD("get_item_text_direction", "index"), &PopupMenu::get_item_text_direction); + ClassDB::bind_method(D_METHOD("get_item_opentype_feature", "index", "tag"), &PopupMenu::get_item_opentype_feature); + ClassDB::bind_method(D_METHOD("clear_item_opentype_features", "index"), &PopupMenu::clear_item_opentype_features); + ClassDB::bind_method(D_METHOD("get_item_language", "index"), &PopupMenu::get_item_language); + ClassDB::bind_method(D_METHOD("get_item_icon", "index"), &PopupMenu::get_item_icon); + ClassDB::bind_method(D_METHOD("is_item_checked", "index"), &PopupMenu::is_item_checked); + ClassDB::bind_method(D_METHOD("get_item_id", "index"), &PopupMenu::get_item_id); ClassDB::bind_method(D_METHOD("get_item_index", "id"), &PopupMenu::get_item_index); - ClassDB::bind_method(D_METHOD("get_item_accelerator", "idx"), &PopupMenu::get_item_accelerator); - ClassDB::bind_method(D_METHOD("get_item_metadata", "idx"), &PopupMenu::get_item_metadata); - ClassDB::bind_method(D_METHOD("is_item_disabled", "idx"), &PopupMenu::is_item_disabled); - ClassDB::bind_method(D_METHOD("get_item_submenu", "idx"), &PopupMenu::get_item_submenu); - ClassDB::bind_method(D_METHOD("is_item_separator", "idx"), &PopupMenu::is_item_separator); - ClassDB::bind_method(D_METHOD("is_item_checkable", "idx"), &PopupMenu::is_item_checkable); - ClassDB::bind_method(D_METHOD("is_item_radio_checkable", "idx"), &PopupMenu::is_item_radio_checkable); - ClassDB::bind_method(D_METHOD("is_item_shortcut_disabled", "idx"), &PopupMenu::is_item_shortcut_disabled); - ClassDB::bind_method(D_METHOD("get_item_tooltip", "idx"), &PopupMenu::get_item_tooltip); - ClassDB::bind_method(D_METHOD("get_item_shortcut", "idx"), &PopupMenu::get_item_shortcut); - + ClassDB::bind_method(D_METHOD("get_item_accelerator", "index"), &PopupMenu::get_item_accelerator); + ClassDB::bind_method(D_METHOD("get_item_metadata", "index"), &PopupMenu::get_item_metadata); + ClassDB::bind_method(D_METHOD("is_item_disabled", "index"), &PopupMenu::is_item_disabled); + ClassDB::bind_method(D_METHOD("get_item_submenu", "index"), &PopupMenu::get_item_submenu); + ClassDB::bind_method(D_METHOD("is_item_separator", "index"), &PopupMenu::is_item_separator); + ClassDB::bind_method(D_METHOD("is_item_checkable", "index"), &PopupMenu::is_item_checkable); + ClassDB::bind_method(D_METHOD("is_item_radio_checkable", "index"), &PopupMenu::is_item_radio_checkable); + 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("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_item_count", "count"), &PopupMenu::set_item_count); ClassDB::bind_method(D_METHOD("get_item_count"), &PopupMenu::get_item_count); - ClassDB::bind_method(D_METHOD("remove_item", "idx"), &PopupMenu::remove_item); + ClassDB::bind_method(D_METHOD("scroll_to_item", "index"), &PopupMenu::scroll_to_item); + + ClassDB::bind_method(D_METHOD("remove_item", "index"), &PopupMenu::remove_item); ClassDB::bind_method(D_METHOD("add_separator", "label", "id"), &PopupMenu::add_separator, DEFVAL(String()), DEFVAL(-1)); ClassDB::bind_method(D_METHOD("clear"), &PopupMenu::clear); - ClassDB::bind_method(D_METHOD("_set_items"), &PopupMenu::_set_items); - ClassDB::bind_method(D_METHOD("_get_items"), &PopupMenu::_get_items); - ClassDB::bind_method(D_METHOD("set_hide_on_item_selection", "enable"), &PopupMenu::set_hide_on_item_selection); ClassDB::bind_method(D_METHOD("is_hide_on_item_selection"), &PopupMenu::is_hide_on_item_selection); @@ -1668,13 +1872,14 @@ void PopupMenu::_bind_methods() { ClassDB::bind_method(D_METHOD("set_allow_search", "allow"), &PopupMenu::set_allow_search); ClassDB::bind_method(D_METHOD("get_allow_search"), &PopupMenu::get_allow_search); - ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "items", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR | PROPERTY_USAGE_INTERNAL), "_set_items", "_get_items"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "hide_on_item_selection"), "set_hide_on_item_selection", "is_hide_on_item_selection"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "hide_on_checkable_item_selection"), "set_hide_on_checkable_item_selection", "is_hide_on_checkable_item_selection"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "hide_on_state_item_selection"), "set_hide_on_state_item_selection", "is_hide_on_state_item_selection"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "submenu_popup_delay"), "set_submenu_popup_delay", "get_submenu_popup_delay"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "allow_search"), "set_allow_search", "get_allow_search"); + ADD_ARRAY_COUNT("Items", "item_count", "set_item_count", "get_item_count", "item_"); + 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"))); diff --git a/scene/gui/popup_menu.h b/scene/gui/popup_menu.h index 428076c6da..5ce55209d4 100644 --- a/scene/gui/popup_menu.h +++ b/scene/gui/popup_menu.h @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ @@ -66,7 +66,7 @@ class PopupMenu : public Popup { Variant metadata; String submenu; String tooltip; - uint32_t accel = 0; + Key accel = Key::NONE; int _ofs_cache = 0; int _height_cache = 0; int h_ofs = 0; @@ -92,7 +92,7 @@ class PopupMenu : public Popup { Timer *submenu_timer; List<Rect2> autohide_areas; Vector<Item> items; - int initial_button_mask = 0; + MouseButton initial_button_mask = MouseButton::NONE; bool during_grabbed_click = false; int mouse_over = -1; int submenu_over = -1; @@ -103,7 +103,6 @@ class PopupMenu : public Popup { int _get_item_height(int p_item) const; int _get_items_total_height() const; - void _scroll_to_item(int p_item); void _shape_item(int p_item); @@ -117,9 +116,6 @@ class PopupMenu : public Popup { bool hide_on_multistate_item_selection = false; Vector2 moved; - Array _get_items() const; - void _set_items(const Array &p_items); - Map<Ref<Shortcut>, int> shortcut_refcount; void _ref_shortcut(Ref<Shortcut> p_sc); @@ -141,6 +137,9 @@ class PopupMenu : public Popup { protected: 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; static void _bind_methods(); public: @@ -148,14 +147,14 @@ public: // this value should be updated to reflect the new size. static const int ITEM_PROPERTY_SIZE = 10; - void add_item(const String &p_label, int p_id = -1, uint32_t p_accel = 0); - void add_icon_item(const Ref<Texture2D> &p_icon, const String &p_label, int p_id = -1, uint32_t p_accel = 0); - void add_check_item(const String &p_label, int p_id = -1, uint32_t p_accel = 0); - void add_icon_check_item(const Ref<Texture2D> &p_icon, const String &p_label, int p_id = -1, uint32_t p_accel = 0); - void add_radio_check_item(const String &p_label, int p_id = -1, uint32_t p_accel = 0); - void add_icon_radio_check_item(const Ref<Texture2D> &p_icon, const String &p_label, int p_id = -1, uint32_t p_accel = 0); + void add_item(const String &p_label, int p_id = -1, Key p_accel = Key::NONE); + void add_icon_item(const Ref<Texture2D> &p_icon, const String &p_label, int p_id = -1, Key p_accel = Key::NONE); + void add_check_item(const String &p_label, int p_id = -1, Key p_accel = Key::NONE); + void add_icon_check_item(const Ref<Texture2D> &p_icon, const String &p_label, int p_id = -1, Key p_accel = Key::NONE); + void add_radio_check_item(const String &p_label, int p_id = -1, Key p_accel = Key::NONE); + void add_icon_radio_check_item(const Ref<Texture2D> &p_icon, const String &p_label, int p_id = -1, Key p_accel = Key::NONE); - void add_multistate_item(const String &p_label, int p_max_states, int p_default_state = 0, int p_id = -1, uint32_t p_accel = 0); + void add_multistate_item(const String &p_label, int p_max_states, int p_default_state = 0, int p_id = -1, Key p_accel = Key::NONE); void add_shortcut(const Ref<Shortcut> &p_shortcut, int p_id = -1, bool p_global = false); void add_icon_shortcut(const Ref<Texture2D> &p_icon, const Ref<Shortcut> &p_shortcut, int p_id = -1, bool p_global = false); @@ -175,7 +174,7 @@ public: void set_item_icon(int p_idx, const Ref<Texture2D> &p_icon); void set_item_checked(int p_idx, bool p_checked); void set_item_id(int p_idx, int p_id); - void set_item_accelerator(int p_idx, uint32_t p_accel); + void set_item_accelerator(int p_idx, Key p_accel); void set_item_metadata(int p_idx, const Variant &p_meta); void set_item_disabled(int p_idx, bool p_disabled); void set_item_submenu(int p_idx, const String &p_submenu); @@ -200,7 +199,7 @@ public: bool is_item_checked(int p_idx) const; int get_item_id(int p_idx) const; int get_item_index(int p_id) const; - uint32_t get_item_accelerator(int p_idx) const; + Key get_item_accelerator(int p_idx) const; Variant get_item_metadata(int p_idx) const; bool is_item_disabled(int p_idx) const; String get_item_submenu(int p_idx) const; @@ -212,9 +211,14 @@ public: Ref<Shortcut> get_item_shortcut(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_item_count(int p_count); int get_item_count() const; + void scroll_to_item(int p_item); + bool activate_item_by_event(const Ref<InputEvent> &p_event, bool p_for_global_only = false); void activate_item(int p_item); diff --git a/scene/gui/progress_bar.cpp b/scene/gui/progress_bar.cpp index 2cfaaa2fde..20b3513375 100644 --- a/scene/gui/progress_bar.cpp +++ b/scene/gui/progress_bar.cpp @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ @@ -29,6 +29,7 @@ /*************************************************************************/ #include "progress_bar.h" + #include "scene/resources/text_line.h" Size2 ProgressBar::get_minimum_size() const { @@ -52,36 +53,38 @@ Size2 ProgressBar::get_minimum_size() const { } void ProgressBar::_notification(int p_what) { - if (p_what == 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")); + 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())); - float r = get_as_ratio(); - int mp = fg->get_minimum_size().width; - int p = r * (get_size().width - mp); - if (p > 0) { - if (is_layout_rtl()) { - draw_style_box(fg, Rect2(Point2(p, 0), Size2(fg->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(bg, Rect2(Point2(), get_size())); + float r = get_as_ratio(); + int mp = fg->get_minimum_size().width; + int p = r * (get_size().width - mp); + if (p > 0) { + if (is_layout_rtl()) { + draw_style_box(fg, Rect2(Point2(p, 0), Size2(fg->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))); + } } - } - if (percent_visible) { - String txt = TS->format_number(itos(int(get_as_ratio() * 100))) + TS->percent_sign(); - TextLine tl = TextLine(txt, font, 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 (percent_visible) { + String txt = TS->format_number(itos(int(get_as_ratio() * 100))) + TS->percent_sign(); + TextLine tl = TextLine(txt, font, 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); + } + tl.draw(get_canvas_item(), text_pos, font_color); } - tl.draw(get_canvas_item(), text_pos, font_color); - } + } break; } } diff --git a/scene/gui/progress_bar.h b/scene/gui/progress_bar.h index fb6060d932..2d89163f78 100644 --- a/scene/gui/progress_bar.h +++ b/scene/gui/progress_bar.h @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ diff --git a/scene/gui/range.cpp b/scene/gui/range.cpp index 92d4261d8d..2fb6452a97 100644 --- a/scene/gui/range.cpp +++ b/scene/gui/range.cpp @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ @@ -40,6 +40,9 @@ TypedArray<String> Range::get_configuration_warnings() const { return warnings; } +void Range::_value_changed(double p_value) { + GDVIRTUAL_CALL(_value_changed, p_value); +} void Range::_value_changed_notify() { _value_changed(shared->val); emit_signal(SNAME("value_changed"), shared->val); @@ -61,6 +64,11 @@ void Range::_changed_notify(const char *p_what) { update(); } +void Range::_validate_values() { + shared->max = MAX(shared->max, shared->min); + shared->page = CLAMP(shared->page, 0, shared->max - shared->min); +} + void Range::Shared::emit_changed(const char *p_what) { for (Set<Range *>::Element *E = owners.front(); E; E = E->next()) { Range *r = E->get(); @@ -100,6 +108,7 @@ void Range::set_value(double p_val) { void Range::set_min(double p_min) { shared->min = p_min; set_value(shared->val); + _validate_values(); shared->emit_changed("min"); @@ -109,6 +118,7 @@ void Range::set_min(double p_min) { void Range::set_max(double p_max) { shared->max = p_max; set_value(shared->val); + _validate_values(); shared->emit_changed("max"); } @@ -121,6 +131,7 @@ void Range::set_step(double p_step) { void Range::set_page(double p_page) { shared->page = p_page; set_value(shared->val); + _validate_values(); shared->emit_changed("page"); } @@ -270,6 +281,14 @@ void Range::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::BOOL, "rounded"), "set_use_rounded_values", "is_using_rounded_values"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "allow_greater"), "set_allow_greater", "is_greater_allowed"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "allow_lesser"), "set_allow_lesser", "is_lesser_allowed"); + + GDVIRTUAL_BIND(_value_changed); + + ADD_LINKED_PROPERTY("min_value", "value"); + ADD_LINKED_PROPERTY("min_value", "max_value"); + ADD_LINKED_PROPERTY("min_value", "page"); + ADD_LINKED_PROPERTY("max_value", "value"); + ADD_LINKED_PROPERTY("max_value", "page"); } void Range::set_use_rounded_values(bool p_enable) { diff --git a/scene/gui/range.h b/scene/gui/range.h index 7a129e88d6..597c50ca26 100644 --- a/scene/gui/range.h +++ b/scene/gui/range.h @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ @@ -59,14 +59,17 @@ class Range : public Control { void _value_changed_notify(); void _changed_notify(const char *p_what = ""); + void _validate_values(); protected: - virtual void _value_changed(double) {} + virtual void _value_changed(double p_value); static void _bind_methods(); bool _rounded_values = false; + GDVIRTUAL1(_value_changed, double) + public: void set_value(double p_val); void set_min(double p_min); diff --git a/scene/gui/reference_rect.cpp b/scene/gui/reference_rect.cpp index 6d7f2cfd57..ed79da5c22 100644 --- a/scene/gui/reference_rect.cpp +++ b/scene/gui/reference_rect.cpp @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ @@ -33,13 +33,15 @@ #include "core/config/engine.h" void ReferenceRect::_notification(int p_what) { - if (p_what == NOTIFICATION_DRAW) { - if (!is_inside_tree()) { - return; - } - if (Engine::get_singleton()->is_editor_hint() || !editor_only) { - draw_rect(Rect2(Point2(), get_size()), border_color, false, border_width); - } + switch (p_what) { + case NOTIFICATION_DRAW: { + if (!is_inside_tree()) { + return; + } + if (Engine::get_singleton()->is_editor_hint() || !editor_only) { + draw_rect(Rect2(Point2(), get_size()), border_color, false, border_width); + } + } break; } } diff --git a/scene/gui/reference_rect.h b/scene/gui/reference_rect.h index 7097e83a15..4a2d328162 100644 --- a/scene/gui/reference_rect.h +++ b/scene/gui/reference_rect.h @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ diff --git a/scene/gui/rich_text_effect.cpp b/scene/gui/rich_text_effect.cpp index 076fa132c0..c9516ed6b9 100644 --- a/scene/gui/rich_text_effect.cpp +++ b/scene/gui/rich_text_effect.cpp @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ diff --git a/scene/gui/rich_text_effect.h b/scene/gui/rich_text_effect.h index 5681f9b193..4532a812ee 100644 --- a/scene/gui/rich_text_effect.h +++ b/scene/gui/rich_text_effect.h @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ diff --git a/scene/gui/rich_text_label.cpp b/scene/gui/rich_text_label.cpp index f1efbbda98..bad7be7d42 100644 --- a/scene/gui/rich_text_label.cpp +++ b/scene/gui/rich_text_label.cpp @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ @@ -33,10 +33,11 @@ #include "core/math/math_defs.h" #include "core/os/keyboard.h" #include "core/os/os.h" +#include "label.h" #include "scene/scene_string_names.h" #include "servers/display_server.h" -#include "modules/modules_enabled.gen.h" +#include "modules/modules_enabled.gen.h" // For regex. #ifdef MODULE_REGEX_ENABLED #include "modules/regex/regex.h" #endif @@ -205,6 +206,49 @@ String RichTextLabel::_letters(int p_num, bool p_capitalize) const { return s; } +void RichTextLabel::_update_line_font(ItemFrame *p_frame, int p_line, const Ref<Font> &p_base_font, int p_base_font_size) { + ERR_FAIL_COND(p_frame == nullptr); + ERR_FAIL_COND(p_line < 0 || p_line >= p_frame->lines.size()); + + Line &l = p_frame->lines.write[p_line]; + + RID t = l.text_buf->get_rid(); + int spans = TS->shaped_get_span_count(t); + for (int i = 0; i < spans; i++) { + ItemText *it = (ItemText *)(uint64_t)TS->shaped_get_span_meta(t, i); + if (it) { + Ref<Font> font = _find_font(it); + if (font.is_null()) { + font = p_base_font; + } + int font_size = _find_font_size(it); + if (font_size == -1) { + font_size = p_base_font_size; + } + Dictionary font_ftr = _find_font_features(it); + TS->shaped_set_span_update_font(t, i, font->get_rids(), font_size, font_ftr); + } + } + + Item *it_to = (p_line + 1 < p_frame->lines.size()) ? p_frame->lines[p_line + 1].from : nullptr; + for (Item *it = l.from; it && it != it_to; it = _get_next_item(it)) { + switch (it->type) { + case ITEM_TABLE: { + ItemTable *table = static_cast<ItemTable *>(it); + for (Item *E : table->subitems) { + ERR_CONTINUE(E->type != ITEM_FRAME); // Children should all be frames. + ItemFrame *frame = static_cast<ItemFrame *>(E); + for (int i = 0; i < frame->lines.size(); i++) { + _update_line_font(frame, i, p_base_font, p_base_font_size); + } + } + } break; + default: + break; + } + } +} + void RichTextLabel::_resize_line(ItemFrame *p_frame, int p_line, const Ref<Font> &p_base_font, int p_base_font_size, int p_width) { ERR_FAIL_COND(p_frame == nullptr); ERR_FAIL_COND(p_line < 0 || p_line >= p_frame->lines.size()); @@ -238,7 +282,8 @@ void RichTextLabel::_resize_line(ItemFrame *p_frame, int p_line, const Ref<Font> ERR_CONTINUE(E->type != ITEM_FRAME); // Children should all be frames. ItemFrame *frame = static_cast<ItemFrame *>(E); for (int i = 0; i < frame->lines.size(); i++) { - _resize_line(frame, i, p_base_font, p_base_font_size, 1); + int w = _find_margin(frame->lines[i].from, p_base_font, p_base_font_size) + 1; + _resize_line(frame, i, p_base_font, p_base_font_size, w); } idx++; } @@ -264,7 +309,7 @@ void RichTextLabel::_resize_line(ItemFrame *p_frame, int p_line, const Ref<Font> // Assign actual widths. for (int i = 0; i < col_count; i++) { table->columns.write[i].width = table->columns[i].min_width; - if (table->columns[i].expand && total_ratio > 0) { + if (table->columns[i].expand && total_ratio > 0 && remaining_width > 0) { table->columns.write[i].width += table->columns[i].expand_ratio * remaining_width / total_ratio; } table->total_width += table->columns[i].width + hseparation; @@ -325,13 +370,16 @@ void RichTextLabel::_resize_line(ItemFrame *p_frame, int p_line, const Ref<Font> table->columns.write[column].width = MAX(table->columns.write[column].width, ceil(frame->lines[i].text_buf->get_size().x)); if (i > 0) { - frame->lines.write[i].offset.y = frame->lines[i - 1].offset.y + frame->lines[i - 1].text_buf->get_size().y; + frame->lines.write[i].offset.y = frame->lines[i - 1].offset.y + frame->lines[i - 1].text_buf->get_size().y + frame->lines[i - 1].text_buf->get_line_count() * get_theme_constant(SNAME("line_separation")); } else { frame->lines.write[i].offset.y = 0; } frame->lines.write[i].offset += offset; - float h = frame->lines[i].text_buf->get_size().y; + 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")); + if (i > 0) { + h += get_theme_constant(SNAME("line_separation")); + } if (frame->min_size_over.y > 0) { h = MAX(h, frame->min_size_over.y); } @@ -362,7 +410,7 @@ void RichTextLabel::_resize_line(ItemFrame *p_frame, int p_line, const Ref<Font> } if (p_line > 0) { - l.offset.y = p_frame->lines[p_line - 1].offset.y + p_frame->lines[p_line - 1].text_buf->get_size().y + get_theme_constant(SNAME("line_separation")); + l.offset.y = p_frame->lines[p_line - 1].offset.y + p_frame->lines[p_line - 1].text_buf->get_size().y + p_frame->lines[p_line - 1].text_buf->get_line_count() * get_theme_constant(SNAME("line_separation")); } else { l.offset.y = 0; } @@ -374,16 +422,31 @@ void RichTextLabel::_shape_line(ItemFrame *p_frame, int p_line, const Ref<Font> Line &l = p_frame->lines.write[p_line]; + uint16_t autowrap_flags = TextServer::BREAK_MANDATORY; + switch (autowrap_mode) { + case AUTOWRAP_WORD_SMART: + autowrap_flags = TextServer::BREAK_WORD_BOUND_ADAPTIVE | TextServer::BREAK_MANDATORY; + break; + case AUTOWRAP_WORD: + autowrap_flags = TextServer::BREAK_WORD_BOUND | TextServer::BREAK_MANDATORY; + break; + case AUTOWRAP_ARBITRARY: + autowrap_flags = TextServer::BREAK_GRAPHEME_BOUND | TextServer::BREAK_MANDATORY; + break; + case AUTOWRAP_OFF: + break; + } + // Clear cache. l.text_buf->clear(); - l.text_buf->set_flags(TextServer::BREAK_MANDATORY | TextServer::BREAK_WORD_BOUND | TextServer::JUSTIFICATION_KASHIDA | TextServer::JUSTIFICATION_WORD_BOUND | TextServer::JUSTIFICATION_TRIM_EDGE_SPACES); + l.text_buf->set_flags(autowrap_flags | TextServer::JUSTIFICATION_KASHIDA | TextServer::JUSTIFICATION_WORD_BOUND | TextServer::JUSTIFICATION_TRIM_EDGE_SPACES); l.char_offset = *r_char_offset; l.char_count = 0; // Add indent. l.offset.x = _find_margin(l.from, p_base_font, p_base_font_size); l.text_buf->set_width(p_width - l.offset.x); - l.text_buf->set_align((HAlign)_find_align(l.from)); + l.text_buf->set_alignment(_find_alignment(l.from)); l.text_buf->set_direction(_find_direction(l.from)); if (tab_size > 0) { // Align inline tabs. @@ -397,7 +460,7 @@ void RichTextLabel::_shape_line(ItemFrame *p_frame, int p_line, const Ref<Font> Item *it_to = (p_line + 1 < p_frame->lines.size()) ? p_frame->lines[p_line + 1].from : nullptr; int remaining_characters = visible_characters - l.char_offset; for (Item *it = l.from; it && it != it_to; it = _get_next_item(it)) { - if (visible_characters >= 0 && remaining_characters <= 0) { + if (visible_chars_behavior == VC_CHARS_BEFORE_SHAPING && visible_characters >= 0 && remaining_characters <= 0) { break; } switch (it->type) { @@ -440,18 +503,18 @@ void RichTextLabel::_shape_line(ItemFrame *p_frame, int p_line, const Ref<Font> Dictionary font_ftr = _find_font_features(it); String lang = _find_language(it); String tx = t->text; - if (visible_characters >= 0 && remaining_characters >= 0) { + if (visible_chars_behavior == VC_CHARS_BEFORE_SHAPING && visible_characters >= 0 && remaining_characters >= 0) { tx = tx.substr(0, remaining_characters); } remaining_characters -= tx.length(); - l.text_buf->add_string(tx, font, font_size, font_ftr, lang); + l.text_buf->add_string(tx, font, font_size, font_ftr, lang, (uint64_t)it); text += tx; l.char_count += tx.length(); } break; case ITEM_IMAGE: { ItemImage *img = (ItemImage *)it; - l.text_buf->add_object((uint64_t)it, img->image->get_size(), img->inline_align, 1); + l.text_buf->add_object((uint64_t)it, img->size, img->inline_align, 1); text += String::chr(0xfffc); l.char_count++; remaining_characters--; @@ -479,7 +542,8 @@ void RichTextLabel::_shape_line(ItemFrame *p_frame, int p_line, const Ref<Font> int column = idx % col_count; for (int i = 0; i < frame->lines.size(); i++) { int char_offset = l.char_offset + l.char_count; - _shape_line(frame, i, p_base_font, p_base_font_size, 1, &char_offset); + int w = _find_margin(frame->lines[i].from, p_base_font, p_base_font_size) + 1; + _shape_line(frame, i, p_base_font, p_base_font_size, w, &char_offset); int cell_ch = (char_offset - (l.char_offset + l.char_count)); l.char_count += cell_ch; t_char_count += cell_ch; @@ -509,7 +573,7 @@ void RichTextLabel::_shape_line(ItemFrame *p_frame, int p_line, const Ref<Font> // Assign actual widths. for (int i = 0; i < col_count; i++) { table->columns.write[i].width = table->columns[i].min_width; - if (table->columns[i].expand && total_ratio > 0) { + if (table->columns[i].expand && total_ratio > 0 && remaining_width > 0) { table->columns.write[i].width += table->columns[i].expand_ratio * remaining_width / total_ratio; } table->total_width += table->columns[i].width + hseparation; @@ -570,13 +634,16 @@ void RichTextLabel::_shape_line(ItemFrame *p_frame, int p_line, const Ref<Font> table->columns.write[column].width = MAX(table->columns.write[column].width, ceil(frame->lines[i].text_buf->get_size().x)); if (i > 0) { - frame->lines.write[i].offset.y = frame->lines[i - 1].offset.y + frame->lines[i - 1].text_buf->get_size().y; + frame->lines.write[i].offset.y = frame->lines[i - 1].offset.y + frame->lines[i - 1].text_buf->get_size().y + frame->lines[i - 1].text_buf->get_line_count() * get_theme_constant(SNAME("line_separation")); } else { frame->lines.write[i].offset.y = 0; } frame->lines.write[i].offset += offset; - float h = frame->lines[i].text_buf->get_size().y; + 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")); + if (i > 0) { + h += get_theme_constant(SNAME("line_separation")); + } if (frame->min_size_over.y > 0) { h = MAX(h, frame->min_size_over.y); } @@ -615,18 +682,19 @@ void RichTextLabel::_shape_line(ItemFrame *p_frame, int p_line, const Ref<Font> *r_char_offset = l.char_offset + l.char_count; if (p_line > 0) { - l.offset.y = p_frame->lines[p_line - 1].offset.y + p_frame->lines[p_line - 1].text_buf->get_size().y + get_theme_constant(SNAME("line_separation")); + l.offset.y = p_frame->lines[p_line - 1].offset.y + p_frame->lines[p_line - 1].text_buf->get_size().y + p_frame->lines[p_line - 1].text_buf->get_line_count() * get_theme_constant(SNAME("line_separation")); } else { l.offset.y = 0; } } -int RichTextLabel::_draw_line(ItemFrame *p_frame, int p_line, const Vector2 &p_ofs, int p_width, const Color &p_base_color, int p_outline_size, const Color &p_outline_color, const Color &p_font_shadow_color, bool p_shadow_as_outline, const Point2 &p_shadow_ofs) { - Vector2 off; - +int RichTextLabel::_draw_line(ItemFrame *p_frame, int p_line, const Vector2 &p_ofs, int p_width, const Color &p_base_color, int p_outline_size, const Color &p_outline_color, const Color &p_font_shadow_color, int p_shadow_outline_size, const Point2 &p_shadow_ofs, int &r_processed_glyphs) { ERR_FAIL_COND_V(p_frame == nullptr, 0); ERR_FAIL_COND_V(p_line < 0 || p_line >= p_frame->lines.size(), 0); + Vector2 off; + int line_spacing = get_theme_constant(SNAME("line_separation")); + Line &l = p_frame->lines.write[p_line]; Item *it_from = l.from; @@ -641,6 +709,12 @@ int RichTextLabel::_draw_line(ItemFrame *p_frame, int p_line, const Vector2 &p_o bool rtl = (l.text_buf->get_direction() == TextServer::DIRECTION_RTL); bool lrtl = is_layout_rtl(); + bool trim_chars = (visible_characters >= 0) && (visible_chars_behavior == VC_CHARS_AFTER_SHAPING); + bool trim_glyphs_ltr = (visible_characters >= 0) && ((visible_chars_behavior == VC_GLYPHS_LTR) || ((visible_chars_behavior == VC_GLYPHS_AUTO) && !lrtl)); + bool trim_glyphs_rtl = (visible_characters >= 0) && ((visible_chars_behavior == VC_GLYPHS_RTL) || ((visible_chars_behavior == 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; + Vector<int> list_index; Vector<ItemList *> list_items; _find_list(l.from, list_index, list_items); @@ -670,7 +744,7 @@ int RichTextLabel::_draw_line(ItemFrame *p_frame, int p_line, const Vector2 &p_o prefix = segment + prefix; } } - if (prefix != "") { + if (!prefix.is_empty()) { Ref<Font> font = _find_font(l.from); if (font.is_null()) { font = get_theme_font(SNAME("normal_font")); @@ -684,13 +758,13 @@ int RichTextLabel::_draw_line(ItemFrame *p_frame, int p_line, const Vector2 &p_o if (!lrtl && p_frame == main) { // Skip Scrollbar. offx -= scroll_w; } - font->draw_string(ci, p_ofs + Vector2(p_width - l.offset.x + offx, l.text_buf->get_line_ascent(0)), " " + prefix, HALIGN_LEFT, l.offset.x, font_size, _find_color(l.from, p_base_color)); + font->draw_string(ci, p_ofs + Vector2(p_width - l.offset.x + offx, l.text_buf->get_line_ascent(0)), " " + prefix, HORIZONTAL_ALIGNMENT_LEFT, l.offset.x, font_size, _find_color(l.from, p_base_color)); } else { float offx = 0.0f; if (lrtl && p_frame == main) { // Skip Scrollbar. offx += scroll_w; } - font->draw_string(ci, p_ofs + Vector2(offx, l.text_buf->get_line_ascent(0)), prefix + " ", HALIGN_RIGHT, l.offset.x, font_size, _find_color(l.from, p_base_color)); + font->draw_string(ci, p_ofs + Vector2(offx, l.text_buf->get_line_ascent(0)), prefix + " ", HORIZONTAL_ALIGNMENT_RIGHT, l.offset.x, font_size, _find_color(l.from, p_base_color)); } } @@ -706,6 +780,10 @@ int RichTextLabel::_draw_line(ItemFrame *p_frame, int p_line, const Vector2 &p_o Size2 ctrl_size = get_size(); // Draw text. for (int line = 0; line < l.text_buf->get_line_count(); line++) { + if (line > 0) { + off.y += line_spacing; + } + RID rid = l.text_buf->get_line_rid(line); if (p_ofs.y + off.y >= ctrl_size.height) { break; @@ -734,17 +812,17 @@ int RichTextLabel::_draw_line(ItemFrame *p_frame, int p_line, const Vector2 &p_o } // Draw text. - switch (l.text_buf->get_align()) { - case HALIGN_FILL: - case HALIGN_LEFT: { + switch (l.text_buf->get_alignment()) { + case HORIZONTAL_ALIGNMENT_FILL: + case HORIZONTAL_ALIGNMENT_LEFT: { if (rtl) { off.x += width - length; } } break; - case HALIGN_CENTER: { + case HORIZONTAL_ALIGNMENT_CENTER: { off.x += Math::floor((width - length) / 2.0); } break; - case HALIGN_RIGHT: { + case HORIZONTAL_ALIGNMENT_RIGHT: { if (!rtl) { off.x += width - length; } @@ -804,7 +882,7 @@ int RichTextLabel::_draw_line(ItemFrame *p_frame, int p_line, const Vector2 &p_o } for (int j = 0; j < frame->lines.size(); j++) { - _draw_line(frame, j, p_ofs + rect.position + off + Vector2(0, frame->lines[j].offset.y), rect.size.x, p_base_color, p_outline_size, p_outline_color, p_font_shadow_color, p_shadow_as_outline, p_shadow_ofs); + _draw_line(frame, j, p_ofs + rect.position + off + Vector2(0, frame->lines[j].offset.y), rect.size.x, p_base_color, p_outline_size, p_outline_color, p_font_shadow_color, p_shadow_outline_size, p_shadow_ofs, r_processed_glyphs); } idx++; } @@ -820,11 +898,13 @@ int RichTextLabel::_draw_line(ItemFrame *p_frame, int p_line, const Vector2 &p_o Vector2 gloff = off; // Draw oulines and shadow. + int processed_glyphs_ol = r_processed_glyphs; for (int i = 0; i < gl_size; i++) { Item *it = _get_item_at_pos(it_from, it_to, glyphs[i].start); int size = _find_outline_size(it, p_outline_size); Color font_color = _find_outline_color(it, p_outline_color); - if (size <= 0) { + Color font_shadow_color = p_font_shadow_color; + if ((size <= 0 || font_color.a == 0) && (font_shadow_color.a == 0)) { gloff.x += glyphs[i].advance; continue; } @@ -871,9 +951,10 @@ int RichTextLabel::_draw_line(ItemFrame *p_frame, int p_line, const Vector2 &p_o faded_visibility = faded_visibility < 0.0f ? 0.0f : faded_visibility; } font_color.a = faded_visibility; + font_shadow_color.a = faded_visibility; } - bool visible = (font_color.a != 0); + bool visible = (font_color.a != 0) || (font_shadow_color.a != 0); for (int j = 0; j < fx_stack.size(); j++) { ItemFX *item_fx = fx_stack[j]; @@ -942,19 +1023,22 @@ int RichTextLabel::_draw_line(ItemFrame *p_frame, int p_line, const Vector2 &p_o } } - Point2 shadow_ofs(get_theme_constant(SNAME("shadow_offset_x")), get_theme_constant(SNAME("shadow_offset_y"))); - // Draw glyph outlines. for (int j = 0; j < glyphs[i].repeat; j++) { if (visible) { - if (frid != RID()) { - if (p_shadow_as_outline) { - TS->font_draw_glyph_outline(frid, ci, glyphs[i].font_size, size, p_ofs + fx_offset + gloff + Vector2(-shadow_ofs.x, shadow_ofs.y), gl, p_font_shadow_color); - TS->font_draw_glyph_outline(frid, ci, glyphs[i].font_size, size, p_ofs + fx_offset + gloff + Vector2(shadow_ofs.x, -shadow_ofs.y), gl, p_font_shadow_color); - TS->font_draw_glyph_outline(frid, ci, glyphs[i].font_size, size, p_ofs + fx_offset + gloff + Vector2(-shadow_ofs.x, -shadow_ofs.y), gl, p_font_shadow_color); + bool skip = (trim_chars && l.char_offset + glyphs[i].end > visible_characters) || (trim_glyphs_ltr && (processed_glyphs_ol >= visible_glyphs)) || (trim_glyphs_rtl && (processed_glyphs_ol < total_glyphs - visible_glyphs)); + if (!skip && frid != RID()) { + if (font_shadow_color.a > 0) { + TS->font_draw_glyph(frid, ci, glyphs[i].font_size, p_ofs + fx_offset + gloff + p_shadow_ofs, gl, font_shadow_color); + } + if (font_shadow_color.a > 0 && p_shadow_outline_size > 0) { + TS->font_draw_glyph_outline(frid, ci, glyphs[i].font_size, p_shadow_outline_size, p_ofs + fx_offset + gloff + p_shadow_ofs, gl, font_shadow_color); + } + if (font_color.a != 0.0 && size > 0) { + TS->font_draw_glyph_outline(frid, ci, glyphs[i].font_size, size, p_ofs + fx_offset + gloff, gl, font_color); } - TS->font_draw_glyph_outline(frid, ci, glyphs[i].font_size, size, p_ofs + fx_offset + gloff, gl, font_color); } + processed_glyphs_ol++; } gloff.x += glyphs[i].advance; } @@ -993,7 +1077,8 @@ int RichTextLabel::_draw_line(ItemFrame *p_frame, int p_line, const Vector2 &p_o 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(); draw_line(p_ofs + Vector2(off.x, off.y + y_off), p_ofs + Vector2(off.x + glyphs[i].advance * glyphs[i].repeat, off.y + y_off), uc, underline_width); - } else if (_find_strikethrough(it)) { + } + if (_find_strikethrough(it)) { Color uc = font_color; uc.a *= 0.5; float y_off = -TS->shaped_text_get_ascent(rid) + TS->shaped_text_get_size(rid).y / 2; @@ -1121,11 +1206,15 @@ int RichTextLabel::_draw_line(ItemFrame *p_frame, int p_line, const Vector2 &p_o // Draw glyphs. for (int j = 0; j < glyphs[i].repeat; j++) { if (visible) { - if (frid != RID()) { - TS->font_draw_glyph(frid, ci, glyphs[i].font_size, p_ofs + fx_offset + off, gl, selected ? selection_fg : font_color); - } else if ((glyphs[i].flags & TextServer::GRAPHEME_IS_VIRTUAL) != TextServer::GRAPHEME_IS_VIRTUAL) { - TS->draw_hex_code_box(ci, glyphs[i].font_size, p_ofs + fx_offset + off, gl, selected ? selection_fg : font_color); + bool skip = (trim_chars && l.char_offset + glyphs[i].end > visible_characters) || (trim_glyphs_ltr && (r_processed_glyphs >= visible_glyphs)) || (trim_glyphs_rtl && (r_processed_glyphs < total_glyphs - visible_glyphs)); + if (!skip) { + if (frid != RID()) { + TS->font_draw_glyph(frid, ci, glyphs[i].font_size, p_ofs + fx_offset + off, gl, selected ? selection_fg : font_color); + } else if ((glyphs[i].flags & TextServer::GRAPHEME_IS_VIRTUAL) != TextServer::GRAPHEME_IS_VIRTUAL) { + TS->draw_hex_code_box(ci, glyphs[i].font_size, p_ofs + fx_offset + off, gl, selected ? selection_fg : font_color); + } } + r_processed_glyphs++; } off.x += glyphs[i].advance; } @@ -1160,7 +1249,7 @@ void RichTextLabel::_find_click(ItemFrame *p_frame, const Point2i &p_click, Item //TODO, change to binary search ? while (from_line < main->lines.size()) { - if (main->lines[from_line].offset.y + main->lines[from_line].text_buf->get_size().y >= vofs) { + if (main->lines[from_line].offset.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")) >= vofs) { break; } from_line++; @@ -1173,7 +1262,7 @@ 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 < main->lines.size()) { _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 + get_theme_constant(SNAME("line_separation")); + 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")); if (((r_click_item != nullptr) && ((*r_click_item) != nullptr)) || ((r_click_frame != nullptr) && ((*r_click_frame) != nullptr))) { if (r_outside != nullptr) { *r_outside = false; @@ -1211,17 +1300,17 @@ float RichTextLabel::_find_click_in_line(ItemFrame *p_frame, int p_line, const V } } - switch (l.text_buf->get_align()) { - case HALIGN_FILL: - case HALIGN_LEFT: { + switch (l.text_buf->get_alignment()) { + case HORIZONTAL_ALIGNMENT_FILL: + case HORIZONTAL_ALIGNMENT_LEFT: { if (rtl) { off.x += width - length; } } break; - case HALIGN_CENTER: { + case HORIZONTAL_ALIGNMENT_CENTER: { off.x += Math::floor((width - length) / 2.0); } break; - case HALIGN_RIGHT: { + case HORIZONTAL_ALIGNMENT_RIGHT: { if (!rtl) { off.x += width - length; } @@ -1292,7 +1381,7 @@ float RichTextLabel::_find_click_in_line(ItemFrame *p_frame, int p_line, const V if (rect.has_point(p_click) && !table_hit) { char_pos = TS->shaped_text_hit_test_position(rid, p_click.x - rect.position.x); } - off.y += TS->shaped_text_get_descent(rid) + l.text_buf->get_spacing_bottom(); + off.y += TS->shaped_text_get_descent(rid) + l.text_buf->get_spacing_bottom() + get_theme_constant(SNAME("line_separation")); } if (char_pos >= 0) { @@ -1413,25 +1502,32 @@ void RichTextLabel::_notification(int p_what) { update(); } } break; + case NOTIFICATION_RESIZED: { main->first_resized_line = 0; //invalidate ALL update(); + } break; + case NOTIFICATION_THEME_CHANGED: { + main->first_invalid_font_line = 0; //invalidate ALL + update(); } break; - case NOTIFICATION_THEME_CHANGED: + case NOTIFICATION_ENTER_TREE: { - if (text != "") { + if (!text.is_empty()) { set_text(text); } main->first_invalid_line = 0; //invalidate ALL update(); } break; + case NOTIFICATION_LAYOUT_DIRECTION_CHANGED: case NOTIFICATION_TRANSLATION_CHANGED: { main->first_invalid_line = 0; //invalidate ALL update(); } break; + case NOTIFICATION_DRAW: { _validate_line_caches(main); _update_scroll(); @@ -1456,7 +1552,7 @@ void RichTextLabel::_notification(int p_what) { //TODO, change to binary search ? while (from_line < main->lines.size()) { - if (main->lines[from_line].offset.y + main->lines[from_line].text_buf->get_size().y >= vofs) { + if (main->lines[from_line].offset.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")) >= vofs) { break; } from_line++; @@ -1470,7 +1566,7 @@ void RichTextLabel::_notification(int p_what) { 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")); - bool use_outline = get_theme_constant(SNAME("shadow_as_outline")); + 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"))); visible_paragraph_count = 0; @@ -1478,13 +1574,15 @@ void RichTextLabel::_notification(int p_what) { // New cache draw. Point2 ofs = text_rect.get_position() + Vector2(0, main->lines[from_line].offset.y - vofs); + int processed_glyphs = 0; while (ofs.y < size.height && from_line < main->lines.size()) { 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, use_outline, shadow_ofs); - ofs.y += main->lines[from_line].text_buf->get_size().y + get_theme_constant(SNAME("line_separation")); + 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")); from_line++; } } break; + case NOTIFICATION_INTERNAL_PROCESS: { if (is_visible_in_tree()) { double dt = get_process_delta_time(); @@ -1492,12 +1590,17 @@ void RichTextLabel::_notification(int p_what) { update(); } } break; + case NOTIFICATION_FOCUS_EXIT: { if (deselect_on_focus_loss_enabled) { selection.active = false; update(); } } break; + + case NOTIFICATION_DRAG_END: { + selection.drag_attempt = false; + } break; } } @@ -1514,6 +1617,10 @@ Control::CursorShape RichTextLabel::get_cursor_shape(const Point2 &p_pos) const return get_default_cursor_shape(); //invalid } + if (main->first_invalid_font_line < main->lines.size()) { + return get_default_cursor_shape(); //invalid + } + if (main->first_resized_line < main->lines.size()) { return get_default_cursor_shape(); //invalid } @@ -1538,11 +1645,14 @@ void RichTextLabel::gui_input(const Ref<InputEvent> &p_event) { if (main->first_invalid_line < main->lines.size()) { return; } + if (main->first_invalid_font_line < main->lines.size()) { + return; + } if (main->first_resized_line < main->lines.size()) { return; } - if (b->get_button_index() == MOUSE_BUTTON_LEFT) { + if (b->get_button_index() == MouseButton::LEFT) { if (b->is_pressed() && !b->is_double_click()) { scroll_updated = false; ItemFrame *c_frame = nullptr; @@ -1551,6 +1661,8 @@ void RichTextLabel::gui_input(const Ref<InputEvent> &p_event) { int c_index = 0; bool outside; + selection.drag_attempt = false; + _find_click(main, b->get_position(), &c_frame, &c_line, &c_item, &c_index, &outside); if (c_item != nullptr) { if (selection.enabled) { @@ -1561,17 +1673,22 @@ void RichTextLabel::gui_input(const Ref<InputEvent> &p_event) { // Erase previous selection. if (selection.active) { - selection.from_frame = nullptr; - selection.from_line = 0; - selection.from_item = nullptr; - selection.from_char = 0; - selection.to_frame = nullptr; - selection.to_line = 0; - selection.to_item = nullptr; - selection.to_char = 0; - selection.active = false; - - update(); + if (_is_click_inside_selection()) { + selection.drag_attempt = true; + selection.click_item = nullptr; + } else { + selection.from_frame = nullptr; + selection.from_line = 0; + selection.from_item = nullptr; + selection.from_char = 0; + selection.to_frame = nullptr; + selection.to_line = 0; + selection.to_item = nullptr; + selection.to_char = 0; + selection.active = false; + + update(); + } } } } @@ -1584,6 +1701,8 @@ void RichTextLabel::gui_input(const Ref<InputEvent> &p_event) { int c_index = 0; bool outside; + selection.drag_attempt = false; + _find_click(main, b->get_position(), &c_frame, &c_line, &c_item, &c_index, &outside); if (c_frame) { @@ -1615,6 +1734,22 @@ void RichTextLabel::gui_input(const Ref<InputEvent> &p_event) { DisplayServer::get_singleton()->clipboard_set_primary(get_selected_text()); } selection.click_item = nullptr; + if (selection.drag_attempt) { + selection.drag_attempt = false; + if (_is_click_inside_selection()) { + selection.from_frame = nullptr; + selection.from_line = 0; + selection.from_item = nullptr; + selection.from_char = 0; + selection.to_frame = nullptr; + selection.to_line = 0; + selection.to_item = nullptr; + selection.to_char = 0; + selection.active = false; + + update(); + } + } if (!b->is_double_click() && !scroll_updated) { Item *c_item = nullptr; @@ -1633,12 +1768,12 @@ void RichTextLabel::gui_input(const Ref<InputEvent> &p_event) { } } - if (b->get_button_index() == MOUSE_BUTTON_WHEEL_UP) { + if (b->get_button_index() == MouseButton::WHEEL_UP) { if (scroll_active) { vscroll->set_value(vscroll->get_value() - vscroll->get_page() * b->get_factor() * 0.5 / 8); } } - if (b->get_button_index() == MOUSE_BUTTON_WHEEL_DOWN) { + if (b->get_button_index() == MouseButton::WHEEL_DOWN) { if (scroll_active) { vscroll->set_value(vscroll->get_value() + vscroll->get_page() * b->get_factor() * 0.5 / 8); } @@ -1701,6 +1836,9 @@ void RichTextLabel::gui_input(const Ref<InputEvent> &p_event) { if (main->first_invalid_line < main->lines.size()) { return; } + if (main->first_invalid_font_line < main->lines.size()) { + return; + } if (main->first_resized_line < main->lines.size()) { return; } @@ -1947,19 +2085,19 @@ int RichTextLabel::_find_margin(Item *p_item, const Ref<Font> &p_base_font, int return margin; } -RichTextLabel::Align RichTextLabel::_find_align(Item *p_item) { +HorizontalAlignment RichTextLabel::_find_alignment(Item *p_item) { Item *item = p_item; while (item) { if (item->type == ITEM_PARAGRAPH) { ItemParagraph *p = static_cast<ItemParagraph *>(item); - return p->align; + return p->alignment; } item = item->parent; } - return default_align; + return default_alignment; } TextServer::Direction RichTextLabel::_find_direction(Item *p_item) { @@ -2153,27 +2291,32 @@ bool RichTextLabel::_find_layout_subitem(Item *from, Item *to) { void RichTextLabel::_validate_line_caches(ItemFrame *p_frame) { if (p_frame->first_invalid_line == p_frame->lines.size()) { + Ref<Font> base_font = get_theme_font(SNAME("normal_font")); + int base_font_size = get_theme_font_size(SNAME("normal_font_size")); + + // Update fonts. + if (p_frame->first_invalid_font_line != p_frame->lines.size()) { + for (int i = p_frame->first_invalid_font_line; i < p_frame->lines.size(); i++) { + _update_line_font(p_frame, i, base_font, base_font_size); + } + p_frame->first_resized_line = p_frame->first_invalid_font_line; + p_frame->first_invalid_font_line = p_frame->lines.size(); + } + if (p_frame->first_resized_line == p_frame->lines.size()) { return; } // Resize lines without reshaping. - Size2 size = get_size(); - if (fixed_width != -1) { - size.width = fixed_width; - } 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")); - for (int i = p_frame->first_resized_line; i < p_frame->lines.size(); i++) { _resize_line(p_frame, i, base_font, base_font_size, text_rect.get_size().width - scroll_w); } int total_height = 0; if (p_frame->lines.size()) { - total_height = p_frame->lines[p_frame->lines.size() - 1].offset.y + p_frame->lines[p_frame->lines.size() - 1].text_buf->get_size().y; + total_height = p_frame->lines[p_frame->lines.size() - 1].offset.y + p_frame->lines[p_frame->lines.size() - 1].text_buf->get_size().y + p_frame->lines[p_frame->lines.size() - 1].text_buf->get_line_count() * get_theme_constant(SNAME("line_separation")); } p_frame->first_resized_line = p_frame->lines.size(); @@ -2182,21 +2325,17 @@ void RichTextLabel::_validate_line_caches(ItemFrame *p_frame) { vscroll->set_max(total_height); vscroll->set_page(text_rect.size.height); if (scroll_follow && scroll_following) { - vscroll->set_value(total_height - size.height); + vscroll->set_value(total_height); } updating_scroll = false; if (fit_content_height) { - minimum_size_changed(); + update_minimum_size(); } return; } // Shape invalid lines. - Size2 size = get_size(); - if (fixed_width != -1) { - size.width = fixed_width; - } Rect2 text_rect = _get_text_rect(); Ref<Font> base_font = get_theme_font(SNAME("normal_font")); @@ -2209,22 +2348,23 @@ void RichTextLabel::_validate_line_caches(ItemFrame *p_frame) { int total_height = 0; if (p_frame->lines.size()) { - total_height = p_frame->lines[p_frame->lines.size() - 1].offset.y + p_frame->lines[p_frame->lines.size() - 1].text_buf->get_size().y; + total_height = p_frame->lines[p_frame->lines.size() - 1].offset.y + p_frame->lines[p_frame->lines.size() - 1].text_buf->get_size().y + p_frame->lines[p_frame->lines.size() - 1].text_buf->get_line_count() * get_theme_constant(SNAME("line_separation")); } p_frame->first_invalid_line = p_frame->lines.size(); p_frame->first_resized_line = p_frame->lines.size(); + p_frame->first_invalid_font_line = p_frame->lines.size(); updating_scroll = true; vscroll->set_max(total_height); vscroll->set_page(text_rect.size.height); if (scroll_follow && scroll_following) { - vscroll->set_value(total_height - size.height); + vscroll->set_value(total_height); } updating_scroll = false; if (fit_content_height) { - minimum_size_changed(); + update_minimum_size(); } } @@ -2321,7 +2461,7 @@ void RichTextLabel::_add_item(Item *p_item, bool p_enter, bool p_ensure_newline) _invalidate_current_line(current_frame); if (fixed_width != -1) { - minimum_size_changed(); + update_minimum_size(); } } @@ -2331,7 +2471,7 @@ void RichTextLabel::_remove_item(Item *p_item, const int p_line, const int p_sub p_item->parent->subitems.erase(p_item); // If a newline was erased, all lines AFTER the newline need to be decremented. if (p_item->type == ITEM_NEWLINE) { - current_frame->lines.remove(p_line); + current_frame->lines.remove_at(p_line); for (int i = 0; i < current->subitems.size(); i++) { if (current->subitems[i]->line > p_subitem_line) { current->subitems[i]->line--; @@ -2346,9 +2486,10 @@ void RichTextLabel::_remove_item(Item *p_item, const int p_line, const int p_sub // Then remove the provided item itself. p_item->parent->subitems.erase(p_item); } + memdelete(p_item); } -void RichTextLabel::add_image(const Ref<Texture2D> &p_image, const int p_width, const int p_height, const Color &p_color, InlineAlign p_align) { +void RichTextLabel::add_image(const Ref<Texture2D> &p_image, const int p_width, const int p_height, const Color &p_color, InlineAlignment p_alignment) { if (current->type == ITEM_TABLE) { return; } @@ -2360,7 +2501,7 @@ void RichTextLabel::add_image(const Ref<Texture2D> &p_image, const int p_width, item->image = p_image; item->color = p_color; - item->inline_align = p_align; + item->inline_align = p_alignment; if (p_width > 0) { // custom width @@ -2420,7 +2561,7 @@ bool RichTextLabel::remove_line(const int p_line) { } if (!had_newline) { - current_frame->lines.remove(p_line); + current_frame->lines.remove_at(p_line); if (current_frame->lines.size() == 0) { current_frame->lines.resize(1); } @@ -2552,11 +2693,11 @@ void RichTextLabel::push_strikethrough() { _add_item(item, true); } -void RichTextLabel::push_paragraph(Align p_align, Control::TextDirection p_direction, const String &p_language, Control::StructuredTextParser p_st_parser) { +void RichTextLabel::push_paragraph(HorizontalAlignment p_alignment, Control::TextDirection p_direction, const String &p_language, Control::StructuredTextParser p_st_parser) { ERR_FAIL_COND(current->type == ITEM_TABLE); ItemParagraph *item = memnew(ItemParagraph); - item->align = p_align; + item->alignment = p_alignment; item->direction = p_direction; item->language = p_language; item->st_parser = p_st_parser; @@ -2592,13 +2733,13 @@ void RichTextLabel::push_meta(const Variant &p_meta) { _add_item(item, true); } -void RichTextLabel::push_table(int p_columns, InlineAlign p_align) { +void RichTextLabel::push_table(int p_columns, InlineAlignment p_alignment) { ERR_FAIL_COND(p_columns < 1); ItemTable *item = memnew(ItemTable); item->columns.resize(p_columns); item->total_width = 0; - item->inline_align = p_align; + item->inline_align = p_alignment; for (int i = 0; i < item->columns.size(); i++) { item->columns.write[i].expand = false; item->columns.write[i].expand_ratio = 1; @@ -2752,7 +2893,7 @@ void RichTextLabel::clear() { } if (fixed_width != -1) { - minimum_size_changed(); + update_minimum_size(); } } @@ -2769,7 +2910,7 @@ int RichTextLabel::get_tab_size() const { void RichTextLabel::set_fit_content_height(bool p_enabled) { if (p_enabled != fit_content_height) { fit_content_height = p_enabled; - minimum_size_changed(); + update_minimum_size(); } } @@ -2955,35 +3096,35 @@ void RichTextLabel::append_text(const String &p_bbcode) { columns = 1; } - int align = INLINE_ALIGN_TOP; + int alignment = INLINE_ALIGNMENT_TOP; if (subtag.size() > 2) { if (subtag[1] == "top" || subtag[1] == "t") { - align = INLINE_ALIGN_TOP_TO; + alignment = INLINE_ALIGNMENT_TOP_TO; } else if (subtag[1] == "center" || subtag[1] == "c") { - align = INLINE_ALIGN_CENTER_TO; + alignment = INLINE_ALIGNMENT_CENTER_TO; } else if (subtag[1] == "bottom" || subtag[1] == "b") { - align = INLINE_ALIGN_BOTTOM_TO; + alignment = INLINE_ALIGNMENT_BOTTOM_TO; } if (subtag[2] == "top" || subtag[2] == "t") { - align |= INLINE_ALIGN_TO_TOP; + alignment |= INLINE_ALIGNMENT_TO_TOP; } else if (subtag[2] == "center" || subtag[2] == "c") { - align |= INLINE_ALIGN_TO_CENTER; + alignment |= INLINE_ALIGNMENT_TO_CENTER; } else if (subtag[2] == "baseline" || subtag[2] == "l") { - align |= INLINE_ALIGN_TO_BASELINE; + alignment |= INLINE_ALIGNMENT_TO_BASELINE; } else if (subtag[2] == "bottom" || subtag[2] == "b") { - align |= INLINE_ALIGN_TO_BOTTOM; + alignment |= INLINE_ALIGNMENT_TO_BOTTOM; } } else if (subtag.size() > 1) { if (subtag[1] == "top" || subtag[1] == "t") { - align = INLINE_ALIGN_TOP; + alignment = INLINE_ALIGNMENT_TOP; } else if (subtag[1] == "center" || subtag[1] == "c") { - align = INLINE_ALIGN_CENTER; + alignment = INLINE_ALIGNMENT_CENTER; } else if (subtag[1] == "bottom" || subtag[1] == "b") { - align = INLINE_ALIGN_BOTTOM; + alignment = INLINE_ALIGNMENT_BOTTOM; } } - push_table(columns, (InlineAlign)align); + push_table(columns, (InlineAlignment)alignment); pos = brk_end + 1; tag_stack.push_front("table"); } else if (tag == "cell") { @@ -3047,6 +3188,12 @@ void RichTextLabel::append_text(const String &p_bbcode) { push_strikethrough(); pos = brk_end + 1; tag_stack.push_front(tag); + } else if (tag == "lb") { + add_text("["); + pos = brk_end + 1; + } else if (tag == "rb") { + add_text("]"); + pos = brk_end + 1; } else if (tag == "lrm") { add_text(String::chr(0x200E)); pos = brk_end + 1; @@ -3096,15 +3243,19 @@ void RichTextLabel::append_text(const String &p_bbcode) { add_text(String::chr(0x00AD)); pos = brk_end + 1; } else if (tag == "center") { - push_paragraph(ALIGN_CENTER); + push_paragraph(HORIZONTAL_ALIGNMENT_CENTER); pos = brk_end + 1; tag_stack.push_front(tag); } else if (tag == "fill") { - push_paragraph(ALIGN_FILL); + push_paragraph(HORIZONTAL_ALIGNMENT_FILL); + pos = brk_end + 1; + tag_stack.push_front(tag); + } else if (tag == "left") { + push_paragraph(HORIZONTAL_ALIGNMENT_LEFT); pos = brk_end + 1; tag_stack.push_front(tag); } else if (tag == "right") { - push_paragraph(ALIGN_RIGHT); + push_paragraph(HORIZONTAL_ALIGNMENT_RIGHT); pos = brk_end + 1; tag_stack.push_front(tag); } else if (tag == "ul") { @@ -3116,7 +3267,7 @@ void RichTextLabel::append_text(const String &p_bbcode) { indent_level++; push_list(indent_level, LIST_NUMBERS, false); pos = brk_end + 1; - tag_stack.push_front(tag); + tag_stack.push_front("ol"); } else if (tag == "ol type=a") { indent_level++; push_list(indent_level, LIST_LETTERS, false); @@ -3143,12 +3294,12 @@ void RichTextLabel::append_text(const String &p_bbcode) { pos = brk_end + 1; tag_stack.push_front(tag); } else if (tag == "p") { - push_paragraph(ALIGN_LEFT); + push_paragraph(HORIZONTAL_ALIGNMENT_LEFT); pos = brk_end + 1; tag_stack.push_front("p"); } else if (tag.begins_with("p ")) { Vector<String> subtag = tag.substr(2, tag.length()).split(" "); - Align align = ALIGN_LEFT; + HorizontalAlignment alignment = HORIZONTAL_ALIGNMENT_LEFT; Control::TextDirection dir = Control::TEXT_DIRECTION_INHERITED; String lang; Control::StructuredTextParser st_parser = STRUCTURED_TEXT_DEFAULT; @@ -3157,13 +3308,13 @@ void RichTextLabel::append_text(const String &p_bbcode) { if (subtag_a.size() == 2) { if (subtag_a[0] == "align") { if (subtag_a[1] == "l" || subtag_a[1] == "left") { - align = ALIGN_LEFT; + alignment = HORIZONTAL_ALIGNMENT_LEFT; } else if (subtag_a[1] == "c" || subtag_a[1] == "center") { - align = ALIGN_CENTER; + alignment = HORIZONTAL_ALIGNMENT_CENTER; } else if (subtag_a[1] == "r" || subtag_a[1] == "right") { - align = ALIGN_RIGHT; + alignment = HORIZONTAL_ALIGNMENT_RIGHT; } else if (subtag_a[1] == "f" || subtag_a[1] == "fill") { - align = ALIGN_FILL; + alignment = HORIZONTAL_ALIGNMENT_FILL; } } else if (subtag_a[0] == "dir" || subtag_a[0] == "direction") { if (subtag_a[1] == "a" || subtag_a[1] == "auto") { @@ -3194,7 +3345,7 @@ void RichTextLabel::append_text(const String &p_bbcode) { } } } - push_paragraph(align, dir, lang, st_parser); + push_paragraph(alignment, dir, lang, st_parser); pos = brk_end + 1; tag_stack.push_front("p"); } else if (tag == "url") { @@ -3262,33 +3413,33 @@ void RichTextLabel::append_text(const String &p_bbcode) { pos = end; tag_stack.push_front(bbcode_name); } else if (tag.begins_with("img")) { - int align = INLINE_ALIGN_CENTER; + int alignment = INLINE_ALIGNMENT_CENTER; if (tag.begins_with("img=")) { Vector<String> subtag = tag.substr(4, tag.length()).split(","); if (subtag.size() > 1) { if (subtag[0] == "top" || subtag[0] == "t") { - align = INLINE_ALIGN_TOP_TO; + alignment = INLINE_ALIGNMENT_TOP_TO; } else if (subtag[0] == "center" || subtag[0] == "c") { - align = INLINE_ALIGN_CENTER_TO; + alignment = INLINE_ALIGNMENT_CENTER_TO; } else if (subtag[0] == "bottom" || subtag[0] == "b") { - align = INLINE_ALIGN_BOTTOM_TO; + alignment = INLINE_ALIGNMENT_BOTTOM_TO; } if (subtag[1] == "top" || subtag[1] == "t") { - align |= INLINE_ALIGN_TO_TOP; + alignment |= INLINE_ALIGNMENT_TO_TOP; } else if (subtag[1] == "center" || subtag[1] == "c") { - align |= INLINE_ALIGN_TO_CENTER; + alignment |= INLINE_ALIGNMENT_TO_CENTER; } else if (subtag[1] == "baseline" || subtag[1] == "l") { - align |= INLINE_ALIGN_TO_BASELINE; + alignment |= INLINE_ALIGNMENT_TO_BASELINE; } else if (subtag[1] == "bottom" || subtag[1] == "b") { - align |= INLINE_ALIGN_TO_BOTTOM; + alignment |= INLINE_ALIGNMENT_TO_BOTTOM; } } else if (subtag.size() > 0) { if (subtag[0] == "top" || subtag[0] == "t") { - align = INLINE_ALIGN_TOP; + alignment = INLINE_ALIGNMENT_TOP; } else if (subtag[0] == "center" || subtag[0] == "c") { - align = INLINE_ALIGN_CENTER; + alignment = INLINE_ALIGNMENT_CENTER; } else if (subtag[0] == "bottom" || subtag[0] == "b") { - align = INLINE_ALIGN_BOTTOM; + alignment = INLINE_ALIGNMENT_BOTTOM; } } } @@ -3330,7 +3481,7 @@ void RichTextLabel::append_text(const String &p_bbcode) { } } - add_image(texture, width, height, color, (InlineAlign)align); + add_image(texture, width, height, color, (InlineAlignment)alignment); } pos = end; @@ -3524,7 +3675,7 @@ void RichTextLabel::append_text(const String &p_bbcode) { pos = brk_pos + 1; } else { String identifier = expr[0]; - expr.remove(0); + expr.remove_at(0); Dictionary properties = parse_expressions_for_values(expr); Ref<RichTextEffect> effect = _get_custom_effect_by_code(identifier); @@ -3578,7 +3729,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; + line_offset += main->lines[i].text_buf->get_line_size(j).y + get_theme_constant(SNAME("line_separation")); } vscroll->set_value(main->lines[i].offset.y + line_offset); return; @@ -3587,6 +3738,28 @@ void RichTextLabel::scroll_to_line(int p_line) { } } +float RichTextLabel::get_line_offset(int p_line) { + int line_count = 0; + for (int i = 0; i < main->lines.size(); i++) { + 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")); + } + return main->lines[i].offset.y + line_offset; + } + line_count += main->lines[i].text_buf->get_line_count(); + } + return 0; +} + +float RichTextLabel::get_paragraph_offset(int p_paragraph) { + if (0 <= p_paragraph && p_paragraph < main->lines.size()) { + return main->lines[p_paragraph].offset.y; + } + return 0; +} + int RichTextLabel::get_line_count() const { int line_count = 0; for (int i = 0; i < main->lines.size(); i++) { @@ -3623,6 +3796,29 @@ void RichTextLabel::set_deselect_on_focus_loss_enabled(const bool p_enabled) { } } +Variant RichTextLabel::get_drag_data(const Point2 &p_point) { + if (selection.drag_attempt && selection.enabled) { + String t = get_selected_text(); + Label *l = memnew(Label); + l->set_text(t); + set_drag_preview(l); + return t; + } + + return Variant(); +} + +bool RichTextLabel::_is_click_inside_selection() const { + if (selection.active && selection.enabled && selection.click_frame && selection.from_frame && selection.to_frame) { + const Line &l_click = selection.click_frame->lines[selection.click_line]; + const Line &l_from = selection.from_frame->lines[selection.from_line]; + const Line &l_to = selection.to_frame->lines[selection.to_line]; + return (l_click.char_offset + selection.click_char >= l_from.char_offset + selection.from_char) && (l_click.char_offset + selection.click_char <= l_to.char_offset + selection.to_char); + } else { + return false; + } +} + bool RichTextLabel::_search_table(ItemTable *p_table, List<Item *>::Element *p_from, const String &p_string, bool p_reverse_search) { List<Item *>::Element *E = p_from; while (E != nullptr) { @@ -3798,45 +3994,32 @@ String RichTextLabel::_get_line_text(ItemFrame *p_frame, int p_line, Selection p } } for (Item *it = l.from; it && it != it_to; it = _get_next_item(it)) { + if (it->type == ITEM_TABLE) { + ItemTable *table = static_cast<ItemTable *>(it); + for (Item *E : table->subitems) { + ERR_CONTINUE(E->type != ITEM_FRAME); // Children should all be frames. + ItemFrame *frame = static_cast<ItemFrame *>(E); + for (int i = 0; i < frame->lines.size(); i++) { + text += _get_line_text(frame, i, p_selection); + } + } + } if ((p_selection.to_item != nullptr) && (p_selection.to_item->index < l.from->index)) { - break; + continue; } if ((p_selection.from_item != nullptr) && (p_selection.from_item->index >= end_idx)) { - break; + continue; } - switch (it->type) { - case ITEM_NEWLINE: { - text += "\n"; - } break; - case ITEM_TEXT: { - ItemText *t = (ItemText *)it; - text += t->text; - } break; - case ITEM_IMAGE: { - text += " "; - } break; - case ITEM_TABLE: { - ItemTable *table = static_cast<ItemTable *>(it); - int idx = 0; - int col_count = table->columns.size(); - for (Item *E : table->subitems) { - ERR_CONTINUE(E->type != ITEM_FRAME); // Children should all be frames. - ItemFrame *frame = static_cast<ItemFrame *>(E); - int column = idx % col_count; - - for (int i = 0; i < frame->lines.size(); i++) { - text += _get_line_text(frame, i, p_selection); - } - if (column == col_count - 1) { - text += "\n"; - } else { - text += " "; - } - idx++; - } - } break; - default: - break; + if (it->type == ITEM_DROPCAP) { + const ItemDropcap *dc = static_cast<ItemDropcap *>(it); + text += dc->text; + } else if (it->type == ITEM_TEXT) { + const ItemText *t = static_cast<ItemText *>(it); + text += t->text; + } else if (it->type == ITEM_NEWLINE) { + text += "\n"; + } else if (it->type == ITEM_IMAGE) { + text += " "; } } if ((l.from != nullptr) && (p_frame == p_selection.to_frame) && (p_selection.to_item != nullptr) && (p_selection.to_item->index >= l.from->index) && (p_selection.to_item->index < end_idx)) { @@ -3863,7 +4046,7 @@ String RichTextLabel::get_selected_text() const { void RichTextLabel::selection_copy() { String text = get_selected_text(); - if (text != "") { + if (!text.is_empty()) { DisplayServer::get_singleton()->clipboard_set(text); } } @@ -3894,7 +4077,7 @@ int RichTextLabel::get_selection_to() const { void RichTextLabel::set_text(const String &p_bbcode) { text = p_bbcode; - if (is_inside_tree() && use_bbcode) { + if (use_bbcode) { parse_bbcode(p_bbcode); } else { // raw text clear(); @@ -3994,6 +4177,19 @@ String RichTextLabel::get_language() const { return language; } +void RichTextLabel::set_autowrap_mode(RichTextLabel::AutowrapMode p_mode) { + if (autowrap_mode != p_mode) { + autowrap_mode = p_mode; + main->first_invalid_line = 0; //invalidate ALL + _validate_line_caches(main); + update(); + } +} + +RichTextLabel::AutowrapMode RichTextLabel::get_autowrap_mode() const { + return autowrap_mode; +} + void RichTextLabel::set_percent_visible(float p_percent) { if (percent_visible != p_percent) { if (p_percent < 0 || p_percent >= 1) { @@ -4003,8 +4199,10 @@ void RichTextLabel::set_percent_visible(float p_percent) { visible_characters = get_total_character_count() * p_percent; percent_visible = p_percent; } - main->first_invalid_line = 0; //invalidate ALL - _validate_line_caches(main); + if (visible_chars_behavior == VC_CHARS_BEFORE_SHAPING) { + main->first_invalid_line = 0; //invalidate ALL + _validate_line_caches(main); + } update(); } } @@ -4015,7 +4213,7 @@ float RichTextLabel::get_percent_visible() const { void RichTextLabel::set_effects(Array p_effects) { custom_effects = p_effects; - if ((text != "") && use_bbcode) { + if ((!text.is_empty()) && use_bbcode) { parse_bbcode(text); } } @@ -4030,7 +4228,7 @@ void RichTextLabel::install_effect(const Variant effect) { if (rteffect.is_valid()) { custom_effects.push_back(effect); - if ((text != "") && use_bbcode) { + if ((!text.is_empty()) && use_bbcode) { parse_bbcode(text); } } @@ -4039,14 +4237,22 @@ void RichTextLabel::install_effect(const Variant effect) { int RichTextLabel::get_content_height() const { int total_height = 0; if (main->lines.size()) { - total_height = main->lines[main->lines.size() - 1].offset.y + main->lines[main->lines.size() - 1].text_buf->get_size().y; + total_height = main->lines[main->lines.size() - 1].offset.y + main->lines[main->lines.size() - 1].text_buf->get_size().y + main->lines[main->lines.size() - 1].text_buf->get_line_count() * get_theme_constant(SNAME("line_separation")); } return total_height; } +int RichTextLabel::get_content_width() const { + int total_width = 0; + for (int i = 0; i < main->lines.size(); i++) { + total_width = MAX(total_width, main->lines[i].offset.x + main->lines[i].text_buf->get_size().x); + } + return total_width; +} + #ifndef DISABLE_DEPRECATED // People will be very angry, if their texts get erased, because of #39148. (3.x -> 4.0) -// Altough some people may not used bbcode_text, so we only overwrite, if bbcode_text is not empty +// Although some people may not used bbcode_text, so we only overwrite, if bbcode_text is not empty. bool RichTextLabel::_set(const StringName &p_name, const Variant &p_value) { if (p_name == "bbcode_text" && !((String)p_value).is_empty()) { set_text(p_value); @@ -4060,7 +4266,7 @@ void RichTextLabel::_bind_methods() { ClassDB::bind_method(D_METHOD("get_parsed_text"), &RichTextLabel::get_parsed_text); ClassDB::bind_method(D_METHOD("add_text", "text"), &RichTextLabel::add_text); ClassDB::bind_method(D_METHOD("set_text", "text"), &RichTextLabel::set_text); - ClassDB::bind_method(D_METHOD("add_image", "image", "width", "height", "color", "inline_align"), &RichTextLabel::add_image, DEFVAL(0), DEFVAL(0), DEFVAL(Color(1.0, 1.0, 1.0)), DEFVAL(INLINE_ALIGN_CENTER)); + ClassDB::bind_method(D_METHOD("add_image", "image", "width", "height", "color", "inline_align"), &RichTextLabel::add_image, DEFVAL(0), DEFVAL(0), DEFVAL(Color(1.0, 1.0, 1.0)), DEFVAL(INLINE_ALIGNMENT_CENTER)); ClassDB::bind_method(D_METHOD("newline"), &RichTextLabel::add_newline); ClassDB::bind_method(D_METHOD("remove_line", "line"), &RichTextLabel::remove_line); ClassDB::bind_method(D_METHOD("push_font", "font"), &RichTextLabel::push_font); @@ -4074,13 +4280,13 @@ void RichTextLabel::_bind_methods() { ClassDB::bind_method(D_METHOD("push_color", "color"), &RichTextLabel::push_color); ClassDB::bind_method(D_METHOD("push_outline_size", "outline_size"), &RichTextLabel::push_outline_size); ClassDB::bind_method(D_METHOD("push_outline_color", "color"), &RichTextLabel::push_outline_color); - ClassDB::bind_method(D_METHOD("push_paragraph", "align", "base_direction", "language", "st_parser"), &RichTextLabel::push_paragraph, DEFVAL(TextServer::DIRECTION_AUTO), DEFVAL(""), DEFVAL(STRUCTURED_TEXT_DEFAULT)); + ClassDB::bind_method(D_METHOD("push_paragraph", "alignment", "base_direction", "language", "st_parser"), &RichTextLabel::push_paragraph, DEFVAL(TextServer::DIRECTION_AUTO), DEFVAL(""), DEFVAL(STRUCTURED_TEXT_DEFAULT)); ClassDB::bind_method(D_METHOD("push_indent", "level"), &RichTextLabel::push_indent); ClassDB::bind_method(D_METHOD("push_list", "level", "type", "capitalize"), &RichTextLabel::push_list); ClassDB::bind_method(D_METHOD("push_meta", "data"), &RichTextLabel::push_meta); ClassDB::bind_method(D_METHOD("push_underline"), &RichTextLabel::push_underline); ClassDB::bind_method(D_METHOD("push_strikethrough"), &RichTextLabel::push_strikethrough); - ClassDB::bind_method(D_METHOD("push_table", "columns", "inline_align"), &RichTextLabel::push_table, DEFVAL(INLINE_ALIGN_TOP)); + ClassDB::bind_method(D_METHOD("push_table", "columns", "inline_align"), &RichTextLabel::push_table, DEFVAL(INLINE_ALIGNMENT_TOP)); ClassDB::bind_method(D_METHOD("push_dropcap", "string", "font", "size", "dropcap_margins", "color", "outline_size", "outline_color"), &RichTextLabel::push_dropcap, DEFVAL(Rect2()), DEFVAL(Color(1, 1, 1)), DEFVAL(0), DEFVAL(Color(0, 0, 0, 0))); ClassDB::bind_method(D_METHOD("set_table_column_expand", "column", "expand", "ratio"), &RichTextLabel::set_table_column_expand); ClassDB::bind_method(D_METHOD("set_cell_row_background_color", "odd_row_bg", "even_row_bg"), &RichTextLabel::set_cell_row_background_color); @@ -4103,6 +4309,9 @@ void RichTextLabel::_bind_methods() { ClassDB::bind_method(D_METHOD("set_language", "language"), &RichTextLabel::set_language); ClassDB::bind_method(D_METHOD("get_language"), &RichTextLabel::get_language); + ClassDB::bind_method(D_METHOD("set_autowrap_mode", "autowrap_mode"), &RichTextLabel::set_autowrap_mode); + ClassDB::bind_method(D_METHOD("get_autowrap_mode"), &RichTextLabel::get_autowrap_mode); + ClassDB::bind_method(D_METHOD("set_meta_underline", "enable"), &RichTextLabel::set_meta_underline); ClassDB::bind_method(D_METHOD("is_meta_underlined"), &RichTextLabel::is_meta_underlined); @@ -4115,7 +4324,7 @@ void RichTextLabel::_bind_methods() { ClassDB::bind_method(D_METHOD("set_scroll_follow", "follow"), &RichTextLabel::set_scroll_follow); ClassDB::bind_method(D_METHOD("is_scroll_following"), &RichTextLabel::is_scroll_following); - ClassDB::bind_method(D_METHOD("get_v_scroll"), &RichTextLabel::get_v_scroll); + ClassDB::bind_method(D_METHOD("get_v_scroll_bar"), &RichTextLabel::get_v_scroll_bar); ClassDB::bind_method(D_METHOD("scroll_to_line", "line"), &RichTextLabel::scroll_to_line); ClassDB::bind_method(D_METHOD("scroll_to_paragraph", "paragraph"), &RichTextLabel::scroll_to_paragraph); @@ -4145,9 +4354,14 @@ void RichTextLabel::_bind_methods() { ClassDB::bind_method(D_METHOD("set_visible_characters", "amount"), &RichTextLabel::set_visible_characters); ClassDB::bind_method(D_METHOD("get_visible_characters"), &RichTextLabel::get_visible_characters); + 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("get_character_line", "character"), &RichTextLabel::get_character_line); + ClassDB::bind_method(D_METHOD("get_character_paragraph", "character"), &RichTextLabel::get_character_paragraph); ClassDB::bind_method(D_METHOD("get_total_character_count"), &RichTextLabel::get_total_character_count); ClassDB::bind_method(D_METHOD("set_use_bbcode", "enable"), &RichTextLabel::set_use_bbcode); @@ -4160,6 +4374,10 @@ void RichTextLabel::_bind_methods() { ClassDB::bind_method(D_METHOD("get_visible_paragraph_count"), &RichTextLabel::get_visible_paragraph_count); ClassDB::bind_method(D_METHOD("get_content_height"), &RichTextLabel::get_content_height); + ClassDB::bind_method(D_METHOD("get_content_width"), &RichTextLabel::get_content_width); + + ClassDB::bind_method(D_METHOD("get_line_offset", "line"), &RichTextLabel::get_line_offset); + ClassDB::bind_method(D_METHOD("get_paragraph_offset", "paragraph"), &RichTextLabel::get_paragraph_offset); ClassDB::bind_method(D_METHOD("parse_expressions_for_values", "expressions"), &RichTextLabel::parse_expressions_for_values); @@ -4167,28 +4385,29 @@ void RichTextLabel::_bind_methods() { ClassDB::bind_method(D_METHOD("get_effects"), &RichTextLabel::get_effects); ClassDB::bind_method(D_METHOD("install_effect", "effect"), &RichTextLabel::install_effect); - ADD_PROPERTY(PropertyInfo(Variant::INT, "visible_characters", PROPERTY_HINT_RANGE, "-1,128000,1"), "set_visible_characters", "get_visible_characters"); - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "percent_visible", PROPERTY_HINT_RANGE, "0,1,0.001"), "set_percent_visible", "get_percent_visible"); + // Note: set "bbcode_enabled" first, to avoid unnecessery "text" resets. + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "bbcode_enabled"), "set_use_bbcode", "is_using_bbcode"); - ADD_PROPERTY(PropertyInfo(Variant::BOOL, "meta_underlined"), "set_meta_underline", "is_meta_underlined"); 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, "bbcode_enabled"), "set_use_bbcode", "is_using_bbcode"); - 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::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::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_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_GROUP("Locale", ""); 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"), "set_language", "get_language"); + ADD_PROPERTY(PropertyInfo(Variant::STRING, "language", PROPERTY_HINT_LOCALE_ID, ""), "set_language", "get_language"); ADD_GROUP("Structured Text", "structured_text_"); ADD_PROPERTY(PropertyInfo(Variant::INT, "structured_text_bidi_override", PROPERTY_HINT_ENUM, "Default,URI,File,Email,List,None,Custom"), "set_structured_text_bidi_override", "get_structured_text_bidi_override"); @@ -4198,10 +4417,10 @@ void RichTextLabel::_bind_methods() { ADD_SIGNAL(MethodInfo("meta_hover_started", PropertyInfo(Variant::NIL, "meta", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NIL_IS_VARIANT))); ADD_SIGNAL(MethodInfo("meta_hover_ended", PropertyInfo(Variant::NIL, "meta", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NIL_IS_VARIANT))); - BIND_ENUM_CONSTANT(ALIGN_LEFT); - BIND_ENUM_CONSTANT(ALIGN_CENTER); - BIND_ENUM_CONSTANT(ALIGN_RIGHT); - BIND_ENUM_CONSTANT(ALIGN_FILL); + BIND_ENUM_CONSTANT(AUTOWRAP_OFF); + BIND_ENUM_CONSTANT(AUTOWRAP_ARBITRARY); + BIND_ENUM_CONSTANT(AUTOWRAP_WORD); + BIND_ENUM_CONSTANT(AUTOWRAP_WORD_SMART); BIND_ENUM_CONSTANT(LIST_NUMBERS); BIND_ENUM_CONSTANT(LIST_LETTERS); @@ -4234,6 +4453,25 @@ void RichTextLabel::_bind_methods() { BIND_ENUM_CONSTANT(ITEM_META); BIND_ENUM_CONSTANT(ITEM_DROPCAP); BIND_ENUM_CONSTANT(ITEM_CUSTOMFX); + + BIND_ENUM_CONSTANT(VC_CHARS_BEFORE_SHAPING); + BIND_ENUM_CONSTANT(VC_CHARS_AFTER_SHAPING); + BIND_ENUM_CONSTANT(VC_GLYPHS_AUTO); + BIND_ENUM_CONSTANT(VC_GLYPHS_LTR); + BIND_ENUM_CONSTANT(VC_GLYPHS_RTL); +} + +RichTextLabel::VisibleCharactersBehavior RichTextLabel::get_visible_characters_behavior() const { + return visible_chars_behavior; +} + +void RichTextLabel::set_visible_characters_behavior(RichTextLabel::VisibleCharactersBehavior p_behavior) { + if (visible_chars_behavior != p_behavior) { + visible_chars_behavior = p_behavior; + main->first_invalid_line = 0; //invalidate ALL + _validate_line_caches(main); + update(); + } } void RichTextLabel::set_visible_characters(int p_visible) { @@ -4247,8 +4485,10 @@ void RichTextLabel::set_visible_characters(int p_visible) { percent_visible = (float)p_visible / (float)total_char_count; } } - main->first_invalid_line = 0; //invalidate ALL - _validate_line_caches(main); + if (visible_chars_behavior == VC_CHARS_BEFORE_SHAPING) { + main->first_invalid_line = 0; //invalidate ALL + _validate_line_caches(main); + } update(); } } @@ -4257,6 +4497,36 @@ int RichTextLabel::get_visible_characters() const { return visible_characters; } +int RichTextLabel::get_character_line(int p_char) { + int line_count = 0; + for (int i = 0; i < main->lines.size(); i++) { + if (main->lines[i].char_offset < p_char && p_char <= main->lines[i].char_offset + main->lines[i].char_count) { + for (int j = 0; j < main->lines[i].text_buf->get_line_count(); j++) { + Vector2i range = main->lines[i].text_buf->get_line_range(j); + if (main->lines[i].char_offset + range.x < p_char && p_char <= main->lines[i].char_offset + range.y) { + return line_count; + } + line_count++; + } + } else { + line_count += main->lines[i].text_buf->get_line_count(); + } + } + return -1; +} + +int RichTextLabel::get_character_paragraph(int p_char) { + int para_count = 0; + for (int i = 0; i < main->lines.size(); i++) { + if (main->lines[i].char_offset < p_char && p_char <= main->lines[i].char_offset + main->lines[i].char_count) { + return para_count; + } else { + para_count++; + } + } + return -1; +} + int RichTextLabel::get_total_character_count() const { // Note: Do not use line buffer "char_count", it includes only visible characters. int tc = 0; @@ -4276,9 +4546,25 @@ int RichTextLabel::get_total_character_count() const { return tc; } +int RichTextLabel::get_total_glyph_count() const { + int tg = 0; + Item *it = main; + while (it) { + if (it->type == ITEM_FRAME) { + ItemFrame *f = static_cast<ItemFrame *>(it); + for (int i = 0; i < f->lines.size(); i++) { + tg += TS->shaped_text_get_glyph_count(f->lines[i].text_buf->get_rid()); + } + } + it = _get_next_item(it, true); + } + + return tg; +} + void RichTextLabel::set_fixed_size_to_width(int p_width) { fixed_width = p_width; - minimum_size_changed(); + update_minimum_size(); } Size2 RichTextLabel::get_minimum_size() const { @@ -4289,7 +4575,7 @@ Size2 RichTextLabel::get_minimum_size() const { size.x += fixed_width; } - if (fixed_width != -1 || fit_content_height) { + if (fit_content_height) { const_cast<RichTextLabel *>(this)->_validate_line_caches(main); size.y += get_content_height(); } @@ -4430,7 +4716,7 @@ Dictionary RichTextLabel::parse_expressions_for_values(Vector<String> p_expressi return d; } -RichTextLabel::RichTextLabel() { +RichTextLabel::RichTextLabel(const String &p_text) { main = memnew(ItemFrame); main->index = 0; current = main; @@ -4438,6 +4724,7 @@ RichTextLabel::RichTextLabel() { main->lines.write[0].from = main; main->first_invalid_line = 0; main->first_resized_line = 0; + main->first_invalid_font_line = 0; current_frame = main; vscroll = memnew(VScrollBar); @@ -4451,6 +4738,8 @@ RichTextLabel::RichTextLabel() { vscroll->set_step(1); vscroll->hide(); + set_text(p_text); + set_clip_contents(true); } diff --git a/scene/gui/rich_text_label.h b/scene/gui/rich_text_label.h index f3c4c11cc8..076b68a0da 100644 --- a/scene/gui/rich_text_label.h +++ b/scene/gui/rich_text_label.h @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ @@ -39,11 +39,11 @@ class RichTextLabel : public Control { GDCLASS(RichTextLabel, Control); public: - enum Align { - ALIGN_LEFT, - ALIGN_CENTER, - ALIGN_RIGHT, - ALIGN_FILL + enum AutowrapMode { + AUTOWRAP_OFF, + AUTOWRAP_ARBITRARY, + AUTOWRAP_WORD, + AUTOWRAP_WORD_SMART }; enum ListType { @@ -82,6 +82,14 @@ public: ITEM_CUSTOMFX }; + enum VisibleCharactersBehavior { + VC_CHARS_BEFORE_SHAPING, + VC_CHARS_AFTER_SHAPING, + VC_GLYPHS_AUTO, + VC_GLYPHS_LTR, + VC_GLYPHS_RTL, + }; + protected: void _notification(int p_what); static void _bind_methods(); @@ -128,6 +136,7 @@ private: Vector<Line> lines; int first_invalid_line = 0; + int first_invalid_font_line = 0; int first_resized_line = 0; ItemFrame *parent_frame = nullptr; @@ -160,7 +169,7 @@ private: struct ItemImage : public Item { Ref<Texture2D> image; - InlineAlign inline_align = INLINE_ALIGN_CENTER; + InlineAlignment inline_align = INLINE_ALIGNMENT_CENTER; Size2 size; Color color; ItemImage() { type = ITEM_IMAGE; } @@ -210,7 +219,7 @@ private: }; struct ItemParagraph : public Item { - Align align = ALIGN_LEFT; + HorizontalAlignment alignment = HORIZONTAL_ALIGNMENT_LEFT; String language; Control::TextDirection direction = Control::TEXT_DIRECTION_AUTO; Control::StructuredTextParser st_parser = STRUCTURED_TEXT_DEFAULT; @@ -247,7 +256,7 @@ private: int total_width = 0; int total_height = 0; - InlineAlign inline_align = INLINE_ALIGN_TOP; + InlineAlignment inline_align = INLINE_ALIGNMENT_TOP; ItemTable() { type = ITEM_TABLE; } }; @@ -344,6 +353,8 @@ private: VScrollBar *vscroll = nullptr; + AutowrapMode autowrap_mode = AUTOWRAP_WORD_SMART; + bool scroll_visible = false; bool scroll_follow = false; bool scroll_following = false; @@ -360,7 +371,7 @@ private: bool underline_meta = true; bool override_selected_font_color = false; - Align default_align = ALIGN_LEFT; + HorizontalAlignment default_alignment = HORIZONTAL_ALIGNMENT_LEFT; ItemMeta *meta_hovering = nullptr; Variant current_meta; @@ -396,6 +407,7 @@ private: bool active = false; // anything selected? i.e. from, to, etc. valid? bool enabled = false; // allow selections? + bool drag_attempt = false; }; Selection selection; @@ -403,7 +415,9 @@ private: int visible_characters = -1; float percent_visible = 1.0; + VisibleCharactersBehavior visible_chars_behavior = 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); String _get_line_text(ItemFrame *p_frame, int p_line, Selection p_sel) const; @@ -412,7 +426,8 @@ private: void _shape_line(ItemFrame *p_frame, int p_line, const Ref<Font> &p_base_font, int p_base_font_size, int p_width, int *r_char_offset); void _resize_line(ItemFrame *p_frame, int p_line, const Ref<Font> &p_base_font, int p_base_font_size, int p_width); - 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, bool p_shadow_as_outline, const Point2 &shadow_ofs); + 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); String _roman(int p_num, bool p_capitalize) const; @@ -428,7 +443,7 @@ private: ItemDropcap *_find_dc_item(Item *p_item); int _find_list(Item *p_item, Vector<int> &r_index, Vector<ItemList *> &r_list); int _find_margin(Item *p_item, const Ref<Font> &p_base_font, int p_base_font_size); - Align _find_align(Item *p_item); + HorizontalAlignment _find_alignment(Item *p_item); TextServer::Direction _find_direction(Item *p_item); Control::StructuredTextParser _find_stt(Item *p_item); String _find_language(Item *p_item); @@ -469,7 +484,7 @@ private: public: String get_parsed_text() const; void add_text(const String &p_text); - void add_image(const Ref<Texture2D> &p_image, const int p_width = 0, const int p_height = 0, const Color &p_color = Color(1.0, 1.0, 1.0), InlineAlign p_align = INLINE_ALIGN_CENTER); + void add_image(const Ref<Texture2D> &p_image, const int p_width = 0, const int p_height = 0, const Color &p_color = Color(1.0, 1.0, 1.0), InlineAlignment p_alignment = INLINE_ALIGNMENT_CENTER); void add_newline(); bool remove_line(const int p_line); void push_dropcap(const String &p_string, const Ref<Font> &p_font, int p_size, const Rect2 &p_dropcap_margins = Rect2(), const Color &p_color = Color(1, 1, 1), int p_ol_size = 0, const Color &p_ol_color = Color(0, 0, 0, 0)); @@ -486,11 +501,11 @@ public: void push_outline_color(const Color &p_color); void push_underline(); void push_strikethrough(); - void push_paragraph(Align p_align, Control::TextDirection p_direction = Control::TEXT_DIRECTION_INHERITED, const String &p_language = "", Control::StructuredTextParser p_st_parser = STRUCTURED_TEXT_DEFAULT); + void push_paragraph(HorizontalAlignment p_alignment, Control::TextDirection p_direction = Control::TEXT_DIRECTION_INHERITED, const String &p_language = "", Control::StructuredTextParser p_st_parser = STRUCTURED_TEXT_DEFAULT); void push_indent(int p_level); void push_list(int p_level, ListType p_list, bool p_capitalize); void push_meta(const Variant &p_meta); - void push_table(int p_columns, InlineAlign p_align = INLINE_ALIGN_TOP); + void push_table(int p_columns, InlineAlignment p_alignment = INLINE_ALIGNMENT_TOP); void push_fade(int p_start_index, int p_length); void push_shake(int p_strength, float p_rate); void push_wave(float p_frequency, float p_amplitude); @@ -536,15 +551,20 @@ public: int get_paragraph_count() const; int get_visible_paragraph_count() const; + float get_line_offset(int p_line); + float get_paragraph_offset(int p_paragraph); + void scroll_to_line(int p_line); int get_line_count() const; int get_visible_line_count() const; int get_content_height() const; + int get_content_width() const; - VScrollBar *get_v_scroll() { return vscroll; } + VScrollBar *get_v_scroll_bar() { return vscroll; } virtual CursorShape get_cursor_shape(const Point2 &p_pos) const override; + virtual Variant get_drag_data(const Point2 &p_point) override; void set_selection_enabled(bool p_enabled); bool is_selection_enabled() const; @@ -570,6 +590,9 @@ public: void set_language(const String &p_language); String get_language() const; + void set_autowrap_mode(AutowrapMode p_mode); + AutowrapMode get_autowrap_mode() const; + void set_structured_text_bidi_override(Control::StructuredTextParser p_parser); Control::StructuredTextParser get_structured_text_bidi_override() const; @@ -578,11 +601,17 @@ public: void set_visible_characters(int p_visible); int get_visible_characters() const; + int get_character_line(int p_char); + int get_character_paragraph(int p_char); int get_total_character_count() const; + int get_total_glyph_count() const; void set_percent_visible(float p_percent); float get_percent_visible() const; + VisibleCharactersBehavior get_visible_characters_behavior() const; + void set_visible_characters_behavior(VisibleCharactersBehavior p_behavior); + void set_effects(Array p_effects); Array get_effects(); @@ -591,12 +620,13 @@ public: void set_fixed_size_to_width(int p_width); virtual Size2 get_minimum_size() const override; - RichTextLabel(); + RichTextLabel(const String &p_text = String()); ~RichTextLabel(); }; -VARIANT_ENUM_CAST(RichTextLabel::Align); +VARIANT_ENUM_CAST(RichTextLabel::AutowrapMode); VARIANT_ENUM_CAST(RichTextLabel::ListType); VARIANT_ENUM_CAST(RichTextLabel::ItemType); +VARIANT_ENUM_CAST(RichTextLabel::VisibleCharactersBehavior); #endif // RICH_TEXT_LABEL_H diff --git a/scene/gui/scroll_bar.cpp b/scene/gui/scroll_bar.cpp index 4a3a6837d5..e1b0e8cca8 100644 --- a/scene/gui/scroll_bar.cpp +++ b/scene/gui/scroll_bar.cpp @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ @@ -54,17 +54,17 @@ void ScrollBar::gui_input(const Ref<InputEvent> &p_event) { if (b.is_valid()) { accept_event(); - if (b->get_button_index() == MOUSE_BUTTON_WHEEL_DOWN && b->is_pressed()) { + if (b->get_button_index() == MouseButton::WHEEL_DOWN && b->is_pressed()) { set_value(get_value() + get_page() / 4.0); accept_event(); } - if (b->get_button_index() == MOUSE_BUTTON_WHEEL_UP && b->is_pressed()) { + if (b->get_button_index() == MouseButton::WHEEL_UP && b->is_pressed()) { set_value(get_value() - get_page() / 4.0); accept_event(); } - if (b->get_button_index() != MOUSE_BUTTON_LEFT) { + if (b->get_button_index() != MouseButton::LEFT) { return; } @@ -218,195 +218,198 @@ void ScrollBar::gui_input(const Ref<InputEvent> &p_event) { } void ScrollBar::_notification(int p_what) { - if (p_what == NOTIFICATION_DRAW) { - RID ci = get_canvas_item(); + switch (p_what) { + case NOTIFICATION_DRAW: { + RID ci = get_canvas_item(); - Ref<Texture2D> decr, incr; + Ref<Texture2D> decr, incr; - if (decr_active) { - decr = get_theme_icon(SNAME("decrement_pressed")); - } else if (highlight == HIGHLIGHT_DECR) { - decr = get_theme_icon(SNAME("decrement_highlight")); - } else { - decr = get_theme_icon(SNAME("decrement")); - } + if (decr_active) { + decr = get_theme_icon(SNAME("decrement_pressed")); + } else if (highlight == HIGHLIGHT_DECR) { + decr = get_theme_icon(SNAME("decrement_highlight")); + } else { + decr = get_theme_icon(SNAME("decrement")); + } - if (incr_active) { - incr = get_theme_icon(SNAME("increment_pressed")); - } else if (highlight == HIGHLIGHT_INCR) { - incr = get_theme_icon(SNAME("increment_highlight")); - } else { - incr = get_theme_icon(SNAME("increment")); - } + if (incr_active) { + incr = get_theme_icon(SNAME("increment_pressed")); + } else if (highlight == HIGHLIGHT_INCR) { + incr = get_theme_icon(SNAME("increment_highlight")); + } else { + incr = get_theme_icon(SNAME("increment")); + } - Ref<StyleBox> bg = has_focus() ? get_theme_stylebox(SNAME("scroll_focus")) : get_theme_stylebox(SNAME("scroll")); + Ref<StyleBox> bg = has_focus() ? get_theme_stylebox(SNAME("scroll_focus")) : get_theme_stylebox(SNAME("scroll")); - Ref<StyleBox> grabber; - if (drag.active) { - grabber = get_theme_stylebox(SNAME("grabber_pressed")); - } else if (highlight == HIGHLIGHT_RANGE) { - grabber = get_theme_stylebox(SNAME("grabber_highlight")); - } else { - grabber = get_theme_stylebox(SNAME("grabber")); - } + Ref<StyleBox> grabber; + if (drag.active) { + grabber = get_theme_stylebox(SNAME("grabber_pressed")); + } else if (highlight == HIGHLIGHT_RANGE) { + grabber = get_theme_stylebox(SNAME("grabber_highlight")); + } else { + grabber = get_theme_stylebox(SNAME("grabber")); + } - Point2 ofs; + Point2 ofs; - decr->draw(ci, Point2()); + decr->draw(ci, Point2()); - if (orientation == HORIZONTAL) { - ofs.x += decr->get_width(); - } else { - ofs.y += decr->get_height(); - } + if (orientation == HORIZONTAL) { + ofs.x += decr->get_width(); + } else { + ofs.y += decr->get_height(); + } - Size2 area = get_size(); + Size2 area = get_size(); - if (orientation == HORIZONTAL) { - area.width -= incr->get_width() + decr->get_width(); - } else { - area.height -= incr->get_height() + decr->get_height(); - } + if (orientation == HORIZONTAL) { + area.width -= incr->get_width() + decr->get_width(); + } else { + area.height -= incr->get_height() + decr->get_height(); + } - bg->draw(ci, Rect2(ofs, area)); + bg->draw(ci, Rect2(ofs, area)); - if (orientation == HORIZONTAL) { - ofs.width += area.width; - } else { - ofs.height += area.height; - } + if (orientation == HORIZONTAL) { + ofs.width += area.width; + } else { + ofs.height += area.height; + } - incr->draw(ci, ofs); - Rect2 grabber_rect; + incr->draw(ci, ofs); + Rect2 grabber_rect; - if (orientation == HORIZONTAL) { - grabber_rect.size.width = get_grabber_size(); - grabber_rect.size.height = get_size().height; - grabber_rect.position.y = 0; - grabber_rect.position.x = get_grabber_offset() + decr->get_width() + bg->get_margin(SIDE_LEFT); - } else { - grabber_rect.size.width = get_size().width; - grabber_rect.size.height = get_grabber_size(); - grabber_rect.position.y = get_grabber_offset() + decr->get_height() + bg->get_margin(SIDE_TOP); - grabber_rect.position.x = 0; - } + if (orientation == HORIZONTAL) { + grabber_rect.size.width = get_grabber_size(); + grabber_rect.size.height = get_size().height; + grabber_rect.position.y = 0; + grabber_rect.position.x = get_grabber_offset() + decr->get_width() + bg->get_margin(SIDE_LEFT); + } else { + grabber_rect.size.width = get_size().width; + grabber_rect.size.height = get_grabber_size(); + grabber_rect.position.y = get_grabber_offset() + decr->get_height() + bg->get_margin(SIDE_TOP); + grabber_rect.position.x = 0; + } - grabber->draw(ci, grabber_rect); - } + grabber->draw(ci, grabber_rect); + } break; - if (p_what == NOTIFICATION_ENTER_TREE) { - if (has_node(drag_node_path)) { - Node *n = get_node(drag_node_path); - drag_node = Object::cast_to<Control>(n); - } + case NOTIFICATION_ENTER_TREE: { + if (has_node(drag_node_path)) { + Node *n = get_node(drag_node_path); + drag_node = Object::cast_to<Control>(n); + } - 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), varray(), CONNECT_ONESHOT); - } - } - if (p_what == NOTIFICATION_EXIT_TREE) { - if (drag_node) { - drag_node->disconnect("gui_input", callable_mp(this, &ScrollBar::_drag_node_input)); - drag_node->disconnect("tree_exiting", callable_mp(this, &ScrollBar::_drag_node_exit)); - } + 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), varray(), CONNECT_ONESHOT); + } + } break; - drag_node = nullptr; - } + case NOTIFICATION_EXIT_TREE: { + if (drag_node) { + drag_node->disconnect("gui_input", callable_mp(this, &ScrollBar::_drag_node_input)); + drag_node->disconnect("tree_exiting", callable_mp(this, &ScrollBar::_drag_node_exit)); + } - if (p_what == NOTIFICATION_INTERNAL_PHYSICS_PROCESS) { - if (scrolling) { - if (get_value() != target_scroll) { - double target = target_scroll - get_value(); - double dist = sqrt(target * target); - double vel = ((target / dist) * 500) * get_physics_process_delta_time(); + drag_node = nullptr; + } break; - if (Math::abs(vel) >= dist) { - set_value(target_scroll); + case NOTIFICATION_INTERNAL_PHYSICS_PROCESS: { + if (scrolling) { + if (get_value() != target_scroll) { + double target = target_scroll - get_value(); + double dist = sqrt(target * target); + double vel = ((target / dist) * 500) * get_physics_process_delta_time(); + + if (Math::abs(vel) >= dist) { + set_value(target_scroll); + scrolling = false; + set_physics_process_internal(false); + } else { + set_value(get_value() + vel); + } + } else { scrolling = false; set_physics_process_internal(false); - } else { - set_value(get_value() + vel); } - } else { - scrolling = false; - set_physics_process_internal(false); - } - } else if (drag_node_touching) { - if (drag_node_touching_deaccel) { - Vector2 pos = Vector2(orientation == HORIZONTAL ? get_value() : 0, orientation == VERTICAL ? get_value() : 0); - pos += drag_node_speed * get_physics_process_delta_time(); + } else if (drag_node_touching) { + if (drag_node_touching_deaccel) { + Vector2 pos = Vector2(orientation == HORIZONTAL ? get_value() : 0, orientation == VERTICAL ? get_value() : 0); + pos += drag_node_speed * get_physics_process_delta_time(); - bool turnoff = false; + bool turnoff = false; - if (orientation == HORIZONTAL) { - if (pos.x < 0) { - pos.x = 0; - turnoff = true; - } + if (orientation == HORIZONTAL) { + if (pos.x < 0) { + pos.x = 0; + turnoff = true; + } - if (pos.x > (get_max() - get_page())) { - pos.x = get_max() - get_page(); - turnoff = true; - } + if (pos.x > (get_max() - get_page())) { + pos.x = get_max() - get_page(); + turnoff = true; + } - set_value(pos.x); + set_value(pos.x); - float sgn_x = drag_node_speed.x < 0 ? -1 : 1; - float val_x = Math::abs(drag_node_speed.x); - val_x -= 1000 * get_physics_process_delta_time(); + float sgn_x = drag_node_speed.x < 0 ? -1 : 1; + float val_x = Math::abs(drag_node_speed.x); + val_x -= 1000 * get_physics_process_delta_time(); - if (val_x < 0) { - turnoff = true; - } + if (val_x < 0) { + turnoff = true; + } - drag_node_speed.x = sgn_x * val_x; + drag_node_speed.x = sgn_x * val_x; - } else { - if (pos.y < 0) { - pos.y = 0; - turnoff = true; - } + } else { + if (pos.y < 0) { + pos.y = 0; + turnoff = true; + } - if (pos.y > (get_max() - get_page())) { - pos.y = get_max() - get_page(); - turnoff = true; - } + if (pos.y > (get_max() - get_page())) { + pos.y = get_max() - get_page(); + turnoff = true; + } - set_value(pos.y); + set_value(pos.y); - float sgn_y = drag_node_speed.y < 0 ? -1 : 1; - float val_y = Math::abs(drag_node_speed.y); - val_y -= 1000 * get_physics_process_delta_time(); + float sgn_y = drag_node_speed.y < 0 ? -1 : 1; + float val_y = Math::abs(drag_node_speed.y); + val_y -= 1000 * get_physics_process_delta_time(); - if (val_y < 0) { - turnoff = true; + if (val_y < 0) { + turnoff = true; + } + drag_node_speed.y = sgn_y * val_y; } - drag_node_speed.y = sgn_y * val_y; - } - if (turnoff) { - set_physics_process_internal(false); - drag_node_touching = false; - drag_node_touching_deaccel = false; - } + if (turnoff) { + set_physics_process_internal(false); + drag_node_touching = false; + drag_node_touching_deaccel = false; + } - } else { - if (time_since_motion == 0 || time_since_motion > 0.1) { - Vector2 diff = drag_node_accum - last_drag_node_accum; - last_drag_node_accum = drag_node_accum; - drag_node_speed = diff / get_physics_process_delta_time(); - } + } else { + if (time_since_motion == 0 || time_since_motion > 0.1) { + Vector2 diff = drag_node_accum - last_drag_node_accum; + last_drag_node_accum = drag_node_accum; + drag_node_speed = diff / get_physics_process_delta_time(); + } - time_since_motion += get_physics_process_delta_time(); + time_since_motion += get_physics_process_delta_time(); + } } - } - } + } break; - if (p_what == NOTIFICATION_MOUSE_EXIT) { - highlight = HIGHLIGHT_NONE; - update(); + case NOTIFICATION_MOUSE_EXIT: { + highlight = HIGHLIGHT_NONE; + update(); + } break; } } @@ -423,11 +426,6 @@ double ScrollBar::get_grabber_size() const { } float page = (get_page() > 0) ? get_page() : 0; - /* - if (grabber_range < get_step()) - grabber_range=get_step(); - */ - double area_size = get_area_size(); double grabber_size = page / range * area_size; return grabber_size + get_grabber_min_size(); @@ -525,7 +523,7 @@ void ScrollBar::_drag_node_input(const Ref<InputEvent> &p_input) { Ref<InputEventMouseButton> mb = p_input; if (mb.is_valid()) { - if (mb->get_button_index() != 1) { + if (mb->get_button_index() != MouseButton::LEFT) { return; } diff --git a/scene/gui/scroll_bar.h b/scene/gui/scroll_bar.h index 574d17ee20..651edd1a74 100644 --- a/scene/gui/scroll_bar.h +++ b/scene/gui/scroll_bar.h @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ diff --git a/scene/gui/scroll_container.cpp b/scene/gui/scroll_container.cpp index 0c0ec39c7f..b3cf2cbf7e 100644 --- a/scene/gui/scroll_container.cpp +++ b/scene/gui/scroll_container.cpp @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ @@ -29,6 +29,8 @@ /*************************************************************************/ #include "scroll_container.h" + +#include "core/config/project_settings.h" #include "core/os/os.h" #include "scene/main/window.h" @@ -49,10 +51,10 @@ Size2 ScrollContainer::get_minimum_size() const { } Size2 minsize = c->get_combined_minimum_size(); - if (!scroll_h) { + if (horizontal_scroll_mode == SCROLL_MODE_DISABLED) { min_size.x = MAX(min_size.x, minsize.x); } - if (!scroll_v) { + if (vertical_scroll_mode == SCROLL_MODE_DISABLED) { min_size.y = MAX(min_size.y, minsize.y); } } @@ -92,7 +94,7 @@ void ScrollContainer::gui_input(const Ref<InputEvent> &p_gui_input) { Ref<InputEventMouseButton> mb = p_gui_input; if (mb.is_valid()) { - if (mb->get_button_index() == MOUSE_BUTTON_WHEEL_UP && mb->is_pressed()) { + if (mb->get_button_index() == MouseButton::WHEEL_UP && mb->is_pressed()) { // only horizontal is enabled, scroll horizontally if (h_scroll->is_visible() && (!v_scroll->is_visible() || mb->is_shift_pressed())) { h_scroll->set_value(h_scroll->get_value() - h_scroll->get_page() / 8 * mb->get_factor()); @@ -101,7 +103,7 @@ void ScrollContainer::gui_input(const Ref<InputEvent> &p_gui_input) { } } - if (mb->get_button_index() == MOUSE_BUTTON_WHEEL_DOWN && mb->is_pressed()) { + if (mb->get_button_index() == MouseButton::WHEEL_DOWN && mb->is_pressed()) { // only horizontal is enabled, scroll horizontally if (h_scroll->is_visible() && (!v_scroll->is_visible() || mb->is_shift_pressed())) { h_scroll->set_value(h_scroll->get_value() + h_scroll->get_page() / 8 * mb->get_factor()); @@ -110,13 +112,13 @@ void ScrollContainer::gui_input(const Ref<InputEvent> &p_gui_input) { } } - if (mb->get_button_index() == MOUSE_BUTTON_WHEEL_LEFT && mb->is_pressed()) { + if (mb->get_button_index() == MouseButton::WHEEL_LEFT && mb->is_pressed()) { if (h_scroll->is_visible_in_tree()) { h_scroll->set_value(h_scroll->get_value() - h_scroll->get_page() * mb->get_factor() / 8); } } - if (mb->get_button_index() == MOUSE_BUTTON_WHEEL_RIGHT && mb->is_pressed()) { + if (mb->get_button_index() == MouseButton::WHEEL_RIGHT && mb->is_pressed()) { if (h_scroll->is_visible_in_tree()) { h_scroll->set_value(h_scroll->get_value() + h_scroll->get_page() * mb->get_factor() / 8); } @@ -130,7 +132,7 @@ void ScrollContainer::gui_input(const Ref<InputEvent> &p_gui_input) { return; } - if (mb->get_button_index() != MOUSE_BUTTON_LEFT) { + if (mb->get_button_index() != MouseButton::LEFT) { return; } @@ -170,7 +172,7 @@ void ScrollContainer::gui_input(const Ref<InputEvent> &p_gui_input) { Vector2 motion = mm->get_relative(); drag_accum -= motion; - if (beyond_deadzone || (scroll_h && Math::abs(drag_accum.x) > deadzone) || (scroll_v && Math::abs(drag_accum.y) > deadzone)) { + if (beyond_deadzone || (horizontal_scroll_mode != SCROLL_MODE_DISABLED && Math::abs(drag_accum.x) > deadzone) || (vertical_scroll_mode != SCROLL_MODE_DISABLED && Math::abs(drag_accum.y) > deadzone)) { if (!beyond_deadzone) { propagate_notification(NOTIFICATION_SCROLL_BEGIN); emit_signal(SNAME("scroll_started")); @@ -180,12 +182,12 @@ void ScrollContainer::gui_input(const Ref<InputEvent> &p_gui_input) { drag_accum = -motion; } Vector2 diff = drag_from + drag_accum; - if (scroll_h) { + if (horizontal_scroll_mode != SCROLL_MODE_DISABLED) { h_scroll->set_value(diff.x); } else { drag_accum.x = 0; } - if (scroll_v) { + if (vertical_scroll_mode != SCROLL_MODE_DISABLED) { v_scroll->set_value(diff.y); } else { drag_accum.y = 0; @@ -286,7 +288,7 @@ void ScrollContainer::_update_dimensions() { child_max_size.y = MAX(child_max_size.y, minsize.y); Rect2 r = Rect2(-Size2(get_h_scroll(), get_v_scroll()), minsize); - if (!scroll_h || (!h_scroll->is_visible_in_tree() && c->get_h_size_flags() & SIZE_EXPAND)) { + if (horizontal_scroll_mode == SCROLL_MODE_DISABLED || (!h_scroll->is_visible_in_tree() && c->get_h_size_flags() & SIZE_EXPAND)) { r.position.x = 0; if (c->get_h_size_flags() & SIZE_EXPAND) { r.size.width = MAX(size.width, minsize.width); @@ -294,7 +296,7 @@ void ScrollContainer::_update_dimensions() { r.size.width = minsize.width; } } - if (!scroll_v || (!v_scroll->is_visible_in_tree() && c->get_v_size_flags() & SIZE_EXPAND)) { + if (vertical_scroll_mode == SCROLL_MODE_DISABLED || (!v_scroll->is_visible_in_tree() && c->get_v_size_flags() & SIZE_EXPAND)) { r.position.y = 0; if (c->get_v_size_flags() & SIZE_EXPAND) { r.size.height = MAX(size.height, minsize.height); @@ -314,95 +316,102 @@ void ScrollContainer::_update_dimensions() { } void ScrollContainer::_notification(int p_what) { - if (p_what == NOTIFICATION_ENTER_TREE || p_what == NOTIFICATION_THEME_CHANGED || p_what == NOTIFICATION_LAYOUT_DIRECTION_CHANGED || p_what == NOTIFICATION_TRANSLATION_CHANGED) { - _updating_scrollbars = true; - call_deferred(SNAME("_update_scrollbar_position")); - }; - - if (p_what == NOTIFICATION_READY) { - get_viewport()->connect("gui_focus_changed", callable_mp(this, &ScrollContainer::_gui_focus_changed)); - _update_dimensions(); - } - - if (p_what == NOTIFICATION_SORT_CHILDREN) { - _update_dimensions(); - }; - - if (p_what == NOTIFICATION_DRAW) { - Ref<StyleBox> sb = get_theme_stylebox(SNAME("bg")); - draw_style_box(sb, Rect2(Vector2(), get_size())); - - update_scrollbars(); - } - - if (p_what == NOTIFICATION_INTERNAL_PHYSICS_PROCESS) { - if (drag_touching) { - if (drag_touching_deaccel) { - Vector2 pos = Vector2(h_scroll->get_value(), v_scroll->get_value()); - pos += drag_speed * get_physics_process_delta_time(); - - bool turnoff_h = false; - bool turnoff_v = false; - - if (pos.x < 0) { - pos.x = 0; - turnoff_h = true; - } - if (pos.x > (h_scroll->get_max() - h_scroll->get_page())) { - pos.x = h_scroll->get_max() - h_scroll->get_page(); - turnoff_h = true; - } - - if (pos.y < 0) { - pos.y = 0; - turnoff_v = true; - } - if (pos.y > (v_scroll->get_max() - v_scroll->get_page())) { - pos.y = v_scroll->get_max() - v_scroll->get_page(); - turnoff_v = true; - } - - if (scroll_h) { - h_scroll->set_value(pos.x); - } - if (scroll_v) { - v_scroll->set_value(pos.y); - } - - float sgn_x = drag_speed.x < 0 ? -1 : 1; - float val_x = Math::abs(drag_speed.x); - val_x -= 1000 * get_physics_process_delta_time(); - - if (val_x < 0) { - turnoff_h = true; - } - - float sgn_y = drag_speed.y < 0 ? -1 : 1; - float val_y = Math::abs(drag_speed.y); - val_y -= 1000 * get_physics_process_delta_time(); - - if (val_y < 0) { - turnoff_v = true; - } - - drag_speed = Vector2(sgn_x * val_x, sgn_y * val_y); + switch (p_what) { + case NOTIFICATION_ENTER_TREE: + case NOTIFICATION_THEME_CHANGED: + case NOTIFICATION_LAYOUT_DIRECTION_CHANGED: + case NOTIFICATION_TRANSLATION_CHANGED: { + _updating_scrollbars = true; + call_deferred(SNAME("_update_scrollbar_position")); + } break; + + case NOTIFICATION_READY: { + Viewport *viewport = get_viewport(); + ERR_FAIL_COND(!viewport); + viewport->connect("gui_focus_changed", callable_mp(this, &ScrollContainer::_gui_focus_changed)); + _update_dimensions(); + } break; + + case NOTIFICATION_SORT_CHILDREN: { + _update_dimensions(); + } break; + + case NOTIFICATION_DRAW: { + Ref<StyleBox> sb = get_theme_stylebox(SNAME("bg")); + draw_style_box(sb, Rect2(Vector2(), get_size())); + + update_scrollbars(); + } break; + + case NOTIFICATION_INTERNAL_PHYSICS_PROCESS: { + if (drag_touching) { + if (drag_touching_deaccel) { + Vector2 pos = Vector2(h_scroll->get_value(), v_scroll->get_value()); + pos += drag_speed * get_physics_process_delta_time(); + + bool turnoff_h = false; + bool turnoff_v = false; + + if (pos.x < 0) { + pos.x = 0; + turnoff_h = true; + } + if (pos.x > (h_scroll->get_max() - h_scroll->get_page())) { + pos.x = h_scroll->get_max() - h_scroll->get_page(); + turnoff_h = true; + } + + if (pos.y < 0) { + pos.y = 0; + turnoff_v = true; + } + if (pos.y > (v_scroll->get_max() - v_scroll->get_page())) { + pos.y = v_scroll->get_max() - v_scroll->get_page(); + turnoff_v = true; + } + + if (horizontal_scroll_mode != SCROLL_MODE_DISABLED) { + h_scroll->set_value(pos.x); + } + if (vertical_scroll_mode != SCROLL_MODE_DISABLED) { + v_scroll->set_value(pos.y); + } + + float sgn_x = drag_speed.x < 0 ? -1 : 1; + float val_x = Math::abs(drag_speed.x); + val_x -= 1000 * get_physics_process_delta_time(); + + if (val_x < 0) { + turnoff_h = true; + } + + float sgn_y = drag_speed.y < 0 ? -1 : 1; + float val_y = Math::abs(drag_speed.y); + val_y -= 1000 * get_physics_process_delta_time(); + + if (val_y < 0) { + turnoff_v = true; + } + + drag_speed = Vector2(sgn_x * val_x, sgn_y * val_y); + + if (turnoff_h && turnoff_v) { + _cancel_drag(); + } - if (turnoff_h && turnoff_v) { - _cancel_drag(); - } + } else { + if (time_since_motion == 0 || time_since_motion > 0.1) { + Vector2 diff = drag_accum - last_drag_accum; + last_drag_accum = drag_accum; + drag_speed = diff / get_physics_process_delta_time(); + } - } else { - if (time_since_motion == 0 || time_since_motion > 0.1) { - Vector2 diff = drag_accum - last_drag_accum; - last_drag_accum = drag_accum; - drag_speed = diff / get_physics_process_delta_time(); + time_since_motion += get_physics_process_delta_time(); } - - time_since_motion += get_physics_process_delta_time(); } - } + } break; } -}; +} void ScrollContainer::update_scrollbars() { Size2 size = get_size(); @@ -411,17 +420,17 @@ void ScrollContainer::update_scrollbars() { Size2 hmin; Size2 vmin; - if (scroll_h) { + if (horizontal_scroll_mode != SCROLL_MODE_DISABLED) { hmin = h_scroll->get_combined_minimum_size(); } - if (scroll_v) { + if (vertical_scroll_mode != SCROLL_MODE_DISABLED) { vmin = v_scroll->get_combined_minimum_size(); } Size2 min = child_max_size; - bool hide_scroll_h = !scroll_h || min.width <= size.width || !h_scroll_visible; - bool hide_scroll_v = !scroll_v || min.height <= size.height || !v_scroll_visible; + bool hide_scroll_h = horizontal_scroll_mode != SCROLL_MODE_SHOW_ALWAYS && (horizontal_scroll_mode == SCROLL_MODE_DISABLED || horizontal_scroll_mode == SCROLL_MODE_SHOW_NEVER || (horizontal_scroll_mode == SCROLL_MODE_AUTO && min.width <= size.width)); + bool hide_scroll_v = vertical_scroll_mode != SCROLL_MODE_SHOW_ALWAYS && (vertical_scroll_mode == SCROLL_MODE_DISABLED || vertical_scroll_mode == SCROLL_MODE_SHOW_NEVER || (vertical_scroll_mode == SCROLL_MODE_AUTO && min.height <= size.height)); h_scroll->set_max(min.width); h_scroll->set_page(size.width - (hide_scroll_v ? 0 : vmin.width)); @@ -459,58 +468,32 @@ int ScrollContainer::get_v_scroll() const { return v_scroll->get_value(); } -void ScrollContainer::set_enable_h_scroll(bool p_enable) { - if (scroll_h == p_enable) { +void ScrollContainer::set_horizontal_scroll_mode(ScrollMode p_mode) { + if (horizontal_scroll_mode == p_mode) { return; } - scroll_h = p_enable; - minimum_size_changed(); + horizontal_scroll_mode = p_mode; + update_minimum_size(); queue_sort(); } -bool ScrollContainer::is_h_scroll_enabled() const { - return scroll_h; +ScrollContainer::ScrollMode ScrollContainer::get_horizontal_scroll_mode() const { + return horizontal_scroll_mode; } -void ScrollContainer::set_enable_v_scroll(bool p_enable) { - if (scroll_v == p_enable) { +void ScrollContainer::set_vertical_scroll_mode(ScrollMode p_mode) { + if (vertical_scroll_mode == p_mode) { return; } - scroll_v = p_enable; - minimum_size_changed(); + vertical_scroll_mode = p_mode; + update_minimum_size(); queue_sort(); } -bool ScrollContainer::is_v_scroll_enabled() const { - return scroll_v; -} - -void ScrollContainer::set_h_scroll_visible(bool p_visible) { - if (h_scroll_visible == p_visible) { - return; - } - - h_scroll_visible = p_visible; - update_scrollbars(); -} - -bool ScrollContainer::is_h_scroll_visible() const { - return h_scroll_visible; -} - -void ScrollContainer::set_v_scroll_visible(bool p_visible) { - if (v_scroll_visible == p_visible) { - return; - } - - v_scroll_visible = p_visible; - update_scrollbars(); -} - -bool ScrollContainer::is_v_scroll_visible() const { - return v_scroll_visible; +ScrollContainer::ScrollMode ScrollContainer::get_vertical_scroll_mode() const { + return vertical_scroll_mode; } int ScrollContainer::get_deadzone() const { @@ -556,11 +539,11 @@ TypedArray<String> ScrollContainer::get_configuration_warnings() const { return warnings; } -HScrollBar *ScrollContainer::get_h_scrollbar() { +HScrollBar *ScrollContainer::get_h_scroll_bar() { return h_scroll; } -VScrollBar *ScrollContainer::get_v_scrollbar() { +VScrollBar *ScrollContainer::get_v_scroll_bar() { return v_scroll; } @@ -573,17 +556,11 @@ void ScrollContainer::_bind_methods() { ClassDB::bind_method(D_METHOD("set_v_scroll", "value"), &ScrollContainer::set_v_scroll); ClassDB::bind_method(D_METHOD("get_v_scroll"), &ScrollContainer::get_v_scroll); - ClassDB::bind_method(D_METHOD("set_enable_h_scroll", "enable"), &ScrollContainer::set_enable_h_scroll); - ClassDB::bind_method(D_METHOD("is_h_scroll_enabled"), &ScrollContainer::is_h_scroll_enabled); + ClassDB::bind_method(D_METHOD("set_horizontal_scroll_mode", "enable"), &ScrollContainer::set_horizontal_scroll_mode); + ClassDB::bind_method(D_METHOD("get_horizontal_scroll_mode"), &ScrollContainer::get_horizontal_scroll_mode); - ClassDB::bind_method(D_METHOD("set_enable_v_scroll", "enable"), &ScrollContainer::set_enable_v_scroll); - ClassDB::bind_method(D_METHOD("is_v_scroll_enabled"), &ScrollContainer::is_v_scroll_enabled); - - ClassDB::bind_method(D_METHOD("set_h_scroll_visible", "visible"), &ScrollContainer::set_h_scroll_visible); - ClassDB::bind_method(D_METHOD("is_h_scroll_visible"), &ScrollContainer::is_h_scroll_visible); - - ClassDB::bind_method(D_METHOD("set_v_scroll_visible", "visible"), &ScrollContainer::set_v_scroll_visible); - ClassDB::bind_method(D_METHOD("is_v_scroll_visible"), &ScrollContainer::is_v_scroll_visible); + ClassDB::bind_method(D_METHOD("set_vertical_scroll_mode", "enable"), &ScrollContainer::set_vertical_scroll_mode); + ClassDB::bind_method(D_METHOD("get_vertical_scroll_mode"), &ScrollContainer::get_vertical_scroll_mode); ClassDB::bind_method(D_METHOD("set_deadzone", "deadzone"), &ScrollContainer::set_deadzone); ClassDB::bind_method(D_METHOD("get_deadzone"), &ScrollContainer::get_deadzone); @@ -591,8 +568,8 @@ void ScrollContainer::_bind_methods() { ClassDB::bind_method(D_METHOD("set_follow_focus", "enabled"), &ScrollContainer::set_follow_focus); ClassDB::bind_method(D_METHOD("is_following_focus"), &ScrollContainer::is_following_focus); - ClassDB::bind_method(D_METHOD("get_h_scrollbar"), &ScrollContainer::get_h_scrollbar); - ClassDB::bind_method(D_METHOD("get_v_scrollbar"), &ScrollContainer::get_v_scrollbar); + ClassDB::bind_method(D_METHOD("get_h_scroll_bar"), &ScrollContainer::get_h_scroll_bar); + ClassDB::bind_method(D_METHOD("get_v_scroll_bar"), &ScrollContainer::get_v_scroll_bar); ClassDB::bind_method(D_METHOD("ensure_control_visible", "control"), &ScrollContainer::ensure_control_visible); ADD_SIGNAL(MethodInfo("scroll_started")); @@ -603,12 +580,15 @@ void ScrollContainer::_bind_methods() { ADD_GROUP("Scroll", "scroll_"); ADD_PROPERTY(PropertyInfo(Variant::INT, "scroll_horizontal"), "set_h_scroll", "get_h_scroll"); ADD_PROPERTY(PropertyInfo(Variant::INT, "scroll_vertical"), "set_v_scroll", "get_v_scroll"); - ADD_PROPERTY(PropertyInfo(Variant::BOOL, "scroll_horizontal_enabled"), "set_enable_h_scroll", "is_h_scroll_enabled"); - ADD_PROPERTY(PropertyInfo(Variant::BOOL, "scroll_vertical_enabled"), "set_enable_v_scroll", "is_v_scroll_enabled"); - ADD_PROPERTY(PropertyInfo(Variant::BOOL, "scroll_horizontal_visible"), "set_h_scroll_visible", "is_h_scroll_visible"); - ADD_PROPERTY(PropertyInfo(Variant::BOOL, "scroll_vertical_visible"), "set_v_scroll_visible", "is_v_scroll_visible"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "horizontal_scroll_mode", PROPERTY_HINT_ENUM, "Disabled,Auto,Always Show,Never Show"), "set_horizontal_scroll_mode", "get_horizontal_scroll_mode"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "vertical_scroll_mode", PROPERTY_HINT_ENUM, "Disabled,Auto,Always Show,Never Show"), "set_vertical_scroll_mode", "get_vertical_scroll_mode"); ADD_PROPERTY(PropertyInfo(Variant::INT, "scroll_deadzone"), "set_deadzone", "get_deadzone"); + BIND_ENUM_CONSTANT(SCROLL_MODE_DISABLED); + BIND_ENUM_CONSTANT(SCROLL_MODE_AUTO); + BIND_ENUM_CONSTANT(SCROLL_MODE_SHOW_ALWAYS); + BIND_ENUM_CONSTANT(SCROLL_MODE_SHOW_NEVER); + GLOBAL_DEF("gui/common/default_scroll_deadzone", 0); }; diff --git a/scene/gui/scroll_container.h b/scene/gui/scroll_container.h index 9f4ec558dc..c00df87b18 100644 --- a/scene/gui/scroll_container.h +++ b/scene/gui/scroll_container.h @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ @@ -38,6 +38,15 @@ class ScrollContainer : public Container { GDCLASS(ScrollContainer, Container); +public: + enum ScrollMode { + SCROLL_MODE_DISABLED = 0, + SCROLL_MODE_AUTO, + SCROLL_MODE_SHOW_ALWAYS, + SCROLL_MODE_SHOW_NEVER, + }; + +private: HScrollBar *h_scroll; VScrollBar *v_scroll; @@ -54,11 +63,8 @@ class ScrollContainer : public Container { bool drag_touching_deaccel = false; bool beyond_deadzone = false; - bool scroll_h = true; - bool scroll_v = true; - - bool h_scroll_visible = true; - bool v_scroll_visible = true; + ScrollMode horizontal_scroll_mode = SCROLL_MODE_AUTO; + ScrollMode vertical_scroll_mode = SCROLL_MODE_AUTO; int deadzone = 0; bool follow_focus = false; @@ -68,7 +74,6 @@ class ScrollContainer : public Container { protected: Size2 get_minimum_size() const override; - virtual void gui_input(const Ref<InputEvent> &p_gui_input) override; void _gui_focus_changed(Control *p_control); void _update_dimensions(); void _notification(int p_what); @@ -80,23 +85,19 @@ protected: void _update_scrollbar_position(); public: + virtual void gui_input(const Ref<InputEvent> &p_gui_input) override; + void set_h_scroll(int p_pos); int get_h_scroll() const; void set_v_scroll(int p_pos); int get_v_scroll() const; - void set_enable_h_scroll(bool p_enable); - bool is_h_scroll_enabled() const; - - void set_enable_v_scroll(bool p_enable); - bool is_v_scroll_enabled() const; + void set_horizontal_scroll_mode(ScrollMode p_mode); + ScrollMode get_horizontal_scroll_mode() const; - void set_h_scroll_visible(bool p_visible); - bool is_h_scroll_visible() const; - - void set_v_scroll_visible(bool p_visible); - bool is_v_scroll_visible() const; + void set_vertical_scroll_mode(ScrollMode p_mode); + ScrollMode get_vertical_scroll_mode() const; int get_deadzone() const; void set_deadzone(int p_deadzone); @@ -104,8 +105,8 @@ public: bool is_following_focus() const; void set_follow_focus(bool p_follow); - HScrollBar *get_h_scrollbar(); - VScrollBar *get_v_scrollbar(); + HScrollBar *get_h_scroll_bar(); + VScrollBar *get_v_scroll_bar(); void ensure_control_visible(Control *p_control); TypedArray<String> get_configuration_warnings() const override; @@ -113,4 +114,6 @@ public: ScrollContainer(); }; +VARIANT_ENUM_CAST(ScrollContainer::ScrollMode); + #endif diff --git a/scene/gui/separator.cpp b/scene/gui/separator.cpp index 1f3cb7aa24..e3400d9c8f 100644 --- a/scene/gui/separator.cpp +++ b/scene/gui/separator.cpp @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ @@ -52,7 +52,6 @@ void Separator::_notification(int p_what) { } else { 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 77162c68fa..1621bb3351 100644 --- a/scene/gui/separator.h +++ b/scene/gui/separator.h @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ diff --git a/scene/gui/slider.cpp b/scene/gui/slider.cpp index 352f87954e..4b680f72cf 100644 --- a/scene/gui/slider.cpp +++ b/scene/gui/slider.cpp @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ @@ -29,6 +29,7 @@ /*************************************************************************/ #include "slider.h" + #include "core/os/keyboard.h" Size2 Slider::get_minimum_size() const { @@ -55,7 +56,7 @@ void Slider::gui_input(const Ref<InputEvent> &p_event) { Ref<InputEventMouseButton> mb = p_event; if (mb.is_valid()) { - if (mb->get_button_index() == MOUSE_BUTTON_LEFT) { + if (mb->get_button_index() == MouseButton::LEFT) { if (mb->is_pressed()) { Ref<Texture2D> grabber = get_theme_icon(mouse_inside || has_focus() ? "grabber_highlight" : "grabber"); grab.pos = orientation == VERTICAL ? mb->get_position().y : mb->get_position().x; @@ -70,14 +71,19 @@ void Slider::gui_input(const Ref<InputEvent> &p_event) { } grab.active = true; grab.uvalue = get_as_ratio(); + + emit_signal(SNAME("drag_started")); } else { grab.active = false; + + const bool value_changed = !Math::is_equal_approx((double)grab.uvalue, get_as_ratio()); + emit_signal(SNAME("drag_ended"), value_changed); } } else if (scrollable) { - if (mb->is_pressed() && mb->get_button_index() == MOUSE_BUTTON_WHEEL_UP) { + if (mb->is_pressed() && mb->get_button_index() == MouseButton::WHEEL_UP) { grab_focus(); set_value(get_value() + get_step()); - } else if (mb->is_pressed() && mb->get_button_index() == MOUSE_BUTTON_WHEEL_DOWN) { + } else if (mb->is_pressed() && mb->get_button_index() == MouseButton::WHEEL_DOWN) { grab_focus(); set_value(get_value() - get_step()); } @@ -142,22 +148,26 @@ void Slider::gui_input(const Ref<InputEvent> &p_event) { void Slider::_notification(int p_what) { switch (p_what) { case NOTIFICATION_THEME_CHANGED: { - minimum_size_changed(); + update_minimum_size(); update(); } break; + case NOTIFICATION_MOUSE_ENTER: { mouse_inside = true; update(); } break; + case NOTIFICATION_MOUSE_EXIT: { mouse_inside = false; update(); } break; - case NOTIFICATION_VISIBILITY_CHANGED: // fallthrough + + case NOTIFICATION_VISIBILITY_CHANGED: case NOTIFICATION_EXIT_TREE: { mouse_inside = false; grab.active = false; } break; + case NOTIFICATION_DRAW: { RID ci = get_canvas_item(); Size2i size = get_size(); @@ -204,7 +214,6 @@ void Slider::_notification(int p_what) { } grabber->draw(ci, Point2i(ratio * areasize, size.height / 2 - grabber->get_size().height / 2)); } - } break; } } @@ -264,6 +273,9 @@ void Slider::_bind_methods() { ClassDB::bind_method(D_METHOD("set_scrollable", "scrollable"), &Slider::set_scrollable); ClassDB::bind_method(D_METHOD("is_scrollable"), &Slider::is_scrollable); + ADD_SIGNAL(MethodInfo("drag_started")); + ADD_SIGNAL(MethodInfo("drag_ended", PropertyInfo(Variant::BOOL, "value_changed"))); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "editable"), "set_editable", "is_editable"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "scrollable"), "set_scrollable", "is_scrollable"); ADD_PROPERTY(PropertyInfo(Variant::INT, "tick_count", PROPERTY_HINT_RANGE, "0,4096,1"), "set_ticks", "get_ticks"); diff --git a/scene/gui/slider.h b/scene/gui/slider.h index 46fa08bbf0..5fbfee2aec 100644 --- a/scene/gui/slider.h +++ b/scene/gui/slider.h @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ diff --git a/scene/gui/spin_box.cpp b/scene/gui/spin_box.cpp index 0446e1b402..e50d7e765c 100644 --- a/scene/gui/spin_box.cpp +++ b/scene/gui/spin_box.cpp @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ @@ -39,15 +39,16 @@ Size2 SpinBox::get_minimum_size() const { return ms; } -void SpinBox::_value_changed(double) { +void SpinBox::_value_changed(double p_value) { String value = TS->format_number(String::num(get_value(), Math::range_step_decimals(get_step()))); - if (prefix != "") { + if (!prefix.is_empty()) { value = prefix + " " + value; } - if (suffix != "") { + if (!suffix.is_empty()) { value += " " + suffix; } line_edit->set_text(value); + Range::_value_changed(p_value); } void SpinBox::_text_submitted(const String &p_string) { @@ -85,7 +86,7 @@ void SpinBox::_line_edit_input(const Ref<InputEvent> &p_event) { } void SpinBox::_range_click_timeout() { - if (!drag.enabled && Input::get_singleton()->is_mouse_button_pressed(MOUSE_BUTTON_LEFT)) { + if (!drag.enabled && Input::get_singleton()->is_mouse_button_pressed(MouseButton::LEFT)) { bool up = get_local_mouse_position().y < (get_size().height / 2); set_value(get_value() + (up ? get_step() : -get_step())); @@ -121,7 +122,7 @@ void SpinBox::gui_input(const Ref<InputEvent> &p_event) { bool up = mb->get_position().y < (get_size().height / 2); switch (mb->get_button_index()) { - case MOUSE_BUTTON_LEFT: { + case MouseButton::LEFT: { line_edit->grab_focus(); set_value(get_value() + (up ? get_step() : -get_step())); @@ -133,17 +134,17 @@ void SpinBox::gui_input(const Ref<InputEvent> &p_event) { drag.allowed = true; drag.capture_pos = mb->get_position(); } break; - case MOUSE_BUTTON_RIGHT: { + case MouseButton::RIGHT: { line_edit->grab_focus(); set_value((up ? get_max() : get_min())); } break; - case MOUSE_BUTTON_WHEEL_UP: { + case MouseButton::WHEEL_UP: { if (line_edit->has_focus()) { set_value(get_value() + get_step() * mb->get_factor()); accept_event(); } } break; - case MOUSE_BUTTON_WHEEL_DOWN: { + case MouseButton::WHEEL_DOWN: { if (line_edit->has_focus()) { set_value(get_value() - get_step() * mb->get_factor()); accept_event(); @@ -154,7 +155,7 @@ void SpinBox::gui_input(const Ref<InputEvent> &p_event) { } } - if (mb.is_valid() && !mb->is_pressed() && mb->get_button_index() == MOUSE_BUTTON_LEFT) { + if (mb.is_valid() && !mb->is_pressed() && mb->get_button_index() == MouseButton::LEFT) { //set_default_cursor_shape(CURSOR_ARROW); range_click_timer->stop(); _release_mouse(); @@ -163,10 +164,10 @@ void SpinBox::gui_input(const Ref<InputEvent> &p_event) { Ref<InputEventMouseMotion> mm = p_event; - if (mm.is_valid() && mm->get_button_mask() & MOUSE_BUTTON_MASK_LEFT) { + if (mm.is_valid() && (mm->get_button_mask() & MouseButton::MASK_LEFT) != MouseButton::NONE) { if (drag.enabled) { drag.diff_y += mm->get_relative().y; - float diff_y = -0.01 * Math::pow(ABS(drag.diff_y), 1.8f) * SGN(drag.diff_y); + float diff_y = -0.01 * Math::pow(ABS(drag.diff_y), 1.8f) * SIGN(drag.diff_y); set_value(CLAMP(drag.base_val + get_step() * diff_y, get_min(), get_max())); } else if (drag.allowed && drag.capture_pos.distance_to(mm->get_position()) > 2) { Input::get_singleton()->set_mouse_mode(Input::MOUSE_MODE_CAPTURED); @@ -196,43 +197,53 @@ inline void SpinBox::_adjust_width_for_icon(const Ref<Texture2D> &icon) { } void SpinBox::_notification(int p_what) { - if (p_what == NOTIFICATION_DRAW) { - Ref<Texture2D> updown = get_theme_icon(SNAME("updown")); - - _adjust_width_for_icon(updown); - - RID ci = get_canvas_item(); - Size2i size = get_size(); - - if (is_layout_rtl()) { - updown->draw(ci, Point2i(0, (size.height - updown->get_height()) / 2)); - } else { - updown->draw(ci, Point2i(size.width - updown->get_width(), (size.height - updown->get_height()) / 2)); - } - - } else if (p_what == NOTIFICATION_FOCUS_EXIT) { - //_value_changed(0); - } else if (p_what == NOTIFICATION_ENTER_TREE) { - _adjust_width_for_icon(get_theme_icon(SNAME("updown"))); - _value_changed(0); - } else if (p_what == NOTIFICATION_EXIT_TREE) { - _release_mouse(); - } else if (p_what == NOTIFICATION_TRANSLATION_CHANGED) { - _value_changed(0); - } else if (p_what == NOTIFICATION_THEME_CHANGED) { - call_deferred(SNAME("minimum_size_changed")); - get_line_edit()->call_deferred(SNAME("minimum_size_changed")); - } else if (p_what == NOTIFICATION_LAYOUT_DIRECTION_CHANGED || p_what == NOTIFICATION_TRANSLATION_CHANGED) { - update(); + switch (p_what) { + case NOTIFICATION_DRAW: { + Ref<Texture2D> updown = get_theme_icon(SNAME("updown")); + + _adjust_width_for_icon(updown); + + RID ci = get_canvas_item(); + Size2i size = get_size(); + + if (is_layout_rtl()) { + updown->draw(ci, Point2i(0, (size.height - updown->get_height()) / 2)); + } else { + updown->draw(ci, Point2i(size.width - updown->get_width(), (size.height - updown->get_height()) / 2)); + } + } break; + + case NOTIFICATION_ENTER_TREE: { + _adjust_width_for_icon(get_theme_icon(SNAME("updown"))); + _value_changed(0); + } break; + + case NOTIFICATION_EXIT_TREE: { + _release_mouse(); + } break; + + case NOTIFICATION_TRANSLATION_CHANGED: { + _value_changed(0); + update(); + } break; + + case NOTIFICATION_THEME_CHANGED: { + call_deferred(SNAME("update_minimum_size")); + get_line_edit()->call_deferred(SNAME("update_minimum_size")); + } break; + + case NOTIFICATION_LAYOUT_DIRECTION_CHANGED: { + update(); + } break; } } -void SpinBox::set_align(LineEdit::Align p_align) { - line_edit->set_align(p_align); +void SpinBox::set_horizontal_alignment(HorizontalAlignment p_alignment) { + line_edit->set_horizontal_alignment(p_alignment); } -LineEdit::Align SpinBox::get_align() const { - return line_edit->get_align(); +HorizontalAlignment SpinBox::get_horizontal_alignment() const { + return line_edit->get_horizontal_alignment(); } void SpinBox::set_suffix(const String &p_suffix) { @@ -284,8 +295,8 @@ void SpinBox::apply() { } void SpinBox::_bind_methods() { - ClassDB::bind_method(D_METHOD("set_align", "align"), &SpinBox::set_align); - ClassDB::bind_method(D_METHOD("get_align"), &SpinBox::get_align); + ClassDB::bind_method(D_METHOD("set_horizontal_alignment", "alignment"), &SpinBox::set_horizontal_alignment); + ClassDB::bind_method(D_METHOD("get_horizontal_alignment"), &SpinBox::get_horizontal_alignment); ClassDB::bind_method(D_METHOD("set_suffix", "suffix"), &SpinBox::set_suffix); ClassDB::bind_method(D_METHOD("get_suffix"), &SpinBox::get_suffix); ClassDB::bind_method(D_METHOD("set_prefix", "prefix"), &SpinBox::set_prefix); @@ -297,7 +308,7 @@ void SpinBox::_bind_methods() { ClassDB::bind_method(D_METHOD("apply"), &SpinBox::apply); ClassDB::bind_method(D_METHOD("get_line_edit"), &SpinBox::get_line_edit); - ADD_PROPERTY(PropertyInfo(Variant::INT, "align", PROPERTY_HINT_ENUM, "Left,Center,Right,Fill"), "set_align", "get_align"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "alignment", PROPERTY_HINT_ENUM, "Left,Center,Right,Fill"), "set_horizontal_alignment", "get_horizontal_alignment"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "editable"), "set_editable", "is_editable"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "update_on_text_changed"), "set_update_on_text_changed", "get_update_on_text_changed"); ADD_PROPERTY(PropertyInfo(Variant::STRING, "prefix"), "set_prefix", "get_prefix"); @@ -310,7 +321,7 @@ SpinBox::SpinBox() { line_edit->set_anchors_and_offsets_preset(Control::PRESET_WIDE); line_edit->set_mouse_filter(MOUSE_FILTER_PASS); - line_edit->set_align(LineEdit::ALIGN_LEFT); + line_edit->set_horizontal_alignment(HORIZONTAL_ALIGNMENT_LEFT); line_edit->connect("text_submitted", callable_mp(this, &SpinBox::_text_submitted), Vector<Variant>(), CONNECT_DEFERRED); line_edit->connect("focus_exited", callable_mp(this, &SpinBox::_line_edit_focus_exit), Vector<Variant>(), CONNECT_DEFERRED); diff --git a/scene/gui/spin_box.h b/scene/gui/spin_box.h index f2299ce1c2..a15e3fe5f5 100644 --- a/scene/gui/spin_box.h +++ b/scene/gui/spin_box.h @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ @@ -47,7 +47,7 @@ class SpinBox : public Range { void _release_mouse(); void _text_submitted(const String &p_string); - virtual void _value_changed(double) override; + virtual void _value_changed(double p_value) override; void _text_changed(const String &p_string); String prefix; @@ -79,8 +79,8 @@ public: virtual Size2 get_minimum_size() const override; - void set_align(LineEdit::Align p_align); - LineEdit::Align get_align() const; + void set_horizontal_alignment(HorizontalAlignment p_alignment); + HorizontalAlignment get_horizontal_alignment() const; void set_editable(bool p_enabled); bool is_editable() const; diff --git a/scene/gui/split_container.cpp b/scene/gui/split_container.cpp index 4736a1ad37..6845d46721 100644 --- a/scene/gui/split_container.cpp +++ b/scene/gui/split_container.cpp @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ @@ -168,15 +168,18 @@ void SplitContainer::_notification(int p_what) { case NOTIFICATION_LAYOUT_DIRECTION_CHANGED: { queue_sort(); } break; + case NOTIFICATION_SORT_CHILDREN: { _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; @@ -200,8 +203,9 @@ void SplitContainer::_notification(int p_what) { draw_texture(tex, Point2i(middle_sep + (sep - tex->get_width()) / 2, (size.y - tex->get_height()) / 2)); } } break; + case NOTIFICATION_THEME_CHANGED: { - minimum_size_changed(); + update_minimum_size(); } break; } } @@ -216,7 +220,7 @@ void SplitContainer::gui_input(const Ref<InputEvent> &p_event) { Ref<InputEventMouseButton> mb = p_event; if (mb.is_valid()) { - if (mb->get_button_index() == MOUSE_BUTTON_LEFT) { + if (mb->get_button_index() == MouseButton::LEFT) { if (mb->is_pressed()) { int sep = get_theme_constant(SNAME("separation")); @@ -336,6 +340,30 @@ bool SplitContainer::is_collapsed() const { return collapsed; } +Vector<int> SplitContainer::get_allowed_size_flags_horizontal() const { + Vector<int> flags; + flags.append(SIZE_FILL); + if (!vertical) { + flags.append(SIZE_EXPAND); + } + flags.append(SIZE_SHRINK_BEGIN); + flags.append(SIZE_SHRINK_CENTER); + flags.append(SIZE_SHRINK_END); + return flags; +} + +Vector<int> SplitContainer::get_allowed_size_flags_vertical() const { + Vector<int> flags; + flags.append(SIZE_FILL); + if (vertical) { + flags.append(SIZE_EXPAND); + } + flags.append(SIZE_SHRINK_BEGIN); + flags.append(SIZE_SHRINK_CENTER); + flags.append(SIZE_SHRINK_END); + return flags; +} + void SplitContainer::_bind_methods() { ClassDB::bind_method(D_METHOD("set_split_offset", "offset"), &SplitContainer::set_split_offset); ClassDB::bind_method(D_METHOD("get_split_offset"), &SplitContainer::get_split_offset); diff --git a/scene/gui/split_container.h b/scene/gui/split_container.h index 47fd30a122..a69ffe4de9 100644 --- a/scene/gui/split_container.h +++ b/scene/gui/split_container.h @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ @@ -79,6 +79,9 @@ public: virtual Size2 get_minimum_size() const override; + virtual Vector<int> get_allowed_size_flags_horizontal() const override; + virtual Vector<int> get_allowed_size_flags_vertical() const override; + SplitContainer(bool p_vertical = false); }; diff --git a/scene/gui/subviewport_container.cpp b/scene/gui/subviewport_container.cpp index 53ea32e1b7..c66e145bc4 100644 --- a/scene/gui/subviewport_container.cpp +++ b/scene/gui/subviewport_container.cpp @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ @@ -54,6 +54,7 @@ Size2 SubViewportContainer::get_minimum_size() const { void SubViewportContainer::set_stretch(bool p_enable) { stretch = p_enable; + update_minimum_size(); queue_sort(); update(); } @@ -90,52 +91,81 @@ int SubViewportContainer::get_stretch_shrink() const { return shrink; } -void SubViewportContainer::_notification(int p_what) { - if (p_what == NOTIFICATION_RESIZED) { - if (!stretch) { - return; - } +Vector<int> SubViewportContainer::get_allowed_size_flags_horizontal() const { + return Vector<int>(); +} + +Vector<int> SubViewportContainer::get_allowed_size_flags_vertical() const { + return Vector<int>(); +} - for (int i = 0; i < get_child_count(); i++) { - SubViewport *c = Object::cast_to<SubViewport>(get_child(i)); - if (!c) { - continue; +void SubViewportContainer::_notification(int p_what) { + switch (p_what) { + case NOTIFICATION_RESIZED: { + if (!stretch) { + return; } - c->set_size(get_size() / shrink); - } - } + for (int i = 0; i < get_child_count(); i++) { + SubViewport *c = Object::cast_to<SubViewport>(get_child(i)); + if (!c) { + continue; + } - if (p_what == NOTIFICATION_ENTER_TREE || p_what == NOTIFICATION_VISIBILITY_CHANGED) { - for (int i = 0; i < get_child_count(); i++) { - SubViewport *c = Object::cast_to<SubViewport>(get_child(i)); - if (!c) { - continue; + c->set_size(get_size() / shrink); } - - if (is_visible_in_tree()) { - c->set_update_mode(SubViewport::UPDATE_ALWAYS); - } else { - c->set_update_mode(SubViewport::UPDATE_DISABLED); + } break; + + case NOTIFICATION_ENTER_TREE: + case NOTIFICATION_VISIBILITY_CHANGED: { + for (int i = 0; i < get_child_count(); i++) { + SubViewport *c = Object::cast_to<SubViewport>(get_child(i)); + if (!c) { + continue; + } + + if (is_visible_in_tree()) { + c->set_update_mode(SubViewport::UPDATE_ALWAYS); + } else { + c->set_update_mode(SubViewport::UPDATE_DISABLED); + } + + c->set_handle_input_locally(false); //do not handle input locally here + } + } break; + + case NOTIFICATION_DRAW: { + for (int i = 0; i < get_child_count(); i++) { + SubViewport *c = Object::cast_to<SubViewport>(get_child(i)); + if (!c) { + continue; + } + + if (stretch) { + draw_texture_rect(c->get_texture(), Rect2(Vector2(), get_size())); + } else { + draw_texture_rect(c->get_texture(), Rect2(Vector2(), c->get_size())); + } } + } break; - c->set_handle_input_locally(false); //do not handle input locally here - } - } + case NOTIFICATION_MOUSE_ENTER: { + _notify_viewports(NOTIFICATION_VP_MOUSE_ENTER); + } break; - if (p_what == NOTIFICATION_DRAW) { - for (int i = 0; i < get_child_count(); i++) { - SubViewport *c = Object::cast_to<SubViewport>(get_child(i)); - if (!c) { - continue; - } + case NOTIFICATION_MOUSE_EXIT: { + _notify_viewports(NOTIFICATION_VP_MOUSE_EXIT); + } break; + } +} - if (stretch) { - draw_texture_rect(c->get_texture(), Rect2(Vector2(), get_size())); - } else { - draw_texture_rect(c->get_texture(), Rect2(Vector2(), c->get_size())); - } +void SubViewportContainer::_notify_viewports(int p_notification) { + for (int i = 0; i < get_child_count(); i++) { + SubViewport *c = Object::cast_to<SubViewport>(get_child(i)); + if (!c) { + continue; } + c->notification(p_notification); } } diff --git a/scene/gui/subviewport_container.h b/scene/gui/subviewport_container.h index 7853f1590e..f52f01e4e2 100644 --- a/scene/gui/subviewport_container.h +++ b/scene/gui/subviewport_container.h @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ @@ -38,6 +38,7 @@ class SubViewportContainer : public Container { bool stretch = false; int shrink = 1; + void _notify_viewports(int p_notification); protected: void _notification(int p_what); @@ -54,6 +55,9 @@ public: virtual Size2 get_minimum_size() const override; + virtual Vector<int> get_allowed_size_flags_horizontal() const override; + virtual Vector<int> get_allowed_size_flags_vertical() const override; + SubViewportContainer(); }; diff --git a/scene/gui/tab_bar.cpp b/scene/gui/tab_bar.cpp index 405fbdae75..af314b9545 100644 --- a/scene/gui/tab_bar.cpp +++ b/scene/gui/tab_bar.cpp @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ @@ -32,54 +32,76 @@ #include "core/object/message_queue.h" #include "core/string/translation.h" - #include "scene/gui/box_container.h" #include "scene/gui/label.h" #include "scene/gui/texture_rect.h" Size2 TabBar::get_minimum_size() const { + Size2 ms; + + if (tabs.is_empty()) { + return ms; + } + Ref<StyleBox> tab_unselected = get_theme_stylebox(SNAME("tab_unselected")); Ref<StyleBox> tab_selected = get_theme_stylebox(SNAME("tab_selected")); Ref<StyleBox> tab_disabled = get_theme_stylebox(SNAME("tab_disabled")); + Ref<StyleBox> button_highlight = get_theme_stylebox(SNAME("button_highlight")); + Ref<Texture2D> close = get_theme_icon(SNAME("close")); + int hseparation = get_theme_constant(SNAME("hseparation")); int y_margin = MAX(MAX(tab_unselected->get_minimum_size().height, tab_selected->get_minimum_size().height), tab_disabled->get_minimum_size().height); - Size2 ms(0, 0); - for (int i = 0; i < tabs.size(); i++) { - Ref<Texture2D> tex = tabs[i].icon; - if (tex.is_valid()) { - ms.height = MAX(ms.height, tex->get_size().height); - if (tabs[i].text != "") { - ms.width += get_theme_constant(SNAME("hseparation")); - } + if (tabs[i].hidden) { + continue; } - ms.width += Math::ceil(tabs[i].text_buf->get_size().x); - ms.height = MAX(ms.height, tabs[i].text_buf->get_size().y + y_margin); + int ofs = ms.width; + Ref<StyleBox> style; if (tabs[i].disabled) { - ms.width += tab_disabled->get_minimum_size().width; + style = tab_disabled; } else if (current == i) { - ms.width += tab_selected->get_minimum_size().width; + style = tab_selected; } else { - ms.width += tab_unselected->get_minimum_size().width; + style = tab_unselected; + } + ms.width += style->get_minimum_size().width; + + Ref<Texture2D> tex = tabs[i].icon; + if (tex.is_valid()) { + ms.height = MAX(ms.height, tex->get_size().height + y_margin); + ms.width += tex->get_size().width + hseparation; } + if (!tabs[i].text.is_empty()) { + ms.width += tabs[i].size_text + hseparation; + } + ms.height = MAX(ms.height, tabs[i].text_buf->get_size().y + y_margin); + + bool close_visible = cb_displaypolicy == CLOSE_BUTTON_SHOW_ALWAYS || (cb_displaypolicy == CLOSE_BUTTON_SHOW_ACTIVE_ONLY && i == current); + if (tabs[i].right_button.is_valid()) { Ref<Texture2D> rb = tabs[i].right_button; - Size2 bms = rb->get_size(); - bms.width += get_theme_constant(SNAME("hseparation")); - ms.width += bms.width; - ms.height = MAX(bms.height + tab_unselected->get_minimum_size().height, ms.height); + + if (close_visible) { + ms.width += button_highlight->get_minimum_size().width + rb->get_width(); + } else { + ms.width += button_highlight->get_margin(SIDE_LEFT) + rb->get_width() + hseparation; + } + + ms.height = MAX(ms.height, rb->get_height() + y_margin); + } + + if (close_visible) { + ms.width += button_highlight->get_margin(SIDE_LEFT) + close->get_width() + hseparation; + + ms.height = MAX(ms.height, close->get_height() + y_margin); } - if (cb_displaypolicy == CLOSE_BUTTON_SHOW_ALWAYS || (cb_displaypolicy == CLOSE_BUTTON_SHOW_ACTIVE_ONLY && i == current)) { - Ref<Texture2D> cb = get_theme_icon(SNAME("close")); - Size2 bms = cb->get_size(); - bms.width += get_theme_constant(SNAME("hseparation")); - ms.width += bms.width; - ms.height = MAX(bms.height + tab_unselected->get_minimum_size().height, ms.height); + if (ms.width - ofs > style->get_minimum_size().width) { + ms.width -= hseparation; } } @@ -143,38 +165,37 @@ 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() == MOUSE_BUTTON_WHEEL_UP && !mb->is_command_pressed()) { + if (mb->is_pressed() && mb->get_button_index() == MouseButton::WHEEL_UP && !mb->is_command_pressed()) { if (scrolling_enabled && buttons_visible) { if (offset > 0) { offset--; + _update_cache(); update(); } } } - if (mb->is_pressed() && mb->get_button_index() == MOUSE_BUTTON_WHEEL_DOWN && !mb->is_command_pressed()) { + if (mb->is_pressed() && mb->get_button_index() == MouseButton::WHEEL_DOWN && !mb->is_command_pressed()) { if (scrolling_enabled && buttons_visible) { - if (missing_right) { + if (missing_right && offset < tabs.size()) { offset++; - _ensure_no_over_offset(); // Avoid overreaching when scrolling fast. + _update_cache(); update(); } } } - if (rb_pressing && !mb->is_pressed() && mb->get_button_index() == MOUSE_BUTTON_LEFT) { + if (rb_pressing && !mb->is_pressed() && mb->get_button_index() == MouseButton::LEFT) { if (rb_hover != -1) { - // pressed - emit_signal(SNAME("tab_rmb_clicked"), rb_hover); + emit_signal(SNAME("tab_button_pressed"), rb_hover); } rb_pressing = false; update(); } - if (cb_pressing && !mb->is_pressed() && mb->get_button_index() == MOUSE_BUTTON_LEFT) { + if (cb_pressing && !mb->is_pressed() && mb->get_button_index() == MouseButton::LEFT) { if (cb_hover != -1) { - // pressed emit_signal(SNAME("tab_close_pressed"), cb_hover); } @@ -182,8 +203,7 @@ void TabBar::gui_input(const Ref<InputEvent> &p_event) { update(); } - if (mb->is_pressed() && (mb->get_button_index() == MOUSE_BUTTON_LEFT || (select_with_rmb && mb->get_button_index() == MOUSE_BUTTON_RIGHT))) { - // clicks + 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) { @@ -194,12 +214,14 @@ void TabBar::gui_input(const Ref<InputEvent> &p_event) { if (pos.x < decr->get_width()) { if (missing_right) { offset++; + _update_cache(); update(); } return; } else if (pos.x < incr->get_width() + decr->get_width()) { if (offset > 0) { offset--; + _update_cache(); update(); } return; @@ -209,12 +231,14 @@ void TabBar::gui_input(const Ref<InputEvent> &p_event) { if (pos.x > limit + decr->get_width()) { if (missing_right) { offset++; + _update_cache(); update(); } return; } else if (pos.x > limit) { if (offset > 0) { offset--; + _update_cache(); update(); } return; @@ -229,13 +253,17 @@ void TabBar::gui_input(const Ref<InputEvent> &p_event) { int found = -1; for (int i = offset; i <= max_drawn_tab; i++) { + if (tabs[i].hidden) { + continue; + } + if (tabs[i].rb_rect.has_point(pos)) { rb_pressing = true; update(); return; } - if (tabs[i].cb_rect.has_point(pos)) { + 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(); return; @@ -251,6 +279,12 @@ void TabBar::gui_input(const Ref<InputEvent> &p_event) { if (found != -1) { set_current_tab(found); + + if (mb->get_button_index() == MouseButton::RIGHT) { + // Right mouse button clicked. + emit_signal(SNAME("tab_rmb_clicked"), found); + } + emit_signal(SNAME("tab_clicked"), found); } } @@ -270,32 +304,39 @@ void TabBar::_shape(int p_tab) { tabs.write[p_tab].text_buf->set_direction((TextServer::Direction)tabs[p_tab].text_direction); } - tabs.write[p_tab].text_buf->add_string(tabs.write[p_tab].xl_text, font, font_size, tabs[p_tab].opentype_features, (tabs[p_tab].language != "") ? tabs[p_tab].language : TranslationServer::get_singleton()->get_tool_locale()); + tabs.write[p_tab].text_buf->add_string(tabs[p_tab].xl_text, font, font_size, tabs[p_tab].opentype_features, !tabs[p_tab].language.is_empty() ? tabs[p_tab].language : TranslationServer::get_singleton()->get_tool_locale()); } void TabBar::_notification(int p_what) { switch (p_what) { case NOTIFICATION_LAYOUT_DIRECTION_CHANGED: { - _update_cache(); update(); } break; + case NOTIFICATION_THEME_CHANGED: case NOTIFICATION_TRANSLATION_CHANGED: { for (int i = 0; i < tabs.size(); ++i) { _shape(i); } - _update_cache(); - minimum_size_changed(); - update(); - } break; + + [[fallthrough]]; + } case NOTIFICATION_RESIZED: { + int ofs_old = offset; + int max_old = max_drawn_tab; + _update_cache(); _ensure_no_over_offset(); - ensure_tab_visible(current); + + if (scroll_to_selected && (offset != ofs_old || max_drawn_tab != max_old)) { + ensure_tab_visible(current); + } } break; + case NOTIFICATION_DRAW: { - _update_cache(); - RID ci = get_canvas_item(); + if (tabs.is_empty()) { + return; + } Ref<StyleBox> tab_unselected = get_theme_stylebox(SNAME("tab_unselected")); Ref<StyleBox> tab_selected = get_theme_stylebox(SNAME("tab_selected")); @@ -303,177 +344,53 @@ void TabBar::_notification(int p_what) { 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> close = get_theme_icon(SNAME("close")); - Color font_outline_color = get_theme_color(SNAME("font_outline_color")); - int outline_size = get_theme_constant(SNAME("outline_size")); - - Vector2 size = get_size(); - bool rtl = is_layout_rtl(); - - int h = get_size().height; - int w = 0; - int mw = 0; - - for (int i = 0; i < tabs.size(); i++) { - tabs.write[i].ofs_cache = mw; - mw += get_tab_width(i); - } - - if (tab_align == ALIGN_CENTER) { - w = (get_size().width - mw) / 2; - } else if (tab_align == ALIGN_RIGHT) { - w = get_size().width - mw; - } - - if (w < 0) { - w = 0; - } - Ref<Texture2D> incr = get_theme_icon(SNAME("increment")); Ref<Texture2D> decr = get_theme_icon(SNAME("decrement")); Ref<Texture2D> incr_hl = get_theme_icon(SNAME("increment_highlight")); Ref<Texture2D> decr_hl = get_theme_icon(SNAME("decrement_highlight")); - int limit = get_size().width; - int limit_minus_buttons = get_size().width - incr->get_width() - decr->get_width(); - - missing_right = false; - - for (int i = offset; i < tabs.size(); i++) { - tabs.write[i].ofs_cache = w; - - int lsize = tabs[i].size_cache; - - Ref<StyleBox> sb; - Color col; - - if (tabs[i].disabled) { - sb = tab_disabled; - col = font_disabled_color; - } else if (i == current) { - sb = tab_selected; - col = font_selected_color; - } else { - sb = tab_unselected; - col = font_unselected_color; - } - - int new_width = w + lsize; - if (new_width > limit || (i < tabs.size() - 1 && new_width > limit_minus_buttons)) { // For the last tab, we accept if the tab covers the buttons. - max_drawn_tab = i - 1; - missing_right = true; - break; - } else { - max_drawn_tab = i; - } - - Rect2 sb_rect; - if (rtl) { - sb_rect = Rect2(size.width - w - tabs[i].size_cache, 0, tabs[i].size_cache, h); - } else { - sb_rect = Rect2(w, 0, tabs[i].size_cache, h); - } - sb->draw(ci, sb_rect); + bool rtl = is_layout_rtl(); + Vector2 size = get_size(); + int limit_minus_buttons = size.width - incr->get_width() - decr->get_width(); - w += sb->get_margin(SIDE_LEFT); + int ofs = tabs[offset].ofs_cache; - Size2i sb_ms = sb->get_minimum_size(); - Ref<Texture2D> icon = tabs[i].icon; - if (icon.is_valid()) { - if (rtl) { - icon->draw(ci, Point2i(size.width - w - icon->get_width(), sb->get_margin(SIDE_TOP) + ((sb_rect.size.y - sb_ms.y) - icon->get_height()) / 2)); - } else { - icon->draw(ci, Point2i(w, sb->get_margin(SIDE_TOP) + ((sb_rect.size.y - sb_ms.y) - icon->get_height()) / 2)); - } - if (tabs[i].text != "") { - w += icon->get_width() + get_theme_constant(SNAME("hseparation")); - } - } - - if (rtl) { - Vector2 text_pos = Point2i(size.width - w - tabs[i].text_buf->get_size().x, sb->get_margin(SIDE_TOP) + ((sb_rect.size.y - sb_ms.y) - tabs[i].text_buf->get_size().y) / 2); - if (outline_size > 0 && font_outline_color.a > 0) { - tabs[i].text_buf->draw_outline(ci, text_pos, outline_size, font_outline_color); - } - tabs[i].text_buf->draw(ci, text_pos, col); - } else { - Vector2 text_pos = Point2i(w, sb->get_margin(SIDE_TOP) + ((sb_rect.size.y - sb_ms.y) - tabs[i].text_buf->get_size().y) / 2); - if (outline_size > 0 && font_outline_color.a > 0) { - tabs[i].text_buf->draw_outline(ci, text_pos, outline_size, font_outline_color); - } - tabs[i].text_buf->draw(ci, text_pos, col); + // Draw unselected tabs in the back. + for (int i = offset; i <= max_drawn_tab; i++) { + if (tabs[i].hidden) { + continue; } - w += tabs[i].size_text; - - if (tabs[i].right_button.is_valid()) { - Ref<StyleBox> style = get_theme_stylebox(SNAME("close_bg_highlight")); - Ref<Texture2D> rb = tabs[i].right_button; + if (i != current) { + Ref<StyleBox> sb; + Color col; - w += get_theme_constant(SNAME("hseparation")); - - Rect2 rb_rect; - rb_rect.size = style->get_minimum_size() + rb->get_size(); - if (rtl) { - rb_rect.position.x = size.width - w - rb_rect.size.x; + if (tabs[i].disabled) { + sb = tab_disabled; + col = font_disabled_color; + } else if (i == current) { + sb = tab_selected; + col = font_selected_color; } else { - rb_rect.position.x = w; - } - rb_rect.position.y = sb->get_margin(SIDE_TOP) + ((sb_rect.size.y - sb_ms.y) - (rb_rect.size.y)) / 2; - - if (rb_hover == i) { - if (rb_pressing) { - get_theme_stylebox(SNAME("button_pressed"))->draw(ci, rb_rect); - } else { - style->draw(ci, rb_rect); - } + sb = tab_unselected; + col = font_unselected_color; } - if (rtl) { - rb->draw(ci, Point2i(size.width - w - rb_rect.size.x + style->get_margin(SIDE_LEFT), rb_rect.position.y + style->get_margin(SIDE_TOP))); - } else { - rb->draw(ci, Point2i(w + style->get_margin(SIDE_LEFT), rb_rect.position.y + style->get_margin(SIDE_TOP))); - } - w += rb->get_width(); - tabs.write[i].rb_rect = rb_rect; + _draw_tab(sb, col, i, rtl ? size.width - ofs - tabs[i].size_cache : ofs); } - if (cb_displaypolicy == CLOSE_BUTTON_SHOW_ALWAYS || (cb_displaypolicy == CLOSE_BUTTON_SHOW_ACTIVE_ONLY && i == current)) { - Ref<StyleBox> style = get_theme_stylebox(SNAME("close_bg_highlight")); - Ref<Texture2D> cb = close; - - w += get_theme_constant(SNAME("hseparation")); - - Rect2 cb_rect; - cb_rect.size = style->get_minimum_size() + cb->get_size(); - if (rtl) { - cb_rect.position.x = size.width - w - cb_rect.size.x; - } else { - cb_rect.position.x = w; - } - cb_rect.position.y = sb->get_margin(SIDE_TOP) + ((sb_rect.size.y - sb_ms.y) - (cb_rect.size.y)) / 2; - - if (!tabs[i].disabled && cb_hover == i) { - if (cb_pressing) { - get_theme_stylebox(SNAME("close_bg_pressed"))->draw(ci, cb_rect); - } else { - style->draw(ci, cb_rect); - } - } + ofs += tabs[i].size_cache; + } - if (rtl) { - cb->draw(ci, Point2i(size.width - w - cb_rect.size.x + style->get_margin(SIDE_LEFT), cb_rect.position.y + style->get_margin(SIDE_TOP))); - } else { - cb->draw(ci, Point2i(w + style->get_margin(SIDE_LEFT), cb_rect.position.y + style->get_margin(SIDE_TOP))); - } - w += cb->get_width(); - tabs.write[i].cb_rect = cb_rect; - } + // 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; + float x = rtl ? size.width - tabs[current].ofs_cache - tabs[current].size_cache : tabs[current].ofs_cache; - w += sb->get_margin(SIDE_RIGHT); + _draw_tab(sb, font_selected_color, current, x); } - if (offset > 0 || missing_right) { + if (buttons_visible) { int vofs = (get_size().height - incr->get_size().height) / 2; if (rtl) { @@ -501,29 +418,147 @@ void TabBar::_notification(int p_what) { draw_texture(incr, Point2(limit_minus_buttons + decr->get_size().width, vofs), Color(1, 1, 1, 0.5)); } } + } + } break; + } +} + +void TabBar::_draw_tab(Ref<StyleBox> &p_tab_style, Color &p_font_color, int p_index, float p_x) { + RID ci = get_canvas_item(); + bool rtl = is_layout_rtl(); + + Color font_outline_color = get_theme_color(SNAME("font_outline_color")); + int outline_size = get_theme_constant(SNAME("outline_size")); + int hseparation = get_theme_constant(SNAME("hseparation")); + + Rect2 sb_rect = Rect2(p_x, 0, tabs[p_index].size_cache, get_size().height); + p_tab_style->draw(ci, sb_rect); + + p_x += rtl ? tabs[p_index].size_cache - p_tab_style->get_margin(SIDE_LEFT) : p_tab_style->get_margin(SIDE_LEFT); + + Size2i sb_ms = p_tab_style->get_minimum_size(); + + // Draw the icon. + Ref<Texture2D> icon = tabs[p_index].icon; + 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; + } + + // Draw the text. + if (!tabs[p_index].text.is_empty()) { + Point2i text_pos = Point2i(rtl ? p_x - tabs[p_index].size_text : p_x, + p_tab_style->get_margin(SIDE_TOP) + ((sb_rect.size.y - sb_ms.y) - tabs[p_index].text_buf->get_size().y) / 2); - buttons_visible = true; + if (outline_size > 0 && font_outline_color.a > 0) { + tabs[p_index].text_buf->draw_outline(ci, text_pos, outline_size, font_outline_color); + } + tabs[p_index].text_buf->draw(ci, text_pos, p_font_color); + + p_x = rtl ? p_x - tabs[p_index].size_text - hseparation : p_x + tabs[p_index].size_text + hseparation; + } + + // Draw and calculate rect of the right button. + if (tabs[p_index].right_button.is_valid()) { + Ref<StyleBox> style = get_theme_stylebox(SNAME("button_highlight")); + Ref<Texture2D> rb = tabs[p_index].right_button; + + Rect2 rb_rect; + rb_rect.size = style->get_minimum_size() + rb->get_size(); + rb_rect.position.x = rtl ? p_x - rb_rect.size.width : p_x; + rb_rect.position.y = p_tab_style->get_margin(SIDE_TOP) + ((sb_rect.size.y - sb_ms.y) - (rb_rect.size.y)) / 2; + + tabs.write[p_index].rb_rect = rb_rect; + + if (rb_hover == p_index) { + if (rb_pressing) { + get_theme_stylebox(SNAME("button_pressed"))->draw(ci, rb_rect); } else { - buttons_visible = false; + style->draw(ci, rb_rect); } - } break; + } + + rb->draw(ci, Point2i(rb_rect.position.x + style->get_margin(SIDE_LEFT), rb_rect.position.y + style->get_margin(SIDE_TOP))); + + p_x = rtl ? rb_rect.position.x : rb_rect.position.x + rb_rect.size.width; + } + + // Draw and calculate rect of the close button. + if (cb_displaypolicy == CLOSE_BUTTON_SHOW_ALWAYS || (cb_displaypolicy == CLOSE_BUTTON_SHOW_ACTIVE_ONLY && p_index == current)) { + Ref<StyleBox> style = get_theme_stylebox(SNAME("button_highlight")); + Ref<Texture2D> cb = get_theme_icon(SNAME("close")); + + Rect2 cb_rect; + cb_rect.size = style->get_minimum_size() + cb->get_size(); + cb_rect.position.x = rtl ? p_x - cb_rect.size.width : p_x; + cb_rect.position.y = p_tab_style->get_margin(SIDE_TOP) + ((sb_rect.size.y - sb_ms.y) - (cb_rect.size.y)) / 2; + + tabs.write[p_index].cb_rect = cb_rect; + + if (!tabs[p_index].disabled && cb_hover == p_index) { + if (cb_pressing) { + get_theme_stylebox(SNAME("button_pressed"))->draw(ci, cb_rect); + } else { + style->draw(ci, cb_rect); + } + } + + cb->draw(ci, Point2i(cb_rect.position.x + style->get_margin(SIDE_LEFT), cb_rect.position.y + style->get_margin(SIDE_TOP))); } } +void TabBar::set_tab_count(int p_count) { + if (p_count == tabs.size()) { + return; + } + + ERR_FAIL_COND(p_count < 0); + tabs.resize(p_count); + + if (p_count == 0) { + offset = 0; + max_drawn_tab = 0; + current = 0; + previous = 0; + } else { + offset = MIN(offset, p_count - 1); + max_drawn_tab = MIN(max_drawn_tab, p_count - 1); + current = MIN(current, p_count - 1); + + _update_cache(); + _ensure_no_over_offset(); + if (scroll_to_selected) { + ensure_tab_visible(current); + } + } + + update(); + update_minimum_size(); + notify_property_list_changed(); +} + int TabBar::get_tab_count() const { return tabs.size(); } void TabBar::set_current_tab(int p_current) { - if (current == p_current) { - return; - } ERR_FAIL_INDEX(p_current, get_tab_count()); previous = current; current = p_current; + if (current == previous) { + emit_signal(SNAME("tab_selected"), current); + return; + } + + emit_signal(SNAME("tab_selected"), current); + _update_cache(); + if (scroll_to_selected) { + ensure_tab_visible(current); + } update(); emit_signal(SNAME("tab_changed"), p_current); @@ -552,9 +587,15 @@ bool TabBar::get_offset_buttons_visible() const { void TabBar::set_tab_title(int p_tab, const String &p_title) { ERR_FAIL_INDEX(p_tab, tabs.size()); tabs.write[p_tab].text = p_title; + _shape(p_tab); + _update_cache(); + _ensure_no_over_offset(); + if (scroll_to_selected) { + ensure_tab_visible(current); + } update(); - minimum_size_changed(); + update_minimum_size(); } String TabBar::get_tab_title(int p_tab) const { @@ -565,6 +606,7 @@ String TabBar::get_tab_title(int p_tab) const { void TabBar::set_tab_text_direction(int p_tab, Control::TextDirection p_text_direction) { ERR_FAIL_INDEX(p_tab, tabs.size()); ERR_FAIL_COND((int)p_text_direction < -1 || (int)p_text_direction > 3); + if (tabs[p_tab].text_direction != p_text_direction) { tabs.write[p_tab].text_direction = p_text_direction; _shape(p_tab); @@ -580,22 +622,38 @@ Control::TextDirection TabBar::get_tab_text_direction(int p_tab) const { void TabBar::clear_tab_opentype_features(int p_tab) { ERR_FAIL_INDEX(p_tab, tabs.size()); tabs.write[p_tab].opentype_features.clear(); + _shape(p_tab); + _update_cache(); + _ensure_no_over_offset(); + if (scroll_to_selected) { + ensure_tab_visible(current); + } update(); + update_minimum_size(); } void TabBar::set_tab_opentype_feature(int p_tab, const String &p_name, int p_value) { ERR_FAIL_INDEX(p_tab, tabs.size()); + int32_t tag = TS->name_to_tag(p_name); if (!tabs[p_tab].opentype_features.has(tag) || (int)tabs[p_tab].opentype_features[tag] != p_value) { tabs.write[p_tab].opentype_features[tag] = p_value; + _shape(p_tab); + _update_cache(); + _ensure_no_over_offset(); + if (scroll_to_selected) { + ensure_tab_visible(current); + } update(); + update_minimum_size(); } } int TabBar::get_tab_opentype_feature(int p_tab, const String &p_name) const { ERR_FAIL_INDEX_V(p_tab, tabs.size(), -1); + int32_t tag = TS->name_to_tag(p_name); if (!tabs[p_tab].opentype_features.has(tag)) { return -1; @@ -605,10 +663,17 @@ int TabBar::get_tab_opentype_feature(int p_tab, const String &p_name) const { void TabBar::set_tab_language(int p_tab, const String &p_language) { ERR_FAIL_INDEX(p_tab, tabs.size()); + if (tabs[p_tab].language != p_language) { tabs.write[p_tab].language = p_language; _shape(p_tab); + _update_cache(); + _ensure_no_over_offset(); + if (scroll_to_selected) { + ensure_tab_visible(current); + } update(); + update_minimum_size(); } } @@ -620,8 +685,14 @@ String TabBar::get_tab_language(int p_tab) const { void TabBar::set_tab_icon(int p_tab, const Ref<Texture2D> &p_icon) { ERR_FAIL_INDEX(p_tab, tabs.size()); tabs.write[p_tab].icon = p_icon; + + _update_cache(); + _ensure_no_over_offset(); + if (scroll_to_selected) { + ensure_tab_visible(current); + } update(); - minimum_size_changed(); + update_minimum_size(); } Ref<Texture2D> TabBar::get_tab_icon(int p_tab) const { @@ -632,23 +703,53 @@ Ref<Texture2D> TabBar::get_tab_icon(int p_tab) const { void TabBar::set_tab_disabled(int p_tab, bool p_disabled) { ERR_FAIL_INDEX(p_tab, tabs.size()); tabs.write[p_tab].disabled = p_disabled; + + _update_cache(); + _ensure_no_over_offset(); + if (scroll_to_selected) { + ensure_tab_visible(current); + } update(); + update_minimum_size(); } -bool TabBar::get_tab_disabled(int p_tab) const { +bool TabBar::is_tab_disabled(int p_tab) const { ERR_FAIL_INDEX_V(p_tab, tabs.size(), false); return tabs[p_tab].disabled; } -void TabBar::set_tab_right_button(int p_tab, const Ref<Texture2D> &p_right_button) { +void TabBar::set_tab_hidden(int p_tab, bool p_hidden) { ERR_FAIL_INDEX(p_tab, tabs.size()); - tabs.write[p_tab].right_button = p_right_button; + tabs.write[p_tab].hidden = p_hidden; + _update_cache(); + _ensure_no_over_offset(); + if (scroll_to_selected) { + ensure_tab_visible(current); + } update(); - minimum_size_changed(); + update_minimum_size(); } -Ref<Texture2D> TabBar::get_tab_right_button(int p_tab) const { +bool TabBar::is_tab_hidden(int p_tab) const { + ERR_FAIL_INDEX_V(p_tab, tabs.size(), false); + return tabs[p_tab].hidden; +} + +void TabBar::set_tab_button_icon(int p_tab, const Ref<Texture2D> &p_icon) { + ERR_FAIL_INDEX(p_tab, tabs.size()); + tabs.write[p_tab].right_button = p_icon; + + _update_cache(); + _ensure_no_over_offset(); + if (scroll_to_selected) { + ensure_tab_visible(current); + } + update(); + update_minimum_size(); +} + +Ref<Texture2D> TabBar::get_tab_button_icon(int p_tab) const { ERR_FAIL_INDEX_V(p_tab, tabs.size(), Ref<Texture2D>()); return tabs[p_tab].right_button; } @@ -658,99 +759,146 @@ void TabBar::_update_hover() { return; } + ERR_FAIL_COND(tabs.is_empty()); + const Point2 &pos = get_local_mouse_position(); - // test hovering to display right or close button + // Test hovering to display right or close button. int hover_now = -1; int hover_buttons = -1; - for (int i = offset; i < tabs.size(); i++) { + for (int i = offset; i <= max_drawn_tab; i++) { + if (tabs[i].hidden) { + continue; + } + Rect2 rect = get_tab_rect(i); if (rect.has_point(pos)) { hover_now = i; } + if (tabs[i].rb_rect.has_point(pos)) { rb_hover = i; cb_hover = -1; hover_buttons = i; - break; } else if (!tabs[i].disabled && tabs[i].cb_rect.has_point(pos)) { cb_hover = i; rb_hover = -1; hover_buttons = i; + } + + if (hover_buttons != -1) { + update(); break; } } + if (hover != hover_now) { hover = hover_now; - emit_signal(SNAME("tab_hovered"), hover); + + if (hover != -1) { + emit_signal(SNAME("tab_hovered"), hover); + } } - if (hover_buttons == -1) { // no hover + if (hover_buttons == -1) { // No hover. + int rb_hover_old = rb_hover; + int cb_hover_old = cb_hover; + rb_hover = hover_buttons; cb_hover = hover_buttons; + + if (rb_hover != rb_hover_old || cb_hover != cb_hover_old) { + update(); + } } } void TabBar::_update_cache() { + if (tabs.is_empty()) { + 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_minus_buttons = get_size().width - incr->get_width() - decr->get_width(); + + int limit = get_size().width; + int limit_minus_buttons = limit - incr->get_width() - decr->get_width(); int w = 0; - int mw = 0; - int size_fixed = 0; - int count_resize = 0; + + max_drawn_tab = tabs.size() - 1; + for (int i = 0; i < tabs.size(); i++) { - tabs.write[i].ofs_cache = mw; - tabs.write[i].size_cache = get_tab_width(i); - tabs.write[i].size_text = Math::ceil(tabs[i].text_buf->get_size().x); tabs.write[i].text_buf->set_width(-1); - mw += tabs[i].size_cache; - if (tabs[i].size_cache <= min_width || i == current) { - size_fixed += tabs[i].size_cache; - } else { - count_resize++; + tabs.write[i].size_text = Math::ceil(tabs[i].text_buf->get_size().x); + tabs.write[i].size_cache = get_tab_width(i); + + if (max_width > 0 && tabs[i].size_cache > max_width) { + int size_textless = tabs[i].size_cache - tabs[i].size_text; + int mw = MAX(size_textless, max_width); + + tabs.write[i].size_text = MAX(mw - size_textless, 1); + tabs.write[i].text_buf->set_width(tabs[i].size_text); + tabs.write[i].size_cache = size_textless + tabs[i].size_text; } - } - int m_width = min_width; - if (count_resize > 0) { - m_width = MAX((limit_minus_buttons - size_fixed) / count_resize, min_width); - } - for (int i = offset; i < tabs.size(); i++) { - Ref<StyleBox> sb; - if (tabs[i].disabled) { - sb = tab_disabled; - } else if (i == current) { - sb = tab_selected; - } else { - sb = tab_unselected; + + if (i < offset || i > max_drawn_tab) { + tabs.write[i].ofs_cache = 0; + continue; } - int lsize = tabs[i].size_cache; - int slen = tabs[i].size_text; - if (min_width > 0 && mw > limit_minus_buttons && i != current) { - if (lsize > m_width) { - slen = m_width - (sb->get_margin(SIDE_LEFT) + sb->get_margin(SIDE_RIGHT)); - if (tabs[i].icon.is_valid()) { - slen -= tabs[i].icon->get_width(); - slen -= get_theme_constant(SNAME("hseparation")); - } - if (cb_displaypolicy == CLOSE_BUTTON_SHOW_ALWAYS || (cb_displaypolicy == CLOSE_BUTTON_SHOW_ACTIVE_ONLY && i == current)) { - Ref<Texture2D> cb = get_theme_icon(SNAME("close")); - slen -= cb->get_width(); - slen -= get_theme_constant(SNAME("hseparation")); + + tabs.write[i].ofs_cache = w; + + if (tabs[i].hidden) { + continue; + } + + w += tabs[i].size_cache; + + // Check if all tabs would fit inside the area. + if (clip_tabs && i > offset && (w > limit || (offset > 0 && w > limit_minus_buttons))) { + tabs.write[i].ofs_cache = 0; + + w -= tabs[i].size_cache; + max_drawn_tab = i - 1; + + while (w > limit_minus_buttons && max_drawn_tab > offset) { + tabs.write[max_drawn_tab].ofs_cache = 0; + + if (!tabs[max_drawn_tab].hidden) { + w -= tabs[max_drawn_tab].size_cache; } - slen = MAX(slen, 1); - lsize = m_width; + + max_drawn_tab--; } } + } + + missing_right = max_drawn_tab < tabs.size() - 1; + buttons_visible = offset > 0 || missing_right; + + if (tab_alignment == ALIGNMENT_LEFT) { + _update_hover(); + return; + } + + if (tab_alignment == ALIGNMENT_CENTER) { + w = ((buttons_visible ? limit_minus_buttons : limit) - w) / 2; + } else if (tab_alignment == ALIGNMENT_RIGHT) { + w = (buttons_visible ? limit_minus_buttons : limit) - w; + } + + for (int i = offset; i <= max_drawn_tab; i++) { tabs.write[i].ofs_cache = w; - tabs.write[i].size_cache = lsize; - tabs.write[i].size_text = slen; - tabs.write[i].text_buf->set_width(slen); - w += lsize; + + if (!tabs[i].hidden) { + w += tabs[i].size_cache; + } } + + _update_hover(); } void TabBar::_on_mouse_exited() { @@ -764,59 +912,79 @@ void TabBar::_on_mouse_exited() { void TabBar::add_tab(const String &p_str, const Ref<Texture2D> &p_icon) { Tab t; t.text = p_str; - t.xl_text = atr(p_str); - t.text_buf.instantiate(); t.text_buf->set_direction(is_layout_rtl() ? TextServer::DIRECTION_RTL : TextServer::DIRECTION_LTR); - t.text_buf->add_string(t.xl_text, get_theme_font(SNAME("font")), get_theme_font_size(SNAME("font_size")), Dictionary(), TranslationServer::get_singleton()->get_tool_locale()); t.icon = p_icon; - t.disabled = false; - t.ofs_cache = 0; - t.size_cache = 0; - tabs.push_back(t); + + _shape(tabs.size() - 1); _update_cache(); - call_deferred(SNAME("_update_hover")); + if (scroll_to_selected) { + ensure_tab_visible(current); + } update(); - minimum_size_changed(); + update_minimum_size(); + + if (tabs.size() == 1 && is_inside_tree()) { + emit_signal(SNAME("tab_changed"), 0); + } } void TabBar::clear_tabs() { + if (tabs.is_empty()) { + return; + } + tabs.clear(); + offset = 0; + max_drawn_tab = 0; current = 0; previous = 0; - call_deferred(SNAME("_update_hover")); + update(); + update_minimum_size(); + notify_property_list_changed(); } void TabBar::remove_tab(int p_idx) { ERR_FAIL_INDEX(p_idx, tabs.size()); - tabs.remove(p_idx); - if (current >= p_idx) { + tabs.remove_at(p_idx); + + bool is_tab_changing = current == p_idx && !tabs.is_empty(); + + if (current >= p_idx && current > 0) { current--; } - _update_cache(); - call_deferred(SNAME("_update_hover")); - update(); - minimum_size_changed(); - if (current < 0) { - current = 0; + if (tabs.is_empty()) { + offset = 0; + max_drawn_tab = 0; previous = 0; - } - if (current >= tabs.size()) { - current = tabs.size() - 1; + } else { + offset = MIN(offset, tabs.size() - 1); + max_drawn_tab = MIN(max_drawn_tab, tabs.size() - 1); + + _update_cache(); + _ensure_no_over_offset(); + if (scroll_to_selected) { + ensure_tab_visible(current); + } } - _ensure_no_over_offset(); + update(); + update_minimum_size(); + notify_property_list_changed(); + + if (is_tab_changing && is_inside_tree()) { + emit_signal(SNAME("tab_changed"), current); + } } Variant TabBar::get_drag_data(const Point2 &p_point) { if (!drag_to_rearrange_enabled) { - return Variant(); + return Control::get_drag_data(p_point); // Allow stuff like TabContainer to override it. } int tab_over = get_tab_idx_at_point(p_point); - if (tab_over < 0) { return Variant(); } @@ -826,27 +994,26 @@ Variant TabBar::get_drag_data(const Point2 &p_point) { if (!tabs[tab_over].icon.is_null()) { TextureRect *tf = memnew(TextureRect); tf->set_texture(tabs[tab_over].icon); + tf->set_stretch_mode(TextureRect::STRETCH_KEEP_CENTERED); drag_preview->add_child(tf); } + Label *label = memnew(Label(tabs[tab_over].xl_text)); drag_preview->add_child(label); - if (!tabs[tab_over].right_button.is_null()) { - TextureRect *tf = memnew(TextureRect); - tf->set_texture(tabs[tab_over].right_button); - drag_preview->add_child(tf); - } + set_drag_preview(drag_preview); Dictionary drag_data; drag_data["type"] = "tab_element"; drag_data["tab_element"] = tab_over; drag_data["from_path"] = get_path(); + return drag_data; } bool TabBar::can_drop_data(const Point2 &p_point, const Variant &p_data) const { if (!drag_to_rearrange_enabled) { - return false; + return Control::can_drop_data(p_point, p_data); // Allow stuff like TabContainer to override it. } Dictionary d = p_data; @@ -860,7 +1027,7 @@ bool TabBar::can_drop_data(const Point2 &p_point, const Variant &p_data) const { if (from_path == to_path) { return true; } else if (get_tabs_rearrange_group() != -1) { - // drag and drop between other TabBars + // Drag and drop between other TabBars. Node *from_node = get_node(from_path); TabBar *from_tabs = Object::cast_to<TabBar>(from_node); if (from_tabs && from_tabs->get_tabs_rearrange_group() == get_tabs_rearrange_group()) { @@ -868,16 +1035,16 @@ bool TabBar::can_drop_data(const Point2 &p_point, const Variant &p_data) const { } } } + return false; } void TabBar::drop_data(const Point2 &p_point, const Variant &p_data) { if (!drag_to_rearrange_enabled) { + Control::drop_data(p_point, p_data); // Allow stuff like TabContainer to override it. return; } - int hover_now = get_tab_idx_at_point(p_point); - Dictionary d = p_data; if (!d.has("type")) { return; @@ -885,36 +1052,56 @@ void TabBar::drop_data(const Point2 &p_point, const Variant &p_data) { if (String(d["type"]) == "tab_element") { int tab_from_id = d["tab_element"]; + int hover_now = get_tab_idx_at_point(p_point); NodePath from_path = d["from_path"]; NodePath to_path = get_path(); + if (from_path == to_path) { if (hover_now < 0) { hover_now = get_tab_count() - 1; } + move_tab(tab_from_id, hover_now); emit_signal(SNAME("active_tab_rearranged"), hover_now); set_current_tab(hover_now); } else if (get_tabs_rearrange_group() != -1) { - // drag and drop between Tabs + // Drag and drop between Tabs. + Node *from_node = get_node(from_path); TabBar *from_tabs = Object::cast_to<TabBar>(from_node); + if (from_tabs && from_tabs->get_tabs_rearrange_group() == get_tabs_rearrange_group()) { if (tab_from_id >= from_tabs->get_tab_count()) { return; } + Tab moving_tab = from_tabs->tabs[tab_from_id]; if (hover_now < 0) { hover_now = get_tab_count(); } - tabs.insert(hover_now, moving_tab); + from_tabs->remove_tab(tab_from_id); + tabs.insert(hover_now, moving_tab); + + if (tabs.size() > 1) { + if (current >= hover_now) { + current++; + } + if (previous >= hover_now) { + previous++; + } + } + set_current_tab(hover_now); - emit_signal(SNAME("tab_changed"), hover_now); - _update_cache(); + update_minimum_size(); + + if (tabs.size() == 1) { + emit_signal(SNAME("tab_selected"), 0); + emit_signal(SNAME("tab_changed"), 0); + } } } } - update(); } int TabBar::get_tab_idx_at_point(const Point2 &p_point) const { @@ -929,14 +1116,16 @@ int TabBar::get_tab_idx_at_point(const Point2 &p_point) const { return hover_now; } -void TabBar::set_tab_align(TabAlign p_align) { - ERR_FAIL_INDEX(p_align, ALIGN_MAX); - tab_align = p_align; +void TabBar::set_tab_alignment(AlignmentMode p_alignment) { + ERR_FAIL_INDEX(p_alignment, ALIGNMENT_MAX); + tab_alignment = p_alignment; + + _update_cache(); update(); } -TabBar::TabAlign TabBar::get_tab_align() const { - return tab_align; +TabBar::AlignmentMode TabBar::get_tab_alignment() const { + return tab_alignment; } void TabBar::set_clip_tabs(bool p_clip_tabs) { @@ -944,28 +1133,59 @@ void TabBar::set_clip_tabs(bool p_clip_tabs) { return; } clip_tabs = p_clip_tabs; + + if (!clip_tabs) { + offset = 0; + max_drawn_tab = 0; + } + + _update_cache(); + if (scroll_to_selected) { + ensure_tab_visible(current); + } update(); - minimum_size_changed(); + update_minimum_size(); } bool TabBar::get_clip_tabs() const { return clip_tabs; } -void TabBar::move_tab(int from, int to) { - if (from == to) { +void TabBar::move_tab(int p_from, int p_to) { + if (p_from == p_to) { return; } - ERR_FAIL_INDEX(from, tabs.size()); - ERR_FAIL_INDEX(to, tabs.size()); + ERR_FAIL_INDEX(p_from, tabs.size()); + ERR_FAIL_INDEX(p_to, tabs.size()); + + Tab tab_from = tabs[p_from]; + tabs.remove_at(p_from); + tabs.insert(p_to, tab_from); + + if (current == p_from) { + current = p_to; + } else if (current > p_from && current <= p_to) { + current--; + } else if (current < p_from && current >= p_to) { + current++; + } - Tab tab_from = tabs[from]; - tabs.remove(from); - tabs.insert(to, tab_from); + if (previous == p_from) { + previous = p_to; + } else if (previous > p_from && previous >= p_to) { + previous--; + } else if (previous < p_from && previous <= p_to) { + previous++; + } _update_cache(); + _ensure_no_over_offset(); + if (scroll_to_selected) { + ensure_tab_visible(current); + } update(); + notify_property_list_changed(); } int TabBar::get_tab_width(int p_idx) const { @@ -974,101 +1194,134 @@ int TabBar::get_tab_width(int p_idx) const { Ref<StyleBox> tab_unselected = get_theme_stylebox(SNAME("tab_unselected")); Ref<StyleBox> tab_selected = get_theme_stylebox(SNAME("tab_selected")); Ref<StyleBox> tab_disabled = get_theme_stylebox(SNAME("tab_disabled")); + int hseparation = get_theme_constant(SNAME("hseparation")); - int x = 0; + Ref<StyleBox> style; + + if (tabs[p_idx].disabled) { + style = tab_disabled; + } else if (current == p_idx) { + style = tab_selected; + } else { + style = tab_unselected; + } + int x = style->get_minimum_size().width; Ref<Texture2D> tex = tabs[p_idx].icon; if (tex.is_valid()) { - x += tex->get_width(); - if (tabs[p_idx].text != "") { - x += get_theme_constant(SNAME("hseparation")); - } + x += tex->get_width() + hseparation; } - x += Math::ceil(tabs[p_idx].text_buf->get_size().x); - - if (tabs[p_idx].disabled) { - x += tab_disabled->get_minimum_size().width; - } else if (current == p_idx) { - x += tab_selected->get_minimum_size().width; - } else { - x += tab_unselected->get_minimum_size().width; + if (!tabs[p_idx].text.is_empty()) { + x += tabs[p_idx].size_text + hseparation; } + bool close_visible = cb_displaypolicy == CLOSE_BUTTON_SHOW_ALWAYS || (cb_displaypolicy == CLOSE_BUTTON_SHOW_ACTIVE_ONLY && p_idx == current); + if (tabs[p_idx].right_button.is_valid()) { + Ref<StyleBox> btn_style = get_theme_stylebox(SNAME("button_highlight")); Ref<Texture2D> rb = tabs[p_idx].right_button; - x += rb->get_width(); - x += get_theme_constant(SNAME("hseparation")); + + if (close_visible) { + x += btn_style->get_minimum_size().width + rb->get_width(); + } else { + x += btn_style->get_margin(SIDE_LEFT) + rb->get_width() + hseparation; + } } - if (cb_displaypolicy == CLOSE_BUTTON_SHOW_ALWAYS || (cb_displaypolicy == CLOSE_BUTTON_SHOW_ACTIVE_ONLY && p_idx == current)) { + if (close_visible) { + Ref<StyleBox> btn_style = get_theme_stylebox(SNAME("button_highlight")); Ref<Texture2D> cb = get_theme_icon(SNAME("close")); - x += cb->get_width(); - x += get_theme_constant(SNAME("hseparation")); + x += btn_style->get_margin(SIDE_LEFT) + cb->get_width() + hseparation; + } + + if (x > style->get_minimum_size().width) { + x -= hseparation; } return x; } void TabBar::_ensure_no_over_offset() { - if (!is_inside_tree()) { + if (!is_inside_tree() || !buttons_visible) { return; } 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 = get_size().width - incr->get_width() - decr->get_width(); - while (offset > 0) { - int total_w = 0; - for (int i = offset - 1; i < tabs.size(); i++) { - total_w += tabs[i].size_cache; + int prev_offset = offset; + + int total_w = tabs[max_drawn_tab].ofs_cache + tabs[max_drawn_tab].size_cache - tabs[offset].ofs_cache; + for (int i = offset; i > 0; i--) { + if (tabs[i - 1].hidden) { + continue; } - if ((buttons_visible && total_w < limit_minus_buttons) || total_w < limit) { // For the last tab, we accept if the tab covers the buttons. + total_w += tabs[i - 1].size_cache; + + if (total_w < limit_minus_buttons) { offset--; - update(); } else { break; } } -} -void TabBar::ensure_tab_visible(int p_idx) { - if (!is_inside_tree()) { - return; + if (prev_offset != offset) { + _update_cache(); + update(); } +} - if (tabs.size() == 0) { +void TabBar::ensure_tab_visible(int p_idx) { + if (!is_inside_tree() || !buttons_visible) { return; } ERR_FAIL_INDEX(p_idx, tabs.size()); - if (p_idx == offset) { + if (tabs[p_idx].hidden || (p_idx >= offset && p_idx <= max_drawn_tab)) { return; } + if (p_idx < offset) { offset = p_idx; + _update_cache(); update(); + return; } - int prev_offset = offset; 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 = get_size().width - incr->get_width() - decr->get_width(); - for (int i = offset; i <= p_idx; i++) { - int total_w = tabs[i].ofs_cache + tabs[i].size_cache; - if (total_w > limit || (buttons_visible && total_w > limit_minus_buttons)) { + int total_w = tabs[max_drawn_tab].ofs_cache - tabs[offset].ofs_cache; + for (int i = max_drawn_tab; i <= p_idx; i++) { + if (tabs[i].hidden) { + continue; + } + + total_w += tabs[i].size_cache; + } + + int prev_offset = offset; + + for (int i = offset; i < p_idx; i++) { + if (tabs[i].hidden) { + continue; + } + + if (total_w > limit_minus_buttons) { + total_w -= tabs[i].size_cache; offset++; + } else { + break; } } if (prev_offset != offset) { + _update_cache(); update(); } } @@ -1085,15 +1338,35 @@ Rect2 TabBar::get_tab_rect(int p_tab) const { void TabBar::set_tab_close_display_policy(CloseButtonDisplayPolicy p_policy) { ERR_FAIL_INDEX(p_policy, CLOSE_BUTTON_MAX); cb_displaypolicy = p_policy; + + _update_cache(); + _ensure_no_over_offset(); + if (scroll_to_selected) { + ensure_tab_visible(current); + } update(); + update_minimum_size(); } TabBar::CloseButtonDisplayPolicy TabBar::get_tab_close_display_policy() const { return cb_displaypolicy; } -void TabBar::set_min_width(int p_width) { - min_width = p_width; +void TabBar::set_max_tab_width(int p_width) { + ERR_FAIL_COND(p_width < 0); + max_width = p_width; + + _update_cache(); + _ensure_no_over_offset(); + if (scroll_to_selected) { + ensure_tab_visible(current); + } + update(); + update_minimum_size(); +} + +int TabBar::get_max_tab_width() const { + return max_width; } void TabBar::set_scrolling_enabled(bool p_enabled) { @@ -1120,6 +1393,17 @@ int TabBar::get_tabs_rearrange_group() const { return tabs_rearrange_group; } +void TabBar::set_scroll_to_selected(bool p_enabled) { + scroll_to_selected = p_enabled; + if (p_enabled) { + ensure_tab_visible(current); + } +} + +bool TabBar::get_scroll_to_selected() const { + return scroll_to_selected; +} + void TabBar::set_select_with_rmb(bool p_enabled) { select_with_rmb = p_enabled; } @@ -1128,8 +1412,60 @@ bool TabBar::get_select_with_rmb() const { return select_with_rmb; } +bool TabBar::_set(const StringName &p_name, const Variant &p_value) { + Vector<String> components = String(p_name).split("/", true, 2); + if (components.size() >= 2 && components[0].begins_with("tab_") && components[0].trim_prefix("tab_").is_valid_int()) { + int tab_index = components[0].trim_prefix("tab_").to_int(); + String property = components[1]; + if (property == "title") { + set_tab_title(tab_index, p_value); + return true; + } else if (property == "icon") { + set_tab_icon(tab_index, p_value); + return true; + } else if (components[1] == "disabled") { + set_tab_disabled(tab_index, p_value); + return true; + } + } + return false; +} + +bool TabBar::_get(const StringName &p_name, Variant &r_ret) const { + Vector<String> components = String(p_name).split("/", true, 2); + if (components.size() >= 2 && components[0].begins_with("tab_") && components[0].trim_prefix("tab_").is_valid_int()) { + int tab_index = components[0].trim_prefix("tab_").to_int(); + String property = components[1]; + if (property == "title") { + r_ret = get_tab_title(tab_index); + return true; + } else if (property == "icon") { + r_ret = get_tab_icon(tab_index); + return true; + } else if (components[1] == "disabled") { + r_ret = is_tab_disabled(tab_index); + return true; + } + } + return false; +} + +void TabBar::_get_property_list(List<PropertyInfo> *p_list) const { + for (int i = 0; i < tabs.size(); i++) { + p_list->push_back(PropertyInfo(Variant::STRING, vformat("tab_%d/title", i))); + + PropertyInfo pi = PropertyInfo(Variant::OBJECT, vformat("tab_%d/icon", i), PROPERTY_HINT_RESOURCE_TYPE, "Texture2D"); + pi.usage &= ~(get_tab_icon(i).is_null() ? PROPERTY_USAGE_STORAGE : 0); + p_list->push_back(pi); + + pi = PropertyInfo(Variant::BOOL, vformat("tab_%d/disabled", i)); + pi.usage &= ~(!is_tab_disabled(i) ? PROPERTY_USAGE_STORAGE : 0); + p_list->push_back(pi); + } +} + void TabBar::_bind_methods() { - ClassDB::bind_method(D_METHOD("_update_hover"), &TabBar::_update_hover); + ClassDB::bind_method(D_METHOD("set_tab_count", "count"), &TabBar::set_tab_count); ClassDB::bind_method(D_METHOD("get_tab_count"), &TabBar::get_tab_count); ClassDB::bind_method(D_METHOD("set_current_tab", "tab_idx"), &TabBar::set_current_tab); ClassDB::bind_method(D_METHOD("get_current_tab"), &TabBar::get_current_tab); @@ -1145,12 +1481,17 @@ void TabBar::_bind_methods() { ClassDB::bind_method(D_METHOD("get_tab_language", "tab_idx"), &TabBar::get_tab_language); ClassDB::bind_method(D_METHOD("set_tab_icon", "tab_idx", "icon"), &TabBar::set_tab_icon); ClassDB::bind_method(D_METHOD("get_tab_icon", "tab_idx"), &TabBar::get_tab_icon); + ClassDB::bind_method(D_METHOD("set_tab_button_icon", "tab_idx", "icon"), &TabBar::set_tab_button_icon); + ClassDB::bind_method(D_METHOD("get_tab_button_icon", "tab_idx"), &TabBar::get_tab_button_icon); ClassDB::bind_method(D_METHOD("set_tab_disabled", "tab_idx", "disabled"), &TabBar::set_tab_disabled); - ClassDB::bind_method(D_METHOD("get_tab_disabled", "tab_idx"), &TabBar::get_tab_disabled); + ClassDB::bind_method(D_METHOD("is_tab_disabled", "tab_idx"), &TabBar::is_tab_disabled); + ClassDB::bind_method(D_METHOD("set_tab_hidden", "tab_idx", "hidden"), &TabBar::set_tab_hidden); + ClassDB::bind_method(D_METHOD("is_tab_hidden", "tab_idx"), &TabBar::is_tab_hidden); ClassDB::bind_method(D_METHOD("remove_tab", "tab_idx"), &TabBar::remove_tab); ClassDB::bind_method(D_METHOD("add_tab", "title", "icon"), &TabBar::add_tab, DEFVAL(""), DEFVAL(Ref<Texture2D>())); - ClassDB::bind_method(D_METHOD("set_tab_align", "align"), &TabBar::set_tab_align); - ClassDB::bind_method(D_METHOD("get_tab_align"), &TabBar::get_tab_align); + ClassDB::bind_method(D_METHOD("get_tab_idx_at_point", "point"), &TabBar::get_tab_idx_at_point); + ClassDB::bind_method(D_METHOD("set_tab_alignment", "alignment"), &TabBar::set_tab_alignment); + ClassDB::bind_method(D_METHOD("get_tab_alignment"), &TabBar::get_tab_alignment); ClassDB::bind_method(D_METHOD("set_clip_tabs", "clip_tabs"), &TabBar::set_clip_tabs); ClassDB::bind_method(D_METHOD("get_clip_tabs"), &TabBar::get_clip_tabs); ClassDB::bind_method(D_METHOD("get_tab_offset"), &TabBar::get_tab_offset); @@ -1160,34 +1501,45 @@ void TabBar::_bind_methods() { ClassDB::bind_method(D_METHOD("move_tab", "from", "to"), &TabBar::move_tab); ClassDB::bind_method(D_METHOD("set_tab_close_display_policy", "policy"), &TabBar::set_tab_close_display_policy); ClassDB::bind_method(D_METHOD("get_tab_close_display_policy"), &TabBar::get_tab_close_display_policy); + ClassDB::bind_method(D_METHOD("set_max_tab_width", "width"), &TabBar::set_max_tab_width); + ClassDB::bind_method(D_METHOD("get_max_tab_width"), &TabBar::get_max_tab_width); ClassDB::bind_method(D_METHOD("set_scrolling_enabled", "enabled"), &TabBar::set_scrolling_enabled); ClassDB::bind_method(D_METHOD("get_scrolling_enabled"), &TabBar::get_scrolling_enabled); ClassDB::bind_method(D_METHOD("set_drag_to_rearrange_enabled", "enabled"), &TabBar::set_drag_to_rearrange_enabled); ClassDB::bind_method(D_METHOD("get_drag_to_rearrange_enabled"), &TabBar::get_drag_to_rearrange_enabled); ClassDB::bind_method(D_METHOD("set_tabs_rearrange_group", "group_id"), &TabBar::set_tabs_rearrange_group); ClassDB::bind_method(D_METHOD("get_tabs_rearrange_group"), &TabBar::get_tabs_rearrange_group); - + ClassDB::bind_method(D_METHOD("set_scroll_to_selected", "enabled"), &TabBar::set_scroll_to_selected); + ClassDB::bind_method(D_METHOD("get_scroll_to_selected"), &TabBar::get_scroll_to_selected); ClassDB::bind_method(D_METHOD("set_select_with_rmb", "enabled"), &TabBar::set_select_with_rmb); ClassDB::bind_method(D_METHOD("get_select_with_rmb"), &TabBar::get_select_with_rmb); + ADD_SIGNAL(MethodInfo("tab_selected", PropertyInfo(Variant::INT, "tab"))); ADD_SIGNAL(MethodInfo("tab_changed", PropertyInfo(Variant::INT, "tab"))); + ADD_SIGNAL(MethodInfo("tab_clicked", PropertyInfo(Variant::INT, "tab"))); ADD_SIGNAL(MethodInfo("tab_rmb_clicked", PropertyInfo(Variant::INT, "tab"))); ADD_SIGNAL(MethodInfo("tab_close_pressed", PropertyInfo(Variant::INT, "tab"))); + ADD_SIGNAL(MethodInfo("tab_button_pressed", PropertyInfo(Variant::INT, "tab"))); ADD_SIGNAL(MethodInfo("tab_hovered", PropertyInfo(Variant::INT, "tab"))); ADD_SIGNAL(MethodInfo("active_tab_rearranged", PropertyInfo(Variant::INT, "idx_to"))); - ADD_SIGNAL(MethodInfo("tab_clicked", PropertyInfo(Variant::INT, "tab"))); ADD_PROPERTY(PropertyInfo(Variant::INT, "current_tab", PROPERTY_HINT_RANGE, "-1,4096,1", PROPERTY_USAGE_EDITOR), "set_current_tab", "get_current_tab"); - ADD_PROPERTY(PropertyInfo(Variant::INT, "tab_align", PROPERTY_HINT_ENUM, "Left,Center,Right"), "set_tab_align", "get_tab_align"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "tab_alignment", PROPERTY_HINT_ENUM, "Left,Center,Right"), "set_tab_alignment", "get_tab_alignment"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "clip_tabs"), "set_clip_tabs", "get_clip_tabs"); ADD_PROPERTY(PropertyInfo(Variant::INT, "tab_close_display_policy", PROPERTY_HINT_ENUM, "Show Never,Show Active Only,Show Always"), "set_tab_close_display_policy", "get_tab_close_display_policy"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "max_tab_width", PROPERTY_HINT_RANGE, "0,99999,1"), "set_max_tab_width", "get_max_tab_width"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "scrolling_enabled"), "set_scrolling_enabled", "get_scrolling_enabled"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "drag_to_rearrange_enabled"), "set_drag_to_rearrange_enabled", "get_drag_to_rearrange_enabled"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "tabs_rearrange_group"), "set_tabs_rearrange_group", "get_tabs_rearrange_group"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "scroll_to_selected"), "set_scroll_to_selected", "get_scroll_to_selected"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "select_with_rmb"), "set_select_with_rmb", "get_select_with_rmb"); + + ADD_ARRAY_COUNT("Tabs", "tab_count", "set_tab_count", "get_tab_count", "tab_"); - BIND_ENUM_CONSTANT(ALIGN_LEFT); - BIND_ENUM_CONSTANT(ALIGN_CENTER); - BIND_ENUM_CONSTANT(ALIGN_RIGHT); - BIND_ENUM_CONSTANT(ALIGN_MAX); + BIND_ENUM_CONSTANT(ALIGNMENT_LEFT); + BIND_ENUM_CONSTANT(ALIGNMENT_CENTER); + BIND_ENUM_CONSTANT(ALIGNMENT_RIGHT); + BIND_ENUM_CONSTANT(ALIGNMENT_MAX); BIND_ENUM_CONSTANT(CLOSE_BUTTON_SHOW_NEVER); BIND_ENUM_CONSTANT(CLOSE_BUTTON_SHOW_ACTIVE_ONLY); @@ -1196,5 +1548,6 @@ void TabBar::_bind_methods() { } TabBar::TabBar() { + set_size(Size2(get_size().width, get_minimum_size().height)); connect("mouse_exited", callable_mp(this, &TabBar::_on_mouse_exited)); } diff --git a/scene/gui/tab_bar.h b/scene/gui/tab_bar.h index 411a62b1d9..0f2184aca7 100644 --- a/scene/gui/tab_bar.h +++ b/scene/gui/tab_bar.h @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ @@ -38,11 +38,11 @@ class TabBar : public Control { GDCLASS(TabBar, Control); public: - enum TabAlign { - ALIGN_LEFT, - ALIGN_CENTER, - ALIGN_RIGHT, - ALIGN_MAX + enum AlignmentMode { + ALIGNMENT_LEFT, + ALIGNMENT_CENTER, + ALIGNMENT_RIGHT, + ALIGNMENT_MAX, }; enum CloseButtonDisplayPolicy { @@ -63,16 +63,19 @@ private: Ref<TextLine> text_buf; Ref<Texture2D> icon; - int ofs_cache = 0; bool disabled = false; + bool hidden = false; + int ofs_cache = 0; int size_cache = 0; int size_text = 0; - int x_cache = 0; - int x_size_cache = 0; Ref<Texture2D> right_button; Rect2 rb_rect; Rect2 cb_rect; + + Tab() { + text_buf.instantiate(); + } }; int offset = 0; @@ -83,7 +86,7 @@ private: Vector<Tab> tabs; int current = 0; int previous = 0; - TabAlign tab_align = ALIGN_CENTER; + AlignmentMode tab_alignment = ALIGNMENT_CENTER; bool clip_tabs = true; int rb_hover = -1; bool rb_pressing = false; @@ -95,9 +98,10 @@ private: CloseButtonDisplayPolicy cb_displaypolicy = CLOSE_BUTTON_SHOW_NEVER; int hover = -1; // Hovered tab. - int min_width = 0; + int max_width = 0; bool scrolling_enabled = true; bool drag_to_rearrange_enabled = false; + bool scroll_to_selected = true; int tabs_rearrange_group = -1; int get_tab_width(int p_idx) const; @@ -109,16 +113,19 @@ private: void _on_mouse_exited(); void _shape(int p_tab); + void _draw_tab(Ref<StyleBox> &p_tab_style, Color &p_font_color, int p_index, float p_x); protected: virtual void gui_input(const Ref<InputEvent> &p_event) 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; void _notification(int p_what); static void _bind_methods(); Variant get_drag_data(const Point2 &p_point) override; bool can_drop_data(const Point2 &p_point, const Variant &p_data) const override; void drop_data(const Point2 &p_point, const Variant &p_data) override; - int get_tab_idx_at_point(const Point2 &p_point) const; public: void add_tab(const String &p_str = "", const Ref<Texture2D> &p_icon = Ref<Texture2D>()); @@ -140,23 +147,30 @@ public: Ref<Texture2D> get_tab_icon(int p_tab) const; void set_tab_disabled(int p_tab, bool p_disabled); - bool get_tab_disabled(int p_tab) const; + bool is_tab_disabled(int p_tab) const; + + void set_tab_hidden(int p_tab, bool p_hidden); + bool is_tab_hidden(int p_tab) const; - void set_tab_right_button(int p_tab, const Ref<Texture2D> &p_right_button); - Ref<Texture2D> get_tab_right_button(int p_tab) const; + void set_tab_button_icon(int p_tab, const Ref<Texture2D> &p_icon); + Ref<Texture2D> get_tab_button_icon(int p_tab) const; - void set_tab_align(TabAlign p_align); - TabAlign get_tab_align() const; + int get_tab_idx_at_point(const Point2 &p_point) const; + + void set_tab_alignment(AlignmentMode p_alignment); + AlignmentMode get_tab_alignment() const; void set_clip_tabs(bool p_clip_tabs); bool get_clip_tabs() const; - void move_tab(int from, int to); + void move_tab(int p_from, int p_to); void set_tab_close_display_policy(CloseButtonDisplayPolicy p_policy); CloseButtonDisplayPolicy get_tab_close_display_policy() const; + void set_tab_count(int p_count); int get_tab_count() const; + void set_current_tab(int p_current); int get_current_tab() const; int get_previous_tab() const; @@ -177,11 +191,16 @@ public: void set_tabs_rearrange_group(int p_group_id); int get_tabs_rearrange_group() const; + void set_scroll_to_selected(bool p_enabled); + bool get_scroll_to_selected() const; + void set_select_with_rmb(bool p_enabled); bool get_select_with_rmb() const; void ensure_tab_visible(int p_idx); - void set_min_width(int p_width); + + void set_max_tab_width(int p_width); + int get_max_tab_width() const; Rect2 get_tab_rect(int p_tab) const; Size2 get_minimum_size() const override; @@ -189,7 +208,7 @@ public: TabBar(); }; -VARIANT_ENUM_CAST(TabBar::TabAlign); +VARIANT_ENUM_CAST(TabBar::AlignmentMode); VARIANT_ENUM_CAST(TabBar::CloseButtonDisplayPolicy); #endif // TAB_BAR_H diff --git a/scene/gui/tab_container.cpp b/scene/gui/tab_container.cpp index c8a0501d8a..ee61c862b7 100644 --- a/scene/gui/tab_container.cpp +++ b/scene/gui/tab_container.cpp @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ @@ -30,45 +30,17 @@ #include "tab_container.h" -#include "core/object/message_queue.h" -#include "core/string/translation.h" - #include "scene/gui/box_container.h" #include "scene/gui/label.h" #include "scene/gui/texture_rect.h" int TabContainer::_get_top_margin() const { - if (!tabs_visible) { - return 0; - } - - // Respect the minimum tab height. - 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 tab_height = MAX(MAX(tab_unselected->get_minimum_size().height, tab_selected->get_minimum_size().height), tab_disabled->get_minimum_size().height); - - // Font height or higher icon wins. - int content_height = 0; - - Vector<Control *> tabs = _get_tabs(); - for (int i = 0; i < tabs.size(); i++) { - content_height = MAX(content_height, text_buf[i]->get_size().y); - - Control *c = tabs[i]; - if (!c->has_meta("_tab_icon")) { - continue; - } - - Ref<Texture2D> tex = c->get_meta("_tab_icon"); - if (!tex.is_valid()) { - continue; - } - content_height = MAX(content_height, tex->get_size().height); + int height = 0; + if (tabs_visible && get_tab_count() > 0) { + height = tab_bar->get_minimum_size().height; } - return tab_height + content_height; + return height; } void TabContainer::gui_input(const Ref<InputEvent> &p_event) { @@ -78,7 +50,7 @@ void TabContainer::gui_input(const Ref<InputEvent> &p_event) { Popup *popup = get_popup(); - if (mb.is_valid() && mb->is_pressed() && mb->get_button_index() == MOUSE_BUTTON_LEFT) { + if (mb.is_valid() && mb->is_pressed() && mb->get_button_index() == MouseButton::LEFT) { Point2 pos = mb->get_position(); Size2 size = get_size(); @@ -114,77 +86,6 @@ void TabContainer::gui_input(const Ref<InputEvent> &p_event) { return; } } - - // Do not activate tabs when tabs is empty. - if (get_tab_count() == 0) { - return; - } - - Vector<Control *> tabs = _get_tabs(); - - // Handle navigation buttons. - if (buttons_visible_cache) { - int popup_ofs = 0; - if (popup) { - popup_ofs = menu->get_width(); - } - - Ref<Texture2D> increment = get_theme_icon(SNAME("increment")); - Ref<Texture2D> decrement = get_theme_icon(SNAME("decrement")); - if (is_layout_rtl()) { - if (pos.x < popup_ofs + decrement->get_width()) { - if (last_tab_cache < tabs.size() - 1) { - first_tab_cache += 1; - update(); - } - return; - } else if (pos.x < popup_ofs + increment->get_width() + decrement->get_width()) { - if (first_tab_cache > 0) { - first_tab_cache -= 1; - update(); - } - return; - } - } else { - if (pos.x > size.width - increment->get_width() - popup_ofs && pos.x) { - if (last_tab_cache < tabs.size() - 1) { - first_tab_cache += 1; - update(); - } - return; - } else if (pos.x > size.width - increment->get_width() - decrement->get_width() - popup_ofs) { - if (first_tab_cache > 0) { - first_tab_cache -= 1; - update(); - } - return; - } - } - } - - // Activate the clicked tab. - if (is_layout_rtl()) { - pos.x = size.width - pos.x; - } - - if (pos.x < tabs_ofs_cache) { - return; - } - - pos.x -= tabs_ofs_cache; - for (int i = first_tab_cache; i <= last_tab_cache; i++) { - if (get_tab_hidden(i)) { - continue; - } - int tab_width = _get_tab_width(i); - if (pos.x < tab_width) { - if (!get_tab_disabled(i)) { - set_current_tab(i); - } - break; - } - pos.x -= tab_width; - } } Ref<InputEventMouseMotion> mm = p_event; @@ -195,9 +96,8 @@ void TabContainer::gui_input(const Ref<InputEvent> &p_event) { // Mouse must be on tabs in the tab header area. if (pos.y > _get_top_margin()) { - if (menu_hovered || highlight_arrow > -1) { + if (menu_hovered) { menu_hovered = false; - highlight_arrow = -1; update(); } return; @@ -209,7 +109,6 @@ void TabContainer::gui_input(const Ref<InputEvent> &p_event) { if (pos.x <= menu->get_width()) { if (!menu_hovered) { menu_hovered = true; - highlight_arrow = -1; update(); return; } @@ -221,7 +120,6 @@ void TabContainer::gui_input(const Ref<InputEvent> &p_event) { if (pos.x >= size.width - menu->get_width()) { if (!menu_hovered) { menu_hovered = true; - highlight_arrow = -1; update(); return; } @@ -235,101 +133,26 @@ void TabContainer::gui_input(const Ref<InputEvent> &p_event) { return; } } - - // Do not activate tabs when tabs is empty. - if ((get_tab_count() == 0 || !buttons_visible_cache) && menu_hovered) { - highlight_arrow = -1; - update(); - return; - } - - int popup_ofs = 0; - if (popup) { - popup_ofs = menu->get_width(); - } - - Ref<Texture2D> increment = get_theme_icon(SNAME("increment")); - Ref<Texture2D> decrement = get_theme_icon(SNAME("decrement")); - - if (is_layout_rtl()) { - if (pos.x <= popup_ofs + decrement->get_width()) { - if (highlight_arrow != 1) { - highlight_arrow = 1; - update(); - } - } else if (pos.x <= popup_ofs + increment->get_width() + decrement->get_width()) { - if (highlight_arrow != 0) { - highlight_arrow = 0; - update(); - } - } else if (highlight_arrow > -1) { - highlight_arrow = -1; - update(); - } - } else { - if (pos.x >= size.width - increment->get_width() - popup_ofs) { - if (highlight_arrow != 1) { - highlight_arrow = 1; - update(); - } - } else if (pos.x >= size.width - increment->get_width() - decrement->get_width() - popup_ofs) { - if (highlight_arrow != 0) { - highlight_arrow = 0; - update(); - } - } else if (highlight_arrow > -1) { - highlight_arrow = -1; - update(); - } - } } } void TabContainer::_notification(int p_what) { switch (p_what) { - case NOTIFICATION_RESIZED: { - Vector<Control *> tabs = _get_tabs(); - int side_margin = get_theme_constant(SNAME("side_margin")); - Ref<Texture2D> menu = get_theme_icon(SNAME("menu")); - Ref<Texture2D> increment = get_theme_icon(SNAME("increment")); - Ref<Texture2D> decrement = get_theme_icon(SNAME("decrement")); - int header_width = get_size().width - side_margin * 2; - - // Find the width of the header area. - Popup *popup = get_popup(); - if (popup) { - header_width -= menu->get_width(); + case NOTIFICATION_ENTER_TREE: { + // If some nodes happen to be renamed outside the tree, the tab names need to be updated manually. + if (get_tab_count() > 0) { + _refresh_tab_names(); } - if (buttons_visible_cache) { - header_width -= increment->get_width() + decrement->get_width(); - } - if (popup || buttons_visible_cache) { - header_width += side_margin; - } - - // Find the width of all tabs after first_tab_cache. - int all_tabs_width = 0; - for (int i = first_tab_cache; i < tabs.size(); i++) { - int tab_width = _get_tab_width(i); - all_tabs_width += tab_width; - } - - // Check if tabs before first_tab_cache would fit into the header area. - for (int i = first_tab_cache - 1; i >= 0; i--) { - int tab_width = _get_tab_width(i); - - if (all_tabs_width + tab_width > header_width) { - break; - } + } break; - all_tabs_width += tab_width; - first_tab_cache--; - } + case NOTIFICATION_READY: + case NOTIFICATION_RESIZED: { + _update_margins(); } break; + case NOTIFICATION_DRAW: { RID canvas = get_canvas_item(); Size2 size = get_size(); - bool rtl = is_layout_rtl(); // Draw only the tab area if the header is hidden. Ref<StyleBox> panel = get_theme_stylebox(SNAME("panel")); @@ -338,494 +161,171 @@ void TabContainer::_notification(int p_what) { return; } - Vector<Control *> tabs = _get_tabs(); - 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<Texture2D> increment = get_theme_icon(SNAME("increment")); - Ref<Texture2D> increment_hl = get_theme_icon(SNAME("increment_highlight")); - Ref<Texture2D> decrement = get_theme_icon(SNAME("decrement")); - Ref<Texture2D> decrement_hl = get_theme_icon(SNAME("decrement_highlight")); - Ref<Texture2D> menu = get_theme_icon(SNAME("menu")); - Ref<Texture2D> menu_hl = get_theme_icon(SNAME("menu_highlight")); - 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")); - int side_margin = get_theme_constant(SNAME("side_margin")); - - // Find out start and width of the header area. - int header_x = side_margin; - int header_width = size.width - side_margin * 2; int header_height = _get_top_margin(); - Popup *popup = get_popup(); - if (popup) { - header_width -= menu->get_width(); - } - - // Check if all tabs would fit into the header area. - int all_tabs_width = 0; - for (int i = 0; i < tabs.size(); i++) { - if (get_tab_hidden(i)) { - continue; - } - int tab_width = _get_tab_width(i); - all_tabs_width += tab_width; - - if (all_tabs_width > header_width) { - // Not all tabs are visible at the same time - reserve space for navigation buttons. - buttons_visible_cache = true; - header_width -= decrement->get_width() + increment->get_width(); - break; - } else { - buttons_visible_cache = false; - } - } - // With buttons, a right side margin does not need to be respected. - if (popup || buttons_visible_cache) { - header_width += side_margin; - } - - if (!buttons_visible_cache) { - first_tab_cache = 0; - } - // Go through the visible tabs to find the width they occupy. - all_tabs_width = 0; - Vector<int> tab_widths; - for (int i = first_tab_cache; i < tabs.size(); i++) { - if (get_tab_hidden(i)) { - tab_widths.push_back(0); - continue; - } - int tab_width = _get_tab_width(i); - if (all_tabs_width + tab_width > header_width && tab_widths.size() > 0) { - break; - } - all_tabs_width += tab_width; - tab_widths.push_back(tab_width); - } - - // Find the offset at which to draw tabs, according to the alignment. - switch (align) { - case ALIGN_LEFT: - tabs_ofs_cache = header_x; - break; - case ALIGN_CENTER: - tabs_ofs_cache = header_x + (header_width / 2) - (all_tabs_width / 2); - break; - case ALIGN_RIGHT: - tabs_ofs_cache = header_x + header_width - all_tabs_width; - break; - } - - if (all_tabs_in_front) { - // Draw the tab area. - panel->draw(canvas, Rect2(0, header_height, size.width, size.height - header_height)); - } + panel->draw(canvas, Rect2(0, header_height, size.width, size.height - header_height)); - // Draw unselected tabs in back - int x = 0; - int x_current = 0; - int index = 0; - for (int i = 0; i < tab_widths.size(); i++) { - index = i + first_tab_cache; - if (get_tab_hidden(index)) { - continue; - } - - int tab_width = tab_widths[i]; - if (index == current) { - x_current = x; - } else if (get_tab_disabled(index)) { - if (rtl) { - _draw_tab(tab_disabled, font_disabled_color, index, size.width - (tabs_ofs_cache + x) - tab_width); - } else { - _draw_tab(tab_disabled, font_disabled_color, index, tabs_ofs_cache + x); - } - } else { - if (rtl) { - _draw_tab(tab_unselected, font_unselected_color, index, size.width - (tabs_ofs_cache + x) - tab_width); - } else { - _draw_tab(tab_unselected, font_unselected_color, index, tabs_ofs_cache + x); - } - } - - x += tab_width; - last_tab_cache = index; - } - - if (!all_tabs_in_front) { - // Draw the tab area. - panel->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")); - // Draw selected tab in front. Only draw selected tab when it's in visible range. - if (tabs.size() > 0 && current - first_tab_cache < tab_widths.size() && current >= first_tab_cache) { - Ref<StyleBox> current_style_box = get_tab_disabled(current) ? tab_disabled : tab_selected; - if (rtl) { - _draw_tab(current_style_box, font_selected_color, current, size.width - (tabs_ofs_cache + x_current) - tab_widths[current]); - } else { - _draw_tab(current_style_box, font_selected_color, current, tabs_ofs_cache + x_current); - } - } + int x = is_layout_rtl() ? 0 : get_size().width - menu->get_width(); - // Draw the popup menu. - if (rtl) { - x = 0; - } else { - x = get_size().width; - } - if (popup) { - if (!rtl) { - x -= menu->get_width(); - } if (menu_hovered) { menu_hl->draw(get_canvas_item(), Size2(x, (header_height - menu_hl->get_height()) / 2)); } else { menu->draw(get_canvas_item(), Size2(x, (header_height - menu->get_height()) / 2)); } - if (rtl) { - x += menu->get_width(); - } - } - - // Draw the navigation buttons. - if (buttons_visible_cache) { - if (rtl) { - if (last_tab_cache < tabs.size() - 1) { - draw_texture(highlight_arrow == 1 ? decrement_hl : decrement, Point2(x, (header_height - increment->get_height()) / 2)); - } else { - draw_texture(decrement, Point2(x, (header_height - increment->get_height()) / 2), Color(1, 1, 1, 0.5)); - } - x += increment->get_width(); - - if (first_tab_cache > 0) { - draw_texture(highlight_arrow == 0 ? increment_hl : increment, Point2(x, (header_height - decrement->get_height()) / 2)); - } else { - draw_texture(increment, Point2(x, (header_height - decrement->get_height()) / 2), Color(1, 1, 1, 0.5)); - } - x += decrement->get_width(); - } else { - x -= increment->get_width(); - if (last_tab_cache < tabs.size() - 1) { - draw_texture(highlight_arrow == 1 ? increment_hl : increment, Point2(x, (header_height - increment->get_height()) / 2)); - } else { - draw_texture(increment, Point2(x, (header_height - increment->get_height()) / 2), Color(1, 1, 1, 0.5)); - } - - x -= decrement->get_width(); - if (first_tab_cache > 0) { - draw_texture(highlight_arrow == 0 ? decrement_hl : decrement, Point2(x, (header_height - decrement->get_height()) / 2)); - } else { - draw_texture(decrement, Point2(x, (header_height - decrement->get_height()) / 2), Color(1, 1, 1, 0.5)); - } - } } } break; + case NOTIFICATION_TRANSLATION_CHANGED: case NOTIFICATION_LAYOUT_DIRECTION_CHANGED: case NOTIFICATION_THEME_CHANGED: { - Vector<Control *> tabs = _get_tabs(); - for (int i = 0; i < tabs.size(); i++) { - text_buf.write[i]->clear(); - } - _theme_changing = true; + theme_changing = true; call_deferred(SNAME("_on_theme_changed")); // Wait until all changed theme. } break; } } -void TabContainer::_draw_tab(Ref<StyleBox> &p_tab_style, Color &p_font_color, int p_index, float p_x) { - Control *control = get_tab_control(p_index); - RID canvas = get_canvas_item(); - Color font_outline_color = get_theme_color(SNAME("font_outline_color")); - int outline_size = get_theme_constant(SNAME("outline_size")); - int icon_text_distance = get_theme_constant(SNAME("icon_separation")); - int tab_width = _get_tab_width(p_index); - int header_height = _get_top_margin(); - - // Draw the tab background. - Rect2 tab_rect(p_x, 0, tab_width, header_height); - p_tab_style->draw(canvas, tab_rect); - - // Draw the tab contents. - String text = control->has_meta("_tab_name") ? String(atr(String(control->get_meta("_tab_name")))) : String(atr(control->get_name())); - - int x_content = tab_rect.position.x + p_tab_style->get_margin(SIDE_LEFT); - int top_margin = p_tab_style->get_margin(SIDE_TOP); - int y_center = top_margin + (tab_rect.size.y - p_tab_style->get_minimum_size().y) / 2; - - // Draw the tab icon. - if (control->has_meta("_tab_icon")) { - Ref<Texture2D> icon = control->get_meta("_tab_icon"); - if (icon.is_valid()) { - int y = y_center - (icon->get_height() / 2); - icon->draw(canvas, Point2i(x_content, y)); - if (text != "") { - x_content += icon->get_width() + icon_text_distance; - } - } - } - - // Draw the tab text. - Point2i text_pos(x_content, y_center - text_buf[p_index]->get_size().y / 2); - if (outline_size > 0 && font_outline_color.a > 0) { - text_buf[p_index]->draw_outline(canvas, text_pos, outline_size, font_outline_color); - } - text_buf[p_index]->draw(canvas, text_pos, p_font_color); -} - -void TabContainer::_refresh_texts() { - text_buf.clear(); - Vector<Control *> tabs = _get_tabs(); - bool rtl = is_layout_rtl(); - Ref<Font> font = get_theme_font(SNAME("font")); - int font_size = get_theme_font_size(SNAME("font_size")); - for (int i = 0; i < tabs.size(); i++) { - Control *control = Object::cast_to<Control>(tabs[i]); - String text = control->has_meta("_tab_name") ? String(atr(String(control->get_meta("_tab_name")))) : String(atr(control->get_name())); - - Ref<TextLine> name; - name.instantiate(); - name->set_direction(rtl ? TextServer::DIRECTION_RTL : TextServer::DIRECTION_LTR); - name->add_string(text, font, font_size, Dictionary(), TranslationServer::get_singleton()->get_tool_locale()); - text_buf.push_back(name); - } -} - void TabContainer::_on_theme_changed() { - if (!_theme_changing) { + if (!theme_changing) { return; } - _refresh_texts(); - - minimum_size_changed(); + 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_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_constant_override(SNAME("font_size"), get_theme_constant(SNAME("font_size"))); + tab_bar->add_theme_constant_override(SNAME("icon_separation"), get_theme_constant(SNAME("icon_separation"))); + tab_bar->add_theme_constant_override(SNAME("outline_size"), get_theme_constant(SNAME("outline_size"))); + + _update_margins(); if (get_tab_count() > 0) { _repaint(); - update(); + } else { + update_minimum_size(); } - _theme_changing = false; + update(); + + theme_changing = false; } void TabContainer::_repaint() { Ref<StyleBox> sb = get_theme_stylebox(SNAME("panel")); - Vector<Control *> tabs = _get_tabs(); - for (int i = 0; i < tabs.size(); i++) { - Control *c = tabs[i]; + Vector<Control *> controls = _get_tab_controls(); + int current = get_current_tab(); + + for (int i = 0; i < controls.size(); i++) { + Control *c = controls[i]; + if (i == current) { c->show(); c->set_anchors_and_offsets_preset(Control::PRESET_WIDE); + if (tabs_visible) { 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)); - } else { c->hide(); } } -} -void TabContainer::_on_mouse_exited() { - if (menu_hovered || highlight_arrow > -1) { - menu_hovered = false; - highlight_arrow = -1; - update(); - } + update_minimum_size(); } -int TabContainer::_get_tab_width(int p_index) const { - ERR_FAIL_INDEX_V(p_index, get_tab_count(), 0); - Control *control = get_tab_control(p_index); - if (!control || get_tab_hidden(p_index)) { - return 0; - } +void TabContainer::_update_margins() { + int menu_width = get_theme_icon(SNAME("menu"))->get_width(); + int side_margin = get_theme_constant(SNAME("side_margin")); - // Get the width of the text displayed on the tab. - Ref<Font> font = get_theme_font(SNAME("font")); - int font_size = get_theme_font_size(SNAME("font_size")); - String text = control->has_meta("_tab_name") ? String(atr(String(control->get_meta("_tab_name")))) : String(atr(control->get_name())); - int width = font->get_string_size(text, font_size).width; - - // Add space for a tab icon. - if (control->has_meta("_tab_icon")) { - Ref<Texture2D> icon = control->get_meta("_tab_icon"); - if (icon.is_valid()) { - width += icon->get_width(); - if (text != "") { - width += get_theme_constant(SNAME("icon_separation")); - } - } - } - - // Respect a minimum size. - 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")); - if (get_tab_disabled(p_index)) { - width += tab_disabled->get_minimum_size().width; - } else if (p_index == current) { - width += tab_selected->get_minimum_size().width; - } else { - width += tab_unselected->get_minimum_size().width; - } - - return width; -} - -Vector<Control *> TabContainer::_get_tabs() const { - Vector<Control *> controls; - for (int i = 0; i < get_child_count(); i++) { - Control *control = Object::cast_to<Control>(get_child(i)); - if (!control || control->is_set_as_top_level()) { - continue; - } - - controls.push_back(control); - } - return controls; -} - -void TabContainer::_child_renamed_callback() { - _refresh_texts(); - update(); -} + // Directly check for validity, to avoid errors when quitting. + bool has_popup = popup_obj_id.is_valid(); -void TabContainer::add_child_notify(Node *p_child) { - Container::add_child_notify(p_child); + if (get_tab_count() == 0) { + tab_bar->set_offset(SIDE_LEFT, 0); + tab_bar->set_offset(SIDE_RIGHT, has_popup ? -menu_width : 0); - Control *c = Object::cast_to<Control>(p_child); - if (!c || c->is_set_as_top_level()) { return; } - Vector<Control *> tabs = _get_tabs(); - _refresh_texts(); - - bool first = false; - - if (tabs.size() != 1) { - c->hide(); - } else { - c->show(); - //call_deferred(SNAME("set_current_tab"),0); - first = true; - current = 0; - previous = 0; - } - c->set_anchors_and_offsets_preset(Control::PRESET_WIDE); - if (tabs_visible) { - c->set_offset(SIDE_TOP, _get_top_margin()); - } - Ref<StyleBox> sb = get_theme_stylebox(SNAME("panel")); - 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)); - update(); - p_child->connect("renamed", callable_mp(this, &TabContainer::_child_renamed_callback)); - if (first && is_inside_tree()) { - emit_signal(SNAME("tab_changed"), current); - } -} - -void TabContainer::move_child_notify(Node *p_child) { - Container::move_child_notify(p_child); - - Control *c = Object::cast_to<Control>(p_child); - if (!c || c->is_set_as_top_level()) { - return; - } + switch (get_tab_alignment()) { + case TabBar::ALIGNMENT_LEFT: { + tab_bar->set_offset(SIDE_LEFT, side_margin); + tab_bar->set_offset(SIDE_RIGHT, has_popup ? -menu_width : 0); + } break; - _update_current_tab(); - update(); -} + case TabBar::ALIGNMENT_CENTER: { + tab_bar->set_offset(SIDE_LEFT, 0); + tab_bar->set_offset(SIDE_RIGHT, has_popup ? -menu_width : 0); + } break; -int TabContainer::get_tab_count() const { - return _get_tabs().size(); -} + case TabBar::ALIGNMENT_RIGHT: { + tab_bar->set_offset(SIDE_LEFT, 0); -void TabContainer::set_current_tab(int p_current) { - ERR_FAIL_INDEX(p_current, get_tab_count()); + if (has_popup) { + tab_bar->set_offset(SIDE_RIGHT, -menu_width); + return; + } - int pending_previous = current; - current = p_current; + int first_tab_pos = tab_bar->get_tab_rect(0).position.x; + Rect2 last_tab_rect = tab_bar->get_tab_rect(get_tab_count() - 1); + int total_tabs_width = last_tab_rect.position.x - first_tab_pos + last_tab_rect.size.width; - _repaint(); + // 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))) { + tab_bar->set_offset(SIDE_RIGHT, has_popup ? -menu_width : 0); + } else { + tab_bar->set_offset(SIDE_RIGHT, -side_margin); + } + } break; - if (pending_previous == current) { - emit_signal(SNAME("tab_selected"), current); - } else { - previous = pending_previous; - emit_signal(SNAME("tab_selected"), current); - emit_signal(SNAME("tab_changed"), current); + case TabBar::ALIGNMENT_MAX: + break; // Can't happen, but silences warning. } - - update(); } -int TabContainer::get_current_tab() const { - return current; -} - -int TabContainer::get_previous_tab() const { - return previous; -} - -Control *TabContainer::get_tab_control(int p_idx) const { - Vector<Control *> tabs = _get_tabs(); - if (p_idx >= 0 && p_idx < tabs.size()) { - return tabs[p_idx]; - } else { - return nullptr; +void TabContainer::_on_mouse_exited() { + if (menu_hovered) { + menu_hovered = false; + update(); } } -Control *TabContainer::get_current_tab_control() const { - return get_tab_control(current); -} - -void TabContainer::remove_child_notify(Node *p_child) { - Container::remove_child_notify(p_child); +Vector<Control *> TabContainer::_get_tab_controls() const { + Vector<Control *> controls; + for (int i = 0; i < get_child_count(); i++) { + Control *control = Object::cast_to<Control>(get_child(i)); + if (!control || control->is_set_as_top_level() || control == tab_bar) { + continue; + } - Control *c = Object::cast_to<Control>(p_child); - if (!c || c->is_set_as_top_level()) { - return; + controls.push_back(control); } - // Defer the call because tab is not yet removed (remove_child_notify is called right before p_child is actually removed). - call_deferred(SNAME("_update_current_tab")); - - p_child->disconnect("renamed", callable_mp(this, &TabContainer::_child_renamed_callback)); - - update(); -} - -void TabContainer::_update_current_tab() { - _refresh_texts(); - - int tc = get_tab_count(); - if (current >= tc) { - current = tc - 1; - } - if (current < 0) { - current = 0; - } else { - set_current_tab(current); - } + return controls; } -Variant TabContainer::get_drag_data(const Point2 &p_point) { +Variant TabContainer::_get_drag_data_fw(const Point2 &p_point, Control *p_from_control) { if (!drag_to_rearrange_enabled) { return Variant(); } int tab_over = get_tab_idx_at_point(p_point); - if (tab_over < 0) { return Variant(); } @@ -838,18 +338,20 @@ Variant TabContainer::get_drag_data(const Point2 &p_point) { tf->set_texture(icon); drag_preview->add_child(tf); } + Label *label = memnew(Label(get_tab_title(tab_over))); - drag_preview->add_child(label); set_drag_preview(drag_preview); + drag_preview->add_child(label); Dictionary drag_data; drag_data["type"] = "tabc_element"; drag_data["tabc_element"] = tab_over; drag_data["from_path"] = get_path(); + return drag_data; } -bool TabContainer::can_drop_data(const Point2 &p_point, const Variant &p_data) const { +bool TabContainer::_can_drop_data_fw(const Point2 &p_point, const Variant &p_data, Control *p_from_control) const { if (!drag_to_rearrange_enabled) { return false; } @@ -865,7 +367,7 @@ bool TabContainer::can_drop_data(const Point2 &p_point, const Variant &p_data) c if (from_path == to_path) { return true; } else if (get_tabs_rearrange_group() != -1) { - // drag and drop between other TabContainers + // Drag and drop between other TabContainers. Node *from_node = get_node(from_path); TabContainer *from_tabc = Object::cast_to<TabContainer>(from_node); if (from_tabc && from_tabc->get_tabs_rearrange_group() == get_tabs_rearrange_group()) { @@ -873,10 +375,11 @@ bool TabContainer::can_drop_data(const Point2 &p_point, const Variant &p_data) c } } } + return false; } -void TabContainer::drop_data(const Point2 &p_point, const Variant &p_data) { +void TabContainer::_drop_data_fw(const Point2 &p_point, const Variant &p_data, Control *p_from_control) { if (!drag_to_rearrange_enabled) { return; } @@ -896,85 +399,195 @@ void TabContainer::drop_data(const Point2 &p_point, const Variant &p_data) { if (hover_now < 0) { hover_now = get_tab_count() - 1; } - move_child(get_tab_control(tab_from_id), get_tab_control(hover_now)->get_index()); - set_current_tab(hover_now); + + move_child(get_tab_control(tab_from_id), get_tab_control(hover_now)->get_index(false)); + if (!is_tab_disabled(hover_now)) { + set_current_tab(hover_now); + } + } else if (get_tabs_rearrange_group() != -1) { - // drag and drop between TabContainers + // Drag and drop between TabContainers. Node *from_node = get_node(from_path); TabContainer *from_tabc = Object::cast_to<TabContainer>(from_node); if (from_tabc && from_tabc->get_tabs_rearrange_group() == get_tabs_rearrange_group()) { Control *moving_tabc = from_tabc->get_tab_control(tab_from_id); from_tabc->remove_child(moving_tabc); - add_child(moving_tabc, false, INTERNAL_MODE_FRONT); + add_child(moving_tabc, true); + if (hover_now < 0) { hover_now = get_tab_count() - 1; } - move_child(moving_tabc, get_tab_control(hover_now)->get_index()); - set_current_tab(hover_now); - emit_signal(SNAME("tab_changed"), hover_now); + + move_child(moving_tabc, get_tab_control(hover_now)->get_index(false)); + if (!is_tab_disabled(hover_now)) { + set_current_tab(hover_now); + } } } } - update(); } -int TabContainer::get_tab_idx_at_point(const Point2 &p_point) const { - if (get_tab_count() == 0) { - return -1; +void TabContainer::_on_tab_changed(int p_tab) { + call_deferred(SNAME("_repaint")); + + emit_signal(SNAME("tab_changed"), p_tab); +} + +void TabContainer::_on_tab_selected(int p_tab) { + if (p_tab != get_previous_tab()) { + call_deferred(SNAME("_repaint")); + } + + emit_signal(SNAME("tab_selected"), p_tab); +} + +void TabContainer::_refresh_tab_names() { + Vector<Control *> controls = _get_tab_controls(); + for (int i = 0; i < controls.size(); i++) { + if (!controls[i]->has_meta("_tab_name") && String(controls[i]->get_name()) != get_tab_title(i)) { + tab_bar->set_tab_title(i, controls[i]->get_name()); + } + } +} + +void TabContainer::add_child_notify(Node *p_child) { + if (p_child == tab_bar) { + return; + } + + Container::add_child_notify(p_child); + + Control *c = Object::cast_to<Control>(p_child); + if (!c || c->is_set_as_top_level()) { + return; } + c->hide(); + + tab_bar->add_tab(p_child->get_name()); + + _update_margins(); + + p_child->connect("renamed", callable_mp(this, &TabContainer::_refresh_tab_names)); - // must be on tabs in the tab header area. - if (p_point.y > _get_top_margin()) { - return -1; + // TabBar won't emit the "tab_changed" signal when not inside the tree. + if (!is_inside_tree()) { + call_deferred("_repaint"); } +} - Size2 size = get_size(); - int button_ofs = 0; - int px = p_point.x; +void TabContainer::move_child_notify(Node *p_child) { + if (p_child == tab_bar) { + return; + } + + Container::move_child_notify(p_child); + + Control *c = Object::cast_to<Control>(p_child); + if (c && !c->is_set_as_top_level()) { + int old_idx = -1; + String tab_name = c->has_meta("_tab_name") ? String(c->get_meta("_tab_name")) : String(c->get_name()); + + // Find the previous tab index of the control. + for (int i = 0; i < get_tab_count(); i++) { + if (get_tab_title(i) == tab_name) { + old_idx = i; + break; + } + } - if (is_layout_rtl()) { - px = size.width - px; + tab_bar->move_tab(old_idx, get_tab_idx_from_control(c)); } +} - if (px < tabs_ofs_cache) { - return -1; +void TabContainer::remove_child_notify(Node *p_child) { + if (p_child == tab_bar) { + return; } - Popup *popup = get_popup(); - if (popup) { - Ref<Texture2D> menu = get_theme_icon(SNAME("menu")); - button_ofs += menu->get_width(); + Container::remove_child_notify(p_child); + + Control *c = Object::cast_to<Control>(p_child); + if (!c || c->is_set_as_top_level()) { + return; + } + + tab_bar->remove_tab(get_tab_idx_from_control(c)); + + _update_margins(); + + if (p_child->has_meta("_tab_name")) { + p_child->remove_meta("_tab_name"); } - if (buttons_visible_cache) { - Ref<Texture2D> increment = get_theme_icon(SNAME("increment")); - Ref<Texture2D> decrement = get_theme_icon(SNAME("decrement")); - button_ofs += increment->get_width() + decrement->get_width(); + p_child->disconnect("renamed", callable_mp(this, &TabContainer::_refresh_tab_names)); + + // TabBar won't emit the "tab_changed" signal when not inside the tree. + if (!is_inside_tree()) { + call_deferred("_repaint"); } - if (px > size.width - button_ofs) { - return -1; +} + +int TabContainer::get_tab_count() const { + return tab_bar->get_tab_count(); +} + +void TabContainer::set_current_tab(int p_current) { + tab_bar->set_current_tab(p_current); +} + +int TabContainer::get_current_tab() const { + return tab_bar->get_current_tab(); +} + +int TabContainer::get_previous_tab() const { + return tab_bar->get_previous_tab(); +} + +Control *TabContainer::get_tab_control(int p_idx) const { + Vector<Control *> controls = _get_tab_controls(); + if (p_idx >= 0 && p_idx < controls.size()) { + return controls[p_idx]; + } else { + return nullptr; } +} + +Control *TabContainer::get_current_tab_control() const { + return get_tab_control(tab_bar->get_current_tab()); +} - // get the tab at the point - Vector<Control *> tabs = _get_tabs(); - px -= tabs_ofs_cache; - for (int i = first_tab_cache; i <= last_tab_cache; i++) { - int tab_width = _get_tab_width(i); - if (px < tab_width) { +int TabContainer::get_tab_idx_at_point(const Point2 &p_point) const { + return tab_bar->get_tab_idx_at_point(p_point); +} + +int TabContainer::get_tab_idx_from_control(Control *p_child) const { + ERR_FAIL_NULL_V(p_child, -1); + ERR_FAIL_COND_V(p_child->get_parent() != this, -1); + + Vector<Control *> controls = _get_tab_controls(); + for (int i = 0; i < controls.size(); i++) { + if (controls[i] == p_child) { return i; } - px -= tab_width; } + return -1; } -void TabContainer::set_tab_align(TabAlign p_align) { - ERR_FAIL_INDEX(p_align, 3); - align = p_align; - update(); +void TabContainer::set_tab_alignment(TabBar::AlignmentMode p_alignment) { + tab_bar->set_tab_alignment(p_alignment); + _update_margins(); } -TabContainer::TabAlign TabContainer::get_tab_align() const { - return align; +TabBar::AlignmentMode TabContainer::get_tab_alignment() const { + return tab_bar->get_tab_alignment(); +} + +void TabContainer::set_clip_tabs(bool p_clip_tabs) { + tab_bar->set_clip_tabs(p_clip_tabs); +} + +bool TabContainer::get_clip_tabs() const { + return tab_bar->get_clip_tabs(); } void TabContainer::set_tabs_visible(bool p_visible) { @@ -983,11 +596,12 @@ void TabContainer::set_tabs_visible(bool p_visible) { } tabs_visible = p_visible; + tab_bar->set_visible(tabs_visible); - Vector<Control *> tabs = _get_tabs(); - for (int i = 0; i < tabs.size(); i++) { - Control *c = tabs[i]; - if (p_visible) { + Vector<Control *> controls = _get_tab_controls(); + for (int i = 0; i < controls.size(); i++) { + Control *c = controls[i]; + if (tabs_visible) { c->set_offset(SIDE_TOP, _get_top_margin()); } else { c->set_offset(SIDE_TOP, 0); @@ -995,7 +609,7 @@ void TabContainer::set_tabs_visible(bool p_visible) { } update(); - minimum_size_changed(); + update_minimum_size(); } bool TabContainer::are_tabs_visible() const { @@ -1009,7 +623,8 @@ void TabContainer::set_all_tabs_in_front(bool p_in_front) { all_tabs_in_front = p_in_front; - update(); + remove_child(tab_bar); + add_child(tab_bar, false, all_tabs_in_front ? INTERNAL_MODE_FRONT : INTERNAL_MODE_BACK); } bool TabContainer::is_all_tabs_in_front() const { @@ -1019,96 +634,79 @@ bool TabContainer::is_all_tabs_in_front() const { void TabContainer::set_tab_title(int p_tab, const String &p_title) { Control *child = get_tab_control(p_tab); ERR_FAIL_COND(!child); - child->set_meta("_tab_name", p_title); - _refresh_texts(); - update(); -} -String TabContainer::get_tab_title(int p_tab) const { - Control *child = get_tab_control(p_tab); - ERR_FAIL_COND_V(!child, ""); - if (child->has_meta("_tab_name")) { - return child->get_meta("_tab_name"); + tab_bar->set_tab_title(p_tab, p_title); + + if (p_title == child->get_name()) { + if (child->has_meta("_tab_name")) { + child->remove_meta("_tab_name"); + } } else { - return child->get_name(); + child->set_meta("_tab_name", p_title); + } + + _update_margins(); + if (!get_clip_tabs()) { + update_minimum_size(); } } +String TabContainer::get_tab_title(int p_tab) const { + return tab_bar->get_tab_title(p_tab); +} + void TabContainer::set_tab_icon(int p_tab, const Ref<Texture2D> &p_icon) { - Control *child = get_tab_control(p_tab); - ERR_FAIL_COND(!child); - child->set_meta("_tab_icon", p_icon); - update(); + tab_bar->set_tab_icon(p_tab, p_icon); + + _update_margins(); + _repaint(); } Ref<Texture2D> TabContainer::get_tab_icon(int p_tab) const { - Control *child = get_tab_control(p_tab); - ERR_FAIL_COND_V(!child, Ref<Texture2D>()); - if (child->has_meta("_tab_icon")) { - return child->get_meta("_tab_icon"); - } else { - return Ref<Texture2D>(); - } + return tab_bar->get_tab_icon(p_tab); } void TabContainer::set_tab_disabled(int p_tab, bool p_disabled) { - Control *child = get_tab_control(p_tab); - ERR_FAIL_COND(!child); - child->set_meta("_tab_disabled", p_disabled); - update(); -} + tab_bar->set_tab_disabled(p_tab, p_disabled); -bool TabContainer::get_tab_disabled(int p_tab) const { - Control *child = get_tab_control(p_tab); - ERR_FAIL_COND_V(!child, false); - if (child->has_meta("_tab_disabled")) { - return child->get_meta("_tab_disabled"); - } else { - return false; + _update_margins(); + if (!get_clip_tabs()) { + update_minimum_size(); } } +bool TabContainer::is_tab_disabled(int p_tab) const { + return tab_bar->is_tab_disabled(p_tab); +} + void TabContainer::set_tab_hidden(int p_tab, bool p_hidden) { Control *child = get_tab_control(p_tab); ERR_FAIL_COND(!child); - child->set_meta("_tab_hidden", p_hidden); - update(); - for (int i = 0; i < get_tab_count(); i++) { - int try_tab = (p_tab + 1 + i) % get_tab_count(); - if (get_tab_disabled(try_tab) || get_tab_hidden(try_tab)) { - continue; - } - - set_current_tab(try_tab); - return; - } - //assumed no other tab can be switched to, just hide + tab_bar->set_tab_hidden(p_tab, p_hidden); child->hide(); -} -bool TabContainer::get_tab_hidden(int p_tab) const { - Control *child = get_tab_control(p_tab); - ERR_FAIL_COND_V(!child, false); - if (child->has_meta("_tab_hidden")) { - return child->get_meta("_tab_hidden"); - } else { - return false; + _update_margins(); + if (!get_clip_tabs()) { + update_minimum_size(); } } +bool TabContainer::is_tab_hidden(int p_tab) const { + return tab_bar->is_tab_hidden(p_tab); +} + void TabContainer::get_translatable_strings(List<String> *p_strings) const { - Vector<Control *> tabs = _get_tabs(); - for (int i = 0; i < tabs.size(); i++) { - Control *c = tabs[i]; + Vector<Control *> controls = _get_tab_controls(); + for (int i = 0; i < controls.size(); i++) { + Control *c = controls[i]; if (!c->has_meta("_tab_name")) { continue; } String name = c->get_meta("_tab_name"); - - if (name != "") { + if (!name.is_empty()) { p_strings->push_back(name); } } @@ -1117,9 +715,26 @@ void TabContainer::get_translatable_strings(List<String> *p_strings) const { Size2 TabContainer::get_minimum_size() const { Size2 ms; - Vector<Control *> tabs = _get_tabs(); - for (int i = 0; i < tabs.size(); i++) { - Control *c = tabs[i]; + if (tabs_visible) { + ms = tab_bar->get_minimum_size(); + + if (!get_clip_tabs()) { + if (get_popup()) { + ms.x += get_theme_icon(SNAME("menu"))->get_width(); + } + + int side_margin = get_theme_constant(SNAME("side_margin")); + if (side_margin > 0 && get_tab_alignment() != TabBar::ALIGNMENT_CENTER && + (get_tab_alignment() != TabBar::ALIGNMENT_RIGHT || !get_popup())) { + ms.x += side_margin; + } + } + } + + Vector<Control *> controls = _get_tab_controls(); + int max_control_height = 0; + for (int i = 0; i < controls.size(); i++) { + Control *c = controls[i]; if (!c->is_visible_in_tree() && !use_hidden_tabs_for_min_size) { continue; @@ -1127,29 +742,30 @@ Size2 TabContainer::get_minimum_size() const { Size2 cms = c->get_combined_minimum_size(); ms.x = MAX(ms.x, cms.x); - ms.y = MAX(ms.y, cms.y); + max_control_height = MAX(max_control_height, cms.y); } + ms.y += max_control_height; - 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")); - - if (tabs_visible) { - ms.y += MAX(MAX(tab_unselected->get_minimum_size().y, tab_selected->get_minimum_size().y), tab_disabled->get_minimum_size().y); - ms.y += _get_top_margin(); - } - - Ref<StyleBox> sb = get_theme_stylebox(SNAME("panel")); - ms += sb->get_minimum_size(); + Size2 panel_ms = get_theme_stylebox(SNAME("panel"))->get_minimum_size(); + ms.x = MAX(ms.x, panel_ms.x); + ms.y += panel_ms.y; return ms; } void TabContainer::set_popup(Node *p_popup) { - ERR_FAIL_NULL(p_popup); + bool had_popup = get_popup(); + Popup *popup = Object::cast_to<Popup>(p_popup); popup_obj_id = popup ? popup->get_instance_id() : ObjectID(); - update(); + + if (had_popup != bool(popup)) { + update(); + _update_margins(); + if (!get_clip_tabs()) { + update_minimum_size(); + } + } } Popup *TabContainer::get_popup() const { @@ -1164,6 +780,7 @@ Popup *TabContainer::get_popup() const { popup_obj_id = ObjectID(); } } + return nullptr; } @@ -1176,21 +793,30 @@ bool TabContainer::get_drag_to_rearrange_enabled() const { } void TabContainer::set_tabs_rearrange_group(int p_group_id) { - tabs_rearrange_group = p_group_id; + tab_bar->set_tabs_rearrange_group(p_group_id); } int TabContainer::get_tabs_rearrange_group() const { - return tabs_rearrange_group; + return tab_bar->get_tabs_rearrange_group(); } void TabContainer::set_use_hidden_tabs_for_min_size(bool p_use_hidden_tabs) { use_hidden_tabs_for_min_size = p_use_hidden_tabs; + update_minimum_size(); } bool TabContainer::get_use_hidden_tabs_for_min_size() const { return use_hidden_tabs_for_min_size; } +Vector<int> TabContainer::get_allowed_size_flags_horizontal() const { + return Vector<int>(); +} + +Vector<int> TabContainer::get_allowed_size_flags_vertical() const { + return Vector<int>(); +} + void TabContainer::_bind_methods() { ClassDB::bind_method(D_METHOD("get_tab_count"), &TabContainer::get_tab_count); ClassDB::bind_method(D_METHOD("set_current_tab", "tab_idx"), &TabContainer::set_current_tab); @@ -1198,8 +824,10 @@ void TabContainer::_bind_methods() { ClassDB::bind_method(D_METHOD("get_previous_tab"), &TabContainer::get_previous_tab); ClassDB::bind_method(D_METHOD("get_current_tab_control"), &TabContainer::get_current_tab_control); ClassDB::bind_method(D_METHOD("get_tab_control", "tab_idx"), &TabContainer::get_tab_control); - ClassDB::bind_method(D_METHOD("set_tab_align", "align"), &TabContainer::set_tab_align); - ClassDB::bind_method(D_METHOD("get_tab_align"), &TabContainer::get_tab_align); + ClassDB::bind_method(D_METHOD("set_tab_alignment", "alignment"), &TabContainer::set_tab_alignment); + ClassDB::bind_method(D_METHOD("get_tab_alignment"), &TabContainer::get_tab_alignment); + ClassDB::bind_method(D_METHOD("set_clip_tabs", "clip_tabs"), &TabContainer::set_clip_tabs); + ClassDB::bind_method(D_METHOD("get_clip_tabs"), &TabContainer::get_clip_tabs); ClassDB::bind_method(D_METHOD("set_tabs_visible", "visible"), &TabContainer::set_tabs_visible); ClassDB::bind_method(D_METHOD("are_tabs_visible"), &TabContainer::are_tabs_visible); ClassDB::bind_method(D_METHOD("set_all_tabs_in_front", "is_front"), &TabContainer::set_all_tabs_in_front); @@ -1209,39 +837,47 @@ void TabContainer::_bind_methods() { ClassDB::bind_method(D_METHOD("set_tab_icon", "tab_idx", "icon"), &TabContainer::set_tab_icon); ClassDB::bind_method(D_METHOD("get_tab_icon", "tab_idx"), &TabContainer::get_tab_icon); ClassDB::bind_method(D_METHOD("set_tab_disabled", "tab_idx", "disabled"), &TabContainer::set_tab_disabled); - ClassDB::bind_method(D_METHOD("get_tab_disabled", "tab_idx"), &TabContainer::get_tab_disabled); + ClassDB::bind_method(D_METHOD("is_tab_disabled", "tab_idx"), &TabContainer::is_tab_disabled); ClassDB::bind_method(D_METHOD("set_tab_hidden", "tab_idx", "hidden"), &TabContainer::set_tab_hidden); - ClassDB::bind_method(D_METHOD("get_tab_hidden", "tab_idx"), &TabContainer::get_tab_hidden); + ClassDB::bind_method(D_METHOD("is_tab_hidden", "tab_idx"), &TabContainer::is_tab_hidden); ClassDB::bind_method(D_METHOD("get_tab_idx_at_point", "point"), &TabContainer::get_tab_idx_at_point); + ClassDB::bind_method(D_METHOD("get_tab_idx_from_control", "control"), &TabContainer::get_tab_idx_from_control); ClassDB::bind_method(D_METHOD("set_popup", "popup"), &TabContainer::set_popup); ClassDB::bind_method(D_METHOD("get_popup"), &TabContainer::get_popup); ClassDB::bind_method(D_METHOD("set_drag_to_rearrange_enabled", "enabled"), &TabContainer::set_drag_to_rearrange_enabled); ClassDB::bind_method(D_METHOD("get_drag_to_rearrange_enabled"), &TabContainer::get_drag_to_rearrange_enabled); ClassDB::bind_method(D_METHOD("set_tabs_rearrange_group", "group_id"), &TabContainer::set_tabs_rearrange_group); ClassDB::bind_method(D_METHOD("get_tabs_rearrange_group"), &TabContainer::get_tabs_rearrange_group); - ClassDB::bind_method(D_METHOD("set_use_hidden_tabs_for_min_size", "enabled"), &TabContainer::set_use_hidden_tabs_for_min_size); ClassDB::bind_method(D_METHOD("get_use_hidden_tabs_for_min_size"), &TabContainer::get_use_hidden_tabs_for_min_size); + ClassDB::bind_method(D_METHOD("_repaint"), &TabContainer::_repaint); ClassDB::bind_method(D_METHOD("_on_theme_changed"), &TabContainer::_on_theme_changed); - ClassDB::bind_method(D_METHOD("_update_current_tab"), &TabContainer::_update_current_tab); + ClassDB::bind_method(D_METHOD("_get_drag_data_fw"), &TabContainer::_get_drag_data_fw); + ClassDB::bind_method(D_METHOD("_can_drop_data_fw"), &TabContainer::_can_drop_data_fw); + ClassDB::bind_method(D_METHOD("_drop_data_fw"), &TabContainer::_drop_data_fw); ADD_SIGNAL(MethodInfo("tab_changed", PropertyInfo(Variant::INT, "tab"))); ADD_SIGNAL(MethodInfo("tab_selected", PropertyInfo(Variant::INT, "tab"))); ADD_SIGNAL(MethodInfo("pre_popup_pressed")); - ADD_PROPERTY(PropertyInfo(Variant::INT, "tab_align", PROPERTY_HINT_ENUM, "Left,Center,Right"), "set_tab_align", "get_tab_align"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "tab_alignment", PROPERTY_HINT_ENUM, "Left,Center,Right"), "set_tab_alignment", "get_tab_alignment"); ADD_PROPERTY(PropertyInfo(Variant::INT, "current_tab", PROPERTY_HINT_RANGE, "-1,4096,1", PROPERTY_USAGE_EDITOR), "set_current_tab", "get_current_tab"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "clip_tabs"), "set_clip_tabs", "get_clip_tabs"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "tabs_visible"), "set_tabs_visible", "are_tabs_visible"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "all_tabs_in_front"), "set_all_tabs_in_front", "is_all_tabs_in_front"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "drag_to_rearrange_enabled"), "set_drag_to_rearrange_enabled", "get_drag_to_rearrange_enabled"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "tabs_rearrange_group"), "set_tabs_rearrange_group", "get_tabs_rearrange_group"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "use_hidden_tabs_for_min_size"), "set_use_hidden_tabs_for_min_size", "get_use_hidden_tabs_for_min_size"); - - BIND_ENUM_CONSTANT(ALIGN_LEFT); - BIND_ENUM_CONSTANT(ALIGN_CENTER); - BIND_ENUM_CONSTANT(ALIGN_RIGHT); } TabContainer::TabContainer() { + tab_bar = memnew(TabBar); + tab_bar->set_drag_forwarding(this); + add_child(tab_bar, false, INTERNAL_MODE_FRONT); + tab_bar->set_anchors_and_offsets_preset(Control::PRESET_TOP_WIDE); + tab_bar->connect("tab_changed", callable_mp(this, &TabContainer::_on_tab_changed)); + tab_bar->connect("tab_selected", callable_mp(this, &TabContainer::_on_tab_selected)); + connect("mouse_exited", callable_mp(this, &TabContainer::_on_mouse_exited)); } diff --git a/scene/gui/tab_container.h b/scene/gui/tab_container.h index fe96df25e8..c54934b37b 100644 --- a/scene/gui/tab_container.h +++ b/scene/gui/tab_container.h @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ @@ -33,65 +33,51 @@ #include "scene/gui/container.h" #include "scene/gui/popup.h" -#include "scene/resources/text_line.h" +#include "scene/gui/tab_bar.h" class TabContainer : public Container { GDCLASS(TabContainer, Container); -public: - enum TabAlign { - ALIGN_LEFT, - ALIGN_CENTER, - ALIGN_RIGHT - }; - -private: - int first_tab_cache = 0; - int tabs_ofs_cache = 0; - int last_tab_cache = 0; - int current = 0; - int previous = 0; + TabBar *tab_bar; bool tabs_visible = true; bool all_tabs_in_front = false; - bool buttons_visible_cache = false; bool menu_hovered = false; - int highlight_arrow = -1; - TabAlign align = ALIGN_CENTER; - int _get_top_margin() const; mutable ObjectID popup_obj_id; bool drag_to_rearrange_enabled = false; bool use_hidden_tabs_for_min_size = false; - int tabs_rearrange_group = -1; + bool theme_changing = false; - Vector<Ref<TextLine>> text_buf; - Vector<Control *> _get_tabs() const; - int _get_tab_width(int p_index) const; - bool _theme_changing = false; + int _get_top_margin() const; + Vector<Control *> _get_tab_controls() const; void _on_theme_changed(); void _repaint(); + void _refresh_tab_names(); + void _update_margins(); void _on_mouse_exited(); - void _update_current_tab(); - void _draw_tab(Ref<StyleBox> &p_tab_style, Color &p_font_color, int p_index, float p_x); - void _refresh_texts(); + void _on_tab_changed(int p_tab); + void _on_tab_selected(int p_tab); + + Variant _get_drag_data_fw(const Point2 &p_point, Control *p_from_control); + bool _can_drop_data_fw(const Point2 &p_point, const Variant &p_data, Control *p_from_control) const; + void _drop_data_fw(const Point2 &p_point, const Variant &p_data, Control *p_from_control); protected: - void _child_renamed_callback(); virtual void gui_input(const Ref<InputEvent> &p_event) 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(); - Variant get_drag_data(const Point2 &p_point) override; - bool can_drop_data(const Point2 &p_point, const Variant &p_data) const override; - void drop_data(const Point2 &p_point, const Variant &p_data) override; +public: int get_tab_idx_at_point(const Point2 &p_point) const; + int get_tab_idx_from_control(Control *p_child) const; - static void _bind_methods(); + void set_tab_alignment(TabBar::AlignmentMode p_alignment); + TabBar::AlignmentMode get_tab_alignment() const; -public: - void set_tab_align(TabAlign p_align); - TabAlign get_tab_align() const; + void set_clip_tabs(bool p_clip_tabs); + bool get_clip_tabs() const; void set_tabs_visible(bool p_visible); bool are_tabs_visible() const; @@ -106,10 +92,10 @@ public: Ref<Texture2D> get_tab_icon(int p_tab) const; void set_tab_disabled(int p_tab, bool p_disabled); - bool get_tab_disabled(int p_tab) const; + bool is_tab_disabled(int p_tab) const; void set_tab_hidden(int p_tab, bool p_hidden); - bool get_tab_hidden(int p_tab) const; + bool is_tab_hidden(int p_tab) const; int get_tab_count() const; void set_current_tab(int p_current); @@ -133,9 +119,10 @@ public: void set_use_hidden_tabs_for_min_size(bool p_use_hidden_tabs); bool get_use_hidden_tabs_for_min_size() const; + virtual Vector<int> get_allowed_size_flags_horizontal() const override; + virtual Vector<int> get_allowed_size_flags_vertical() const override; + TabContainer(); }; -VARIANT_ENUM_CAST(TabContainer::TabAlign); - #endif // TAB_CONTAINER_H diff --git a/scene/gui/text_edit.cpp b/scene/gui/text_edit.cpp index cb7a6c0978..3c80e3f987 100644 --- a/scene/gui/text_edit.cpp +++ b/scene/gui/text_edit.cpp @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ @@ -39,21 +39,10 @@ #include "core/os/os.h" #include "core/string/string_builder.h" #include "core/string/translation.h" +#include "label.h" #include "scene/main/window.h" -static bool _is_text_char(char32_t c) { - return !is_symbol(c); -} - -static bool _is_whitespace(char32_t c) { - return c == '\t' || c == ' '; -} - -static bool _is_char(char32_t c) { - return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || c == '_'; -} - /////////////////////////////////////////////////////////////////////////////// /// TEXT /// /////////////////////////////////////////////////////////////////////////////// @@ -127,6 +116,10 @@ void TextEdit::Text::set_width(float p_width) { width = p_width; } +float TextEdit::Text::get_width() const { + return width; +} + int TextEdit::Text::get_line_wrap_amount(int p_line) const { ERR_FAIL_INDEX_V(p_line, text.size(), 0); @@ -154,57 +147,72 @@ _FORCE_INLINE_ const String &TextEdit::Text::operator[](int p_line) const { void TextEdit::Text::_calculate_line_height() { int height = 0; - for (int i = 0; i < text.size(); i++) { + for (const Line &l : text) { // Found another line with the same height...nothing to update. - if (text[i].height == line_height) { + if (l.height == line_height) { height = line_height; break; } - height = MAX(height, text[i].height); + height = MAX(height, l.height); } line_height = height; } void TextEdit::Text::_calculate_max_line_width() { int width = 0; - for (int i = 0; i < text.size(); i++) { - if (is_hidden(i)) { + for (const Line &l : text) { + if (l.hidden) { continue; } // Found another line with the same width...nothing to update. - if (text[i].width == max_width) { + if (l.width == max_width) { width = max_width; break; } - width = MAX(width, text[i].width); + width = MAX(width, l.width); } max_width = width; } -void TextEdit::Text::invalidate_cache(int p_line, int p_column, const String &p_ime_text, const Array &p_bidi_override) { +void TextEdit::Text::invalidate_cache(int p_line, int p_column, bool p_text_changed, const String &p_ime_text, const Array &p_bidi_override) { ERR_FAIL_INDEX(p_line, text.size()); if (font.is_null() || font_size <= 0) { return; // Not in tree? } - text.write[p_line].data_buf->clear(); + if (p_text_changed) { + text.write[p_line].data_buf->clear(); + } + text.write[p_line].data_buf->set_width(width); text.write[p_line].data_buf->set_direction((TextServer::Direction)direction); text.write[p_line].data_buf->set_preserve_control(draw_control_chars); if (p_ime_text.length() > 0) { - text.write[p_line].data_buf->add_string(p_ime_text, font, font_size, opentype_features, language); + if (p_text_changed) { + text.write[p_line].data_buf->add_string(p_ime_text, font, font_size, opentype_features, language); + } if (!p_bidi_override.is_empty()) { TS->shaped_text_set_bidi_override(text.write[p_line].data_buf->get_rid(), p_bidi_override); } } else { - text.write[p_line].data_buf->add_string(text[p_line].data, font, font_size, opentype_features, language); + if (p_text_changed) { + text.write[p_line].data_buf->add_string(text[p_line].data, font, font_size, opentype_features, language); + } if (!text[p_line].bidi_override.is_empty()) { TS->shaped_text_set_bidi_override(text.write[p_line].data_buf->get_rid(), text[p_line].bidi_override); } } + if (!p_text_changed) { + RID r = text.write[p_line].data_buf->get_rid(); + int spans = TS->shaped_get_span_count(r); + for (int i = 0; i < spans; i++) { + TS->shaped_set_span_update_font(r, i, font->get_rids(), font_size, opentype_features); + } + } + // Apply tab align. if (tab_size > 0) { Vector<float> tabs; @@ -215,7 +223,7 @@ void TextEdit::Text::invalidate_cache(int p_line, int p_column, const String &p_ // Update height. const int old_height = text.write[p_line].height; const int wrap_amount = get_line_wrap_amount(p_line); - int height = font->get_height(font_size); + int height = font_height; for (int i = 0; i <= wrap_amount; i++) { height = MAX(height, text[p_line].data_buf->get_line_size(i).y); } @@ -261,20 +269,53 @@ void TextEdit::Text::invalidate_all_lines() { } } +void TextEdit::Text::invalidate_font() { + if (!is_dirty) { + return; + } + + max_width = -1; + line_height = -1; + + if (!font.is_null() && font_size > 0) { + font_height = font->get_height(font_size); + } + + for (int i = 0; i < text.size(); i++) { + invalidate_cache(i, -1, false); + } + is_dirty = false; +} + void TextEdit::Text::invalidate_all() { if (!is_dirty) { return; } + max_width = -1; + line_height = -1; + + if (!font.is_null() && font_size > 0) { + font_height = font->get_height(font_size); + } + for (int i = 0; i < text.size(); i++) { - invalidate_cache(i); + invalidate_cache(i, -1, true); } is_dirty = false; } void TextEdit::Text::clear() { text.clear(); - insert(0, "", Array()); + + max_width = -1; + line_height = -1; + + Line line; + line.gutters.resize(gutter_count); + line.data = ""; + text.insert(0, line); + invalidate_cache(0, -1, true); } int TextEdit::Text::get_max_width() const { @@ -286,33 +327,67 @@ void TextEdit::Text::set(int p_line, const String &p_text, const Array &p_bidi_o text.write[p_line].data = p_text; text.write[p_line].bidi_override = p_bidi_override; - invalidate_cache(p_line); + invalidate_cache(p_line, -1, true); } -void TextEdit::Text::insert(int p_at, const String &p_text, const Array &p_bidi_override) { - Line line; - line.gutters.resize(gutter_count); - line.hidden = false; - line.data = p_text; - line.bidi_override = p_bidi_override; - text.insert(p_at, line); +void TextEdit::Text::insert(int p_at, const Vector<String> &p_text, const Vector<Array> &p_bidi_override) { + int new_line_count = p_text.size() - 1; + if (new_line_count > 0) { + text.resize(text.size() + new_line_count); + for (int i = (text.size() - 1); i > p_at; i--) { + if ((i - new_line_count) <= 0) { + break; + } + text.write[i] = text[i - new_line_count]; + } + } - invalidate_cache(p_at); + for (int i = 0; i < p_text.size(); i++) { + if (i == 0) { + set(p_at + i, p_text[i], p_bidi_override[i]); + continue; + } + Line line; + line.gutters.resize(gutter_count); + line.data = p_text[i]; + line.bidi_override = p_bidi_override[i]; + text.write[p_at + i] = line; + invalidate_cache(p_at + i, -1, true); + } } -void TextEdit::Text::remove(int p_at) { - int height = text[p_at].height; - int width = text[p_at].width; +void TextEdit::Text::remove_range(int p_from_line, int p_to_line) { + if (p_from_line == p_to_line) { + return; + } + + bool dirty_height = false; + bool dirty_width = false; + for (int i = p_from_line; i < p_to_line; i++) { + if (!dirty_height && text[i].height == line_height) { + dirty_height = true; + } - text.remove(p_at); + if (!dirty_width && text[i].width == max_width) { + dirty_width = true; + } - // If this is the tallest line, we need to get the next tallest. - if (height == line_height) { + if (dirty_height && dirty_width) { + break; + } + } + + int diff = (p_to_line - p_from_line); + for (int i = p_to_line; i < text.size() - 1; i++) { + text.write[(i - diff) + 1] = text[i + 1]; + } + text.resize(text.size() - diff); + + if (dirty_height) { _calculate_line_height(); } - // If this is the longest line, we need to get the next longest. - if (width == max_width) { + if (dirty_width) { _calculate_max_line_width(); } } @@ -330,7 +405,7 @@ void TextEdit::Text::add_gutter(int p_at) { void TextEdit::Text::remove_gutter(int p_gutter) { for (int i = 0; i < text.size(); i++) { - text.write[i].gutters.remove(p_gutter); + text.write[i].gutters.remove_at(p_gutter); } gutter_count--; } @@ -357,32 +432,38 @@ void TextEdit::_notification(int p_what) { } _update_wrap_at_column(true); } break; + case NOTIFICATION_RESIZED: { _update_scrollbars(); _update_wrap_at_column(); } break; + case NOTIFICATION_VISIBILITY_CHANGED: { if (is_visible()) { call_deferred(SNAME("_update_scrollbars")); call_deferred(SNAME("_update_wrap_at_column")); } } break; + case NOTIFICATION_LAYOUT_DIRECTION_CHANGED: case NOTIFICATION_TRANSLATION_CHANGED: case NOTIFICATION_THEME_CHANGED: { _update_caches(); _update_wrap_at_column(true); } break; + case NOTIFICATION_WM_WINDOW_FOCUS_IN: { window_has_focus = true; draw_caret = true; update(); } break; + case NOTIFICATION_WM_WINDOW_FOCUS_OUT: { window_has_focus = false; draw_caret = false; update(); } break; + case NOTIFICATION_INTERNAL_PHYSICS_PROCESS: { if (scrolling && get_v_scroll() != target_v_scroll) { double target_y = target_v_scroll - get_v_scroll(); @@ -404,6 +485,7 @@ void TextEdit::_notification(int p_what) { set_physics_process_internal(false); } } break; + case NOTIFICATION_DRAW: { if (first_draw) { // Size may not be the final one, so attempts to ensure caret was visible may have failed. @@ -596,19 +678,21 @@ void TextEdit::_notification(int p_what) { } } + bool draw_placeholder = text.size() == 1 && text[0].length() == 0; + // Get the highlighted words. String highlighted_text = get_selected_text(); // Check if highlighted words contain only whitespaces (tabs or spaces). - bool only_whitespaces_highlighted = highlighted_text.strip_edges() == String(); + bool only_whitespaces_highlighted = highlighted_text.strip_edges().is_empty(); const int caret_wrap_index = get_caret_wrap_index(); int first_visible_line = get_first_visible_line() - 1; int draw_amount = visible_rows + (smooth_scroll_enabled ? 1 : 0); - draw_amount += get_line_wrap_count(first_visible_line + 1); + draw_amount += draw_placeholder ? placeholder_wraped_rows.size() - 1 : get_line_wrap_count(first_visible_line + 1); - // minimap + // Draw minimap. if (draw_minimap) { int minimap_visible_lines = get_minimap_visible_lines(); int minimap_line_height = (minimap_char_size.y + minimap_line_spacing); @@ -731,8 +815,8 @@ void TextEdit::_notification(int p_what) { int xpos = indent_px + ((xmargin_end + minimap_char_size.x) + (minimap_char_size.x * j)) + tabs; bool out_of_bounds = (xpos >= xmargin_end + minimap_width); - bool is_whitespace = _is_whitespace(str[j]); - if (!is_whitespace) { + bool whitespace = is_whitespace(str[j]); + if (!whitespace) { characters++; if (j < str.length() - 1 && color == previous_color && !out_of_bounds) { @@ -754,7 +838,7 @@ void TextEdit::_notification(int p_what) { if (characters > 0) { previous_color.a *= 0.6; // take one for zero indexing, and if we hit whitespace / the end of a word. - int chars = MAX(0, (j - (characters - 1)) - (is_whitespace ? 1 : 0)) + 1; + int chars = MAX(0, (j - (characters - 1)) - (whitespace ? 1 : 0)) + 1; int char_x_ofs = indent_px + ((xmargin_end + minimap_char_size.x) + (minimap_char_size.x * chars)) + tabs; if (rtl) { RenderingServer::get_singleton()->canvas_item_add_rect(ci, Rect2(Point2(size.width - char_x_ofs - minimap_char_size.x * characters, minimap_line_height * i), Point2(minimap_char_size.x * characters, minimap_char_size.y)), previous_color); @@ -788,9 +872,10 @@ void TextEdit::_notification(int p_what) { bottom_limit_y -= style_normal->get_margin(SIDE_BOTTOM); } - // draw main text + // Draw main text. caret.visible = false; - int row_height = get_line_height(); + line_drawing_cache.clear(); + int row_height = draw_placeholder ? placeholder_line_height + line_spacing : get_line_height(); int line = first_visible_line; for (int i = 0; i < draw_amount; i++) { line++; @@ -810,15 +895,20 @@ void TextEdit::_notification(int p_what) { continue; } + LineDrawingCache cache_entry; + Dictionary color_map = _get_line_syntax_highlighting(line); // Ensure we at least use the font color. Color current_color = !editable ? font_readonly_color : font_color; + if (draw_placeholder) { + current_color = font_placeholder_color; + } - const Ref<TextParagraph> ldata = text.get_line_data(line); + const Ref<TextParagraph> ldata = draw_placeholder ? placeholder_data_buf : text.get_line_data(line); - Vector<String> wrap_rows = get_line_wrapped_text(line); - int line_wrap_amount = get_line_wrap_count(line); + Vector<String> wrap_rows = draw_placeholder ? placeholder_wraped_rows : get_line_wrapped_text(line); + int line_wrap_amount = draw_placeholder ? placeholder_wraped_rows.size() - 1 : get_line_wrap_count(line); for (int line_wrap_index = 0; line_wrap_index <= line_wrap_amount; line_wrap_index++) { if (line_wrap_index != 0) { @@ -878,7 +968,7 @@ void TextEdit::_notification(int p_what) { // Give visual indication of empty selected line. if (selection.active && line >= selection.from_line && line <= selection.to_line && char_margin >= xmargin_beg) { - int char_w = font->get_char_size(' ', 0, font_size).width; + float char_w = font->get_char_size(' ', 0, font_size).width; if (rtl) { RenderingServer::get_singleton()->canvas_item_add_rect(ci, Rect2(size.width - xmargin_beg - ofs_x - char_w, ofs_y, char_w, row_height), selection_color); } else { @@ -899,6 +989,8 @@ void TextEdit::_notification(int p_what) { if (line_wrap_index == 0) { // Only do these if we are on the first wrapped part of a line. + cache_entry.y_offset = ofs_y; + int gutter_offset = style_normal->get_margin(SIDE_LEFT); for (int g = 0; g < gutters.size(); g++) { const GutterInfo gutter = gutters[g]; @@ -910,7 +1002,7 @@ void TextEdit::_notification(int p_what) { switch (gutter.type) { case GUTTER_TYPE_STRING: { const String &text = get_line_gutter_text(line, g); - if (text == "") { + if (text.is_empty()) { break; } @@ -953,15 +1045,17 @@ void TextEdit::_notification(int p_what) { icon->draw_rect(ci, gutter_rect, false, get_line_gutter_item_color(line, g)); } break; case GUTTER_TYPE_CUSTOM: { - if (gutter.custom_draw_obj.is_valid()) { - Object *cdo = ObjectDB::get_instance(gutter.custom_draw_obj); - if (cdo) { - Rect2i gutter_rect = Rect2i(Point2i(gutter_offset, ofs_y), Size2i(gutter.width, row_height)); - if (rtl) { - gutter_rect.position.x = size.width - gutter_rect.position.x - gutter_rect.size.x; - } - cdo->call(gutter.custom_draw_callback, line, g, Rect2(gutter_rect)); + if (gutter.custom_draw_callback.is_valid()) { + Rect2i gutter_rect = Rect2i(Point2i(gutter_offset, ofs_y), Size2i(gutter.width, row_height)); + if (rtl) { + gutter_rect.position.x = size.width - gutter_rect.position.x - gutter_rect.size.x; } + + Variant args[3] = { line, g, Rect2(gutter_rect) }; + const Variant *argp[] = { &args[0], &args[1], &args[2] }; + Callable::CallError ce; + Variant ret; + gutter.custom_draw_callback.call(argp, 3, ret, ce); } } break; } @@ -1045,12 +1139,12 @@ void TextEdit::_notification(int p_what) { } if (!clipped && lookup_symbol_word.length() != 0) { // Highlight word - if (_is_char(lookup_symbol_word[0]) || lookup_symbol_word[0] == '.') { + if (is_ascii_char(lookup_symbol_word[0]) || lookup_symbol_word[0] == '_' || lookup_symbol_word[0] == '.') { int highlighted_word_col = _get_column_pos_of_word(lookup_symbol_word, str, SEARCH_MATCH_CASE | SEARCH_WHOLE_WORDS, 0); while (highlighted_word_col != -1) { Vector<Vector2> sel = TS->shaped_text_get_selection(rid, highlighted_word_col + start, highlighted_word_col + lookup_symbol_word.length() + start); for (int j = 0; j < sel.size(); j++) { - Rect2 rect = Rect2(sel[j].x + char_margin + ofs_x, ofs_y, sel[j].y - sel[j].x, row_height); + Rect2 rect = Rect2(sel[j].x + char_margin + ofs_x, ofs_y + (line_spacing / 2), sel[j].y - sel[j].x, row_height); if (rect.position.x + rect.size.x <= xmargin_beg || rect.position.x > xmargin_end) { continue; } @@ -1060,9 +1154,9 @@ void TextEdit::_notification(int p_what) { } else if (rect.position.x + rect.size.x > xmargin_end) { rect.size.x = xmargin_end - rect.position.x; } - rect.position.y = TS->shaped_text_get_ascent(rid) + font->get_underline_position(font_size); - rect.size.y = font->get_underline_thickness(font_size); - draw_rect(rect, font_selected_color); + rect.position.y += ceil(TS->shaped_text_get_ascent(rid)) + ceil(font->get_underline_position(font_size)); + rect.size.y = MAX(1, font->get_underline_thickness(font_size)); + draw_rect(rect, color); } highlighted_word_col = _get_column_pos_of_word(lookup_symbol_word, str, SEARCH_MATCH_CASE | SEARCH_WHOLE_WORDS, highlighted_word_col + 1); @@ -1076,7 +1170,11 @@ void TextEdit::_notification(int p_what) { int gl_size = TS->shaped_text_get_glyph_count(rid); ofs_y += ldata->get_line_ascent(line_wrap_index); - int char_ofs = 0; + + int first_visible_char = TS->shaped_text_get_range(rid).y; + int last_visible_char = TS->shaped_text_get_range(rid).x; + + float char_ofs = 0; if (outline_size > 0 && outline_color.a > 0) { for (int j = 0; j < gl_size; j++) { for (int k = 0; k < glyphs[j].repeat; k++) { @@ -1111,7 +1209,7 @@ void TextEdit::_notification(int p_what) { } } - int char_pos = char_ofs + char_margin + ofs_x; + float char_pos = char_ofs + char_margin + ofs_x; if (char_pos >= xmargin_beg) { if (highlight_matching_braces_enabled) { if ((brace_open_match_line == line && brace_open_match_column == glyphs[j].start) || @@ -1143,21 +1241,36 @@ void TextEdit::_notification(int p_what) { } } + bool had_glyphs_drawn = false; for (int k = 0; k < glyphs[j].repeat; k++) { if (!clipped && (char_ofs + char_margin) >= xmargin_beg && (char_ofs + glyphs[j].advance + char_margin) <= xmargin_end) { if (glyphs[j].font_rid != RID()) { TS->font_draw_glyph(glyphs[j].font_rid, ci, glyphs[j].font_size, Vector2(char_margin + char_ofs + ofs_x + glyphs[j].x_off, ofs_y + glyphs[j].y_off), glyphs[j].index, current_color); + had_glyphs_drawn = true; } else if ((glyphs[j].flags & TextServer::GRAPHEME_IS_VIRTUAL) != TextServer::GRAPHEME_IS_VIRTUAL) { TS->draw_hex_code_box(ci, glyphs[j].font_size, Vector2(char_margin + char_ofs + ofs_x + glyphs[j].x_off, ofs_y + glyphs[j].y_off), glyphs[j].index, current_color); + had_glyphs_drawn = true; } } char_ofs += glyphs[j].advance; } + + if (had_glyphs_drawn) { + if (first_visible_char > glyphs[j].start) { + first_visible_char = glyphs[j].start; + } else if (last_visible_char < glyphs[j].end) { + last_visible_char = glyphs[j].end; + } + } + if ((char_ofs + char_margin) >= xmargin_end) { break; } } + cache_entry.first_visible_chars.push_back(first_visible_char); + cache_entry.last_visible_chars.push_back(last_visible_char); + // is_line_folded if (line_wrap_index == line_wrap_amount && line < text.size() - 1 && _is_line_hidden(line + 1)) { int xofs = char_ofs + char_margin + ofs_x + (folded_eol_icon->get_width() / 2); @@ -1170,7 +1283,8 @@ void TextEdit::_notification(int p_what) { } // Carets. - int caret_width = Math::round(1 * get_theme_default_base_scale()); + // 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()); if (!clipped && caret.line == line && line_wrap_index == caret_wrap_index) { caret.draw_pos.y = ofs_y + ldata->get_line_descent(line_wrap_index); @@ -1200,7 +1314,7 @@ void TextEdit::_notification(int p_what) { if (caret.draw_pos.x >= xmargin_beg && caret.draw_pos.x < xmargin_end) { caret.visible = true; - if (draw_caret) { + if (draw_caret || drag_caret_force_displayed) { if (caret_type == CaretType::CARET_TYPE_BLOCK || overtype_mode) { //Block or underline caret, draw trailing carets at full height. int h = font->get_height(font_size); @@ -1308,6 +1422,10 @@ void TextEdit::_notification(int p_what) { } } } + + if (draw_placeholder) { + line_drawing_cache[line] = cache_entry; + } } if (has_focus()) { @@ -1317,6 +1435,7 @@ void TextEdit::_notification(int p_what) { } } } break; + case NOTIFICATION_FOCUS_ENTER: { if (caret_blink_enabled) { caret_blink_timer->start(); @@ -1348,6 +1467,7 @@ void TextEdit::_notification(int p_what) { DisplayServer::get_singleton()->virtual_keyboard_show(get_text(), get_global_rect(), true, -1, caret_start, caret_end); } } break; + case NOTIFICATION_FOCUS_EXIT: { if (caret_blink_enabled) { caret_blink_timer->stop(); @@ -1357,18 +1477,21 @@ void TextEdit::_notification(int p_what) { DisplayServer::get_singleton()->window_set_ime_position(Point2(), get_viewport()->get_window_id()); DisplayServer::get_singleton()->window_set_ime_active(false, get_viewport()->get_window_id()); } - ime_text = ""; - ime_selection = Point2(); - text.invalidate_cache(caret.line, caret.column, ime_text); + if (!ime_text.is_empty()) { + ime_text = ""; + ime_selection = Point2(); + text.invalidate_cache(caret.line, caret.column, true, ime_text); + } if (DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_VIRTUAL_KEYBOARD) && virtual_keyboard_enabled) { DisplayServer::get_singleton()->virtual_keyboard_hide(); } - if (deselect_on_focus_loss_enabled) { + if (deselect_on_focus_loss_enabled && !selection.drag_attempt) { deselect(); } } break; + case MainLoop::NOTIFICATION_OS_IME_UPDATE: { if (has_focus()) { ime_text = DisplayServer::get_singleton()->ime_get_text(); @@ -1381,10 +1504,36 @@ void TextEdit::_notification(int p_what) { t = ime_text; } - text.invalidate_cache(caret.line, caret.column, t, structured_text_parser(st_parser, st_args, t)); + text.invalidate_cache(caret.line, caret.column, true, t, structured_text_parser(st_parser, st_args, t)); update(); } } break; + + case NOTIFICATION_DRAG_BEGIN: { + selection.selecting_mode = SelectionMode::SELECTION_MODE_NONE; + drag_action = true; + dragging_minimap = false; + dragging_selection = false; + can_drag_minimap = false; + click_select_held->stop(); + } break; + + case NOTIFICATION_DRAG_END: { + if (is_drag_successful()) { + if (selection.drag_attempt) { + selection.drag_attempt = false; + if (is_editable() && !Input::get_singleton()->is_key_pressed(Key::CTRL)) { + delete_selection(); + } else if (deselect_on_focus_loss_enabled) { + deselect(); + } + } + } else { + selection.drag_attempt = false; + } + drag_action = false; + drag_caret_force_displayed = false; + } break; } } @@ -1407,7 +1556,7 @@ void TextEdit::gui_input(const Ref<InputEvent> &p_gui_input) { } if (mb->is_pressed()) { - if (mb->get_button_index() == MOUSE_BUTTON_WHEEL_UP && !mb->is_command_pressed()) { + if (mb->get_button_index() == MouseButton::WHEEL_UP && !mb->is_command_pressed()) { if (mb->is_shift_pressed()) { h_scroll->set_value(h_scroll->get_value() - (100 * mb->get_factor())); } else if (mb->is_alt_pressed()) { @@ -1418,7 +1567,7 @@ void TextEdit::gui_input(const Ref<InputEvent> &p_gui_input) { _scroll_up(3 * mb->get_factor()); } } - if (mb->get_button_index() == MOUSE_BUTTON_WHEEL_DOWN && !mb->is_command_pressed()) { + if (mb->get_button_index() == MouseButton::WHEEL_DOWN && !mb->is_command_pressed()) { if (mb->is_shift_pressed()) { h_scroll->set_value(h_scroll->get_value() + (100 * mb->get_factor())); } else if (mb->is_alt_pressed()) { @@ -1429,13 +1578,13 @@ void TextEdit::gui_input(const Ref<InputEvent> &p_gui_input) { _scroll_down(3 * mb->get_factor()); } } - if (mb->get_button_index() == MOUSE_BUTTON_WHEEL_LEFT) { + if (mb->get_button_index() == MouseButton::WHEEL_LEFT) { h_scroll->set_value(h_scroll->get_value() - (100 * mb->get_factor())); } - if (mb->get_button_index() == MOUSE_BUTTON_WHEEL_RIGHT) { + if (mb->get_button_index() == MouseButton::WHEEL_RIGHT) { h_scroll->set_value(h_scroll->get_value() + (100 * mb->get_factor())); } - if (mb->get_button_index() == MOUSE_BUTTON_LEFT) { + if (mb->get_button_index() == MouseButton::LEFT) { _reset_caret_blink_timer(); Point2i pos = get_line_column_at_pos(mpos); @@ -1469,6 +1618,7 @@ void TextEdit::gui_input(const Ref<InputEvent> &p_gui_input) { set_caret_line(row, false, false); set_caret_column(col); + selection.drag_attempt = false; if (mb->is_shift_pressed() && (caret.column != prev_col || caret.line != prev_line)) { if (!selection.active) { @@ -1512,6 +1662,9 @@ void TextEdit::gui_input(const Ref<InputEvent> &p_gui_input) { update(); } + } else if (is_mouse_over_selection()) { + selection.selecting_mode = SelectionMode::SELECTION_MODE_NONE; + selection.drag_attempt = true; } else { selection.active = false; selection.selecting_mode = SelectionMode::SELECTION_MODE_POINTER; @@ -1525,6 +1678,7 @@ void TextEdit::gui_input(const Ref<InputEvent> &p_gui_input) { if (!mb->is_double_click() && (OS::get_singleton()->get_ticks_msec() - last_dblclk) < triple_click_timeout && mb->get_position().distance_to(last_dblclk_pos) < triple_click_tolerance) { // Triple-click select line. selection.selecting_mode = SelectionMode::SELECTION_MODE_LINE; + selection.drag_attempt = false; _update_selection_mode_line(); last_dblclk = 0; } else if (mb->is_double_click() && text[caret.line].length()) { @@ -1538,11 +1692,11 @@ void TextEdit::gui_input(const Ref<InputEvent> &p_gui_input) { update(); } - if (is_middle_mouse_paste_enabled() && mb->get_button_index() == MOUSE_BUTTON_MIDDLE && DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_CLIPBOARD_PRIMARY)) { + if (is_middle_mouse_paste_enabled() && mb->get_button_index() == MouseButton::MIDDLE && DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_CLIPBOARD_PRIMARY)) { paste_primary_clipboard(); } - if (mb->get_button_index() == MOUSE_BUTTON_RIGHT && context_menu_enabled) { + if (mb->get_button_index() == MouseButton::RIGHT && context_menu_enabled) { _reset_caret_blink_timer(); Point2i pos = get_line_column_at_pos(mpos); @@ -1568,17 +1722,23 @@ void TextEdit::gui_input(const Ref<InputEvent> &p_gui_input) { } _generate_context_menu(); - menu->set_position(get_screen_transform().xform(mpos)); - menu->set_size(Vector2(1, 1)); + menu->set_position(get_screen_position() + mpos); + menu->reset_size(); menu->popup(); grab_focus(); } } else { - if (mb->get_button_index() == MOUSE_BUTTON_LEFT) { + if (mb->get_button_index() == MouseButton::LEFT) { + if (selection.drag_attempt && is_mouse_over_selection()) { + selection.active = false; + } dragging_minimap = false; dragging_selection = false; can_drag_minimap = false; click_select_held->stop(); + if (!drag_action) { + selection.drag_attempt = false; + } if (DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_CLIPBOARD_PRIMARY)) { DisplayServer::get_singleton()->clipboard_set_primary(get_selected_text()); } @@ -1613,7 +1773,7 @@ void TextEdit::gui_input(const Ref<InputEvent> &p_gui_input) { mpos.x = get_size().x - mpos.x; } - if (mm->get_button_mask() & MOUSE_BUTTON_MASK_LEFT && get_viewport()->gui_get_drag_data() == Variant()) { // Ignore if dragging. + if ((mm->get_button_mask() & MouseButton::MASK_LEFT) != MouseButton::NONE && get_viewport()->gui_get_drag_data() == Variant()) { // Ignore if dragging. _reset_caret_blink_timer(); if (draw_minimap && !dragging_selection) { @@ -1663,6 +1823,14 @@ void TextEdit::gui_input(const Ref<InputEvent> &p_gui_input) { hovered_gutter = current_hovered_gutter; update(); } + + if (drag_action && can_drop_data(mpos, get_viewport()->gui_get_drag_data())) { + drag_caret_force_displayed = true; + Point2i pos = get_line_column_at_pos(get_local_mouse_pos()); + set_caret_line(pos.y, false); + set_caret_column(pos.x); + dragging_selection = true; + } } if (draw_minimap && !dragging_selection) { @@ -1681,7 +1849,7 @@ void TextEdit::gui_input(const Ref<InputEvent> &p_gui_input) { } // If a modifier has been pressed, and nothing else, return. - if (k->get_keycode() == KEY_CTRL || k->get_keycode() == KEY_ALT || k->get_keycode() == KEY_SHIFT || k->get_keycode() == KEY_META) { + if (k->get_keycode() == Key::CTRL || k->get_keycode() == Key::ALT || k->get_keycode() == Key::SHIFT || k->get_keycode() == Key::META) { return; } @@ -1801,8 +1969,8 @@ void TextEdit::gui_input(const Ref<InputEvent> &p_gui_input) { if (context_menu_enabled) { _generate_context_menu(); adjust_viewport_to_caret(); - menu->set_position(get_screen_transform().xform(get_caret_draw_pos())); - menu->set_size(Vector2(1, 1)); + menu->set_position(get_screen_position() + get_caret_draw_pos()); + menu->reset_size(); menu->popup(); menu->grab_focus(); } @@ -1898,7 +2066,7 @@ void TextEdit::gui_input(const Ref<InputEvent> &p_gui_input) { } // Handle Unicode (if no modifiers active). Tab has a value of 0x09. - if (allow_unicode_handling && editable && (k->get_unicode() >= 32 || k->get_keycode() == KEY_TAB)) { + if (allow_unicode_handling && editable && (k->get_unicode() >= 32 || k->get_keycode() == Key::TAB)) { handle_unicode_input(k->get_unicode()); accept_event(); return; @@ -1926,6 +2094,7 @@ void TextEdit::_new_line(bool p_split_current_line, bool p_above) { bool first_line = false; if (!p_split_current_line) { + deselect(); if (p_above) { if (caret.line > 0) { set_caret_line(caret.line - 1, false); @@ -2206,7 +2375,7 @@ void TextEdit::_do_backspace(bool p_word, bool p_all_to_left) { if (p_all_to_left) { int caret_current_column = caret.column; - caret.column = 0; + set_caret_column(0); _remove_text(caret.line, 0, caret.line, caret_current_column); return; } @@ -2311,6 +2480,47 @@ void TextEdit::_move_caret_document_end(bool p_select) { } } +void TextEdit::_update_placeholder() { + if (font.is_null() || font_size <= 0) { + return; // Not in tree? + } + + // Placeholder is generally smaller then text documents, and updates less so this should be fast enough for now. + placeholder_data_buf->clear(); + placeholder_data_buf->set_width(text.get_width()); + placeholder_data_buf->set_direction((TextServer::Direction)text_direction); + placeholder_data_buf->set_preserve_control(draw_control_chars); + placeholder_data_buf->add_string(placeholder_text, font, font_size, opentype_features, language); + + placeholder_bidi_override = structured_text_parser(st_parser, st_args, placeholder_text); + if (placeholder_bidi_override.is_empty()) { + TS->shaped_text_set_bidi_override(placeholder_data_buf->get_rid(), placeholder_bidi_override); + } + + if (get_tab_size() > 0) { + Vector<float> tabs; + tabs.push_back(font->get_char_size(' ', 0, font_size).width * get_tab_size()); + placeholder_data_buf->tab_align(tabs); + } + + // Update height. + const int wrap_amount = placeholder_data_buf->get_line_count() - 1; + placeholder_line_height = font->get_height(font_size); + for (int i = 0; i <= wrap_amount; i++) { + placeholder_line_height = MAX(placeholder_line_height, placeholder_data_buf->get_line_size(i).y); + } + + // Update width. + placeholder_max_width = placeholder_data_buf->get_size().x; + + // Update wrapped rows. + placeholder_wraped_rows.clear(); + for (int i = 0; i <= wrap_amount; i++) { + Vector2i line_range = placeholder_data_buf->get_line_range(i); + placeholder_wraped_rows.push_back(placeholder_text.substr(line_range.x, line_range.y - line_range.x)); + } +} + void TextEdit::_update_caches() { /* Internal API for CodeEdit. */ brace_mismatch_color = get_theme_color(SNAME("brace_mismatch_color"), SNAME("CodeEdit")); @@ -2341,6 +2551,7 @@ void TextEdit::_update_caches() { font_size = get_theme_font_size(SNAME("font_size")); font_color = get_theme_color(SNAME("font_color")); font_readonly_color = get_theme_color(SNAME("font_readonly_color")); + font_placeholder_color = get_theme_color(SNAME("font_placeholder_color")); outline_size = get_theme_constant(SNAME("outline_size")); outline_color = get_theme_color(SNAME("font_outline_color")); @@ -2358,12 +2569,13 @@ void TextEdit::_update_caches() { } else { dir = (TextServer::Direction)text_direction; } - text.set_direction_and_language(dir, (language != "") ? language : TranslationServer::get_singleton()->get_tool_locale()); + text.set_direction_and_language(dir, (!language.is_empty()) ? language : TranslationServer::get_singleton()->get_tool_locale()); text.set_font_features(opentype_features); text.set_draw_control_chars(draw_control_chars); text.set_font(font); text.set_font_size(font_size); - text.invalidate_all(); + text.invalidate_font(); + _update_placeholder(); /* Syntax highlighting. */ if (syntax_highlighter.is_valid()) { @@ -2380,6 +2592,75 @@ bool TextEdit::is_text_field() const { return true; } +Variant TextEdit::get_drag_data(const Point2 &p_point) { + if (selection.active && selection.drag_attempt) { + String t = get_selected_text(); + Label *l = memnew(Label); + l->set_text(t); + set_drag_preview(l); + return t; + } + + return Variant(); +} + +bool TextEdit::can_drop_data(const Point2 &p_point, const Variant &p_data) const { + bool drop_override = Control::can_drop_data(p_point, p_data); // In case user wants to drop custom data. + if (drop_override) { + return drop_override; + } + + return is_editable() && p_data.get_type() == Variant::STRING; +} + +void TextEdit::drop_data(const Point2 &p_point, const Variant &p_data) { + Control::drop_data(p_point, p_data); + + if (p_data.get_type() == Variant::STRING && is_editable()) { + Point2i pos = get_line_column_at_pos(get_local_mouse_pos()); + int caret_row_tmp = pos.y; + int caret_column_tmp = pos.x; + if (selection.drag_attempt) { + selection.drag_attempt = false; + if (!is_mouse_over_selection(!Input::get_singleton()->is_key_pressed(Key::CTRL))) { + begin_complex_operation(); + if (!Input::get_singleton()->is_key_pressed(Key::CTRL)) { + if (caret_row_tmp > selection.to_line) { + caret_row_tmp = caret_row_tmp - (selection.to_line - selection.from_line); + } else if (caret_row_tmp == selection.to_line && caret_column_tmp >= selection.to_column) { + caret_column_tmp = caret_column_tmp - (selection.to_column - selection.from_column); + } + delete_selection(); + } else { + deselect(); + } + + set_caret_line(caret_row_tmp, true, false); + set_caret_column(caret_column_tmp); + insert_text_at_caret(p_data); + end_complex_operation(); + } + } else if (is_mouse_over_selection()) { + caret_row_tmp = selection.from_line; + caret_column_tmp = selection.from_column; + set_caret_line(caret_row_tmp, true, false); + set_caret_column(caret_column_tmp); + insert_text_at_caret(p_data); + grab_focus(); + } else { + deselect(); + set_caret_line(caret_row_tmp, true, false); + set_caret_column(caret_column_tmp); + insert_text_at_caret(p_data); + grab_focus(); + } + + if (caret_row_tmp != caret.line || caret_column_tmp != caret.column) { + select(caret_row_tmp, caret_column_tmp, caret.line, caret.column); + } + } +} + Control::CursorShape TextEdit::get_cursor_shape(const Point2 &p_pos) const { Point2i pos = get_line_column_at_pos(p_pos); int row = pos.y; @@ -2410,8 +2691,7 @@ Control::CursorShape TextEdit::get_cursor_shape(const Point2 &p_pos) const { } String TextEdit::get_tooltip(const Point2 &p_pos) const { - Object *tooltip_obj = ObjectDB::get_instance(tooltip_obj_id); - if (!tooltip_obj) { + if (!tooltip_callback.is_valid()) { return Control::get_tooltip(p_pos); } Point2i pos = get_line_column_at_pos(p_pos); @@ -2424,19 +2704,20 @@ String TextEdit::get_tooltip(const Point2 &p_pos) const { } int beg, end; if (select_word(s, col, beg, end)) { - String tt = tooltip_obj->call(tooltip_func, s.substr(beg, end - beg), tooltip_ud); - - return tt; + Variant args[1] = { s.substr(beg, end - beg) }; + const Variant *argp[] = { &args[0] }; + Callable::CallError ce; + Variant ret; + tooltip_callback.call(argp, 1, ret, ce); + ERR_FAIL_COND_V_MSG(ce.error != Callable::CallError::CALL_OK, "", "Failed to call custom tooltip."); + return ret; } return Control::get_tooltip(p_pos); } -void TextEdit::set_tooltip_request_func(Object *p_obj, const StringName &p_function, const Variant &p_udata) { - ERR_FAIL_NULL(p_obj); - tooltip_obj_id = p_obj->get_instance_id(); - tooltip_func = p_function; - tooltip_ud = p_udata; +void TextEdit::set_tooltip_request_func(const Callable &p_tooltip_callback) { + tooltip_callback = p_tooltip_callback; } /* Text */ @@ -2472,8 +2753,9 @@ void TextEdit::set_text_direction(Control::TextDirection p_text_direction) { } else { dir = (TextServer::Direction)text_direction; } - text.set_direction_and_language(dir, (language != "") ? language : TranslationServer::get_singleton()->get_tool_locale()); - text.invalidate_all(); + text.set_direction_and_language(dir, (!language.is_empty()) ? language : TranslationServer::get_singleton()->get_tool_locale()); + text.invalidate_font(); + _update_placeholder(); if (menu_dir) { menu_dir->set_item_checked(menu_dir->get_item_index(MENU_DIR_INHERITED), text_direction == TEXT_DIRECTION_INHERITED); @@ -2494,7 +2776,8 @@ void TextEdit::set_opentype_feature(const String &p_name, int p_value) { if (!opentype_features.has(tag) || (int)opentype_features[tag] != p_value) { opentype_features[tag] = p_value; text.set_font_features(opentype_features); - text.invalidate_all(); + text.invalidate_font(); + _update_placeholder(); update(); } } @@ -2510,7 +2793,8 @@ int TextEdit::get_opentype_feature(const String &p_name) const { void TextEdit::clear_opentype_features() { opentype_features.clear(); text.set_font_features(opentype_features); - text.invalidate_all(); + text.invalidate_font(); + _update_placeholder(); update(); } @@ -2523,8 +2807,9 @@ void TextEdit::set_language(const String &p_language) { } else { dir = (TextServer::Direction)text_direction; } - text.set_direction_and_language(dir, (language != "") ? language : TranslationServer::get_singleton()->get_tool_locale()); + text.set_direction_and_language(dir, (!language.is_empty()) ? language : TranslationServer::get_singleton()->get_tool_locale()); text.invalidate_all(); + _update_placeholder(); update(); } } @@ -2566,6 +2851,7 @@ void TextEdit::set_tab_size(const int p_size) { } text.set_tab_size(p_size); text.invalidate_all_lines(); + _update_placeholder(); update(); } @@ -2624,15 +2910,31 @@ void TextEdit::clear() { } void TextEdit::_clear() { + if (editable && undo_enabled) { + _move_caret_document_start(false); + begin_complex_operation(); + + _remove_text(0, 0, MAX(0, get_line_count() - 1), MAX(get_line(MAX(get_line_count() - 1, 0)).size() - 1, 0)); + insert_text_at_caret(""); + text.clear(); + + end_complex_operation(); + return; + } + // Cannot merge with above, as we are not part of the tree on creation. + int old_text_size = text.size(); + clear_undo_history(); text.clear(); - caret.column = 0; - caret.line = 0; + set_caret_line(0, false); + set_caret_column(0); caret.x_ofs = 0; caret.line_ofs = 0; caret.wrap_ofs = 0; caret.last_fit_x = 0; selection.active = false; + + emit_signal(SNAME("lines_edited_from"), old_text_size, 0); } void TextEdit::set_text(const String &p_text) { @@ -2677,18 +2979,30 @@ int TextEdit::get_line_count() const { return text.size(); } +void TextEdit::set_placeholder(const String &p_text) { + placeholder_text = p_text; + _update_placeholder(); + update(); +} + +String TextEdit::get_placeholder() const { + return placeholder_text; +} + void TextEdit::set_line(int p_line, const String &p_new_text) { if (p_line < 0 || p_line >= text.size()) { return; } + begin_complex_operation(); _remove_text(p_line, 0, p_line, text[p_line].length()); _insert_text(p_line, 0, p_new_text); - if (caret.line == p_line) { - caret.column = MIN(caret.column, p_new_text.length()); + if (caret.line == p_line && caret.column > p_new_text.length()) { + set_caret_column(MIN(caret.column, p_new_text.length()), false); } if (has_selection() && p_line == selection.to_line && selection.to_column > text[p_line].length()) { selection.to_column = text[p_line].length(); } + end_complex_operation(); } String TextEdit::get_line(int p_line) const { @@ -2731,7 +3045,7 @@ int TextEdit::get_first_non_whitespace_column(int p_line) const { ERR_FAIL_INDEX_V(p_line, text.size(), 0); int col = 0; - while (col < text[p_line].length() && _is_whitespace(text[p_line][col])) { + while (col < text[p_line].length() && is_whitespace(text[p_line][col])) { col++; } return col; @@ -2743,8 +3057,10 @@ void TextEdit::swap_lines(int p_from_line, int p_to_line) { String tmp = get_line(p_from_line); String tmp2 = get_line(p_to_line); + begin_complex_operation(); set_line(p_to_line, tmp); set_line(p_from_line, tmp2); + end_complex_operation(); } void TextEdit::insert_line_at(int p_at, const String &p_text) { @@ -2753,7 +3069,7 @@ void TextEdit::insert_line_at(int p_at, const String &p_text) { _insert_text(p_at, 0, p_text + "\n"); if (caret.line >= p_at) { // offset caret when located after inserted line - ++caret.line; + set_caret_line(caret.line + 1, false); } if (has_selection()) { if (selection.from_line >= p_at) { @@ -2878,6 +3194,16 @@ Point2i TextEdit::get_next_visible_line_index_offset_from(int p_line_from, int p } } wrap_index = get_line_wrap_count(MIN(i, text.size() - 1)) - MAX(0, num_visible - p_visible_amount); + + // If we are a hidden line, then we are the last line as we cannot reach "p_visible_amount". + // This means we need to backtrack to get last visible line. + // Currently, line 0 cannot be hidden so this should always be valid. + int line = (p_line_from + num_total) - 1; + if (_is_line_hidden(line)) { + Point2i backtrack = get_next_visible_line_index_offset_from(line, 0, -1); + num_total = num_total - (backtrack.x - 1); + wrap_index = backtrack.y; + } } else { p_visible_amount = ABS(p_visible_amount); int i; @@ -3306,9 +3632,9 @@ Point2i TextEdit::search(const String &p_key, uint32_t p_search_flags, int p_fro if (pos != -1 && (p_search_flags & SEARCH_WHOLE_WORDS)) { // Validate for whole words. - if (pos > 0 && _is_text_char(text_line[pos - 1])) { + if (pos > 0 && !is_symbol(text_line[pos - 1])) { is_match = false; - } else if (pos + p_key.length() < text_line.length() && _is_text_char(text_line[pos + p_key.length()])) { + } else if (pos + p_key.length() < text_line.length() && !is_symbol(text_line[pos + p_key.length()])) { is_match = false; } } @@ -3386,7 +3712,7 @@ String TextEdit::get_word_at_pos(const Vector2 &p_pos) const { return String(); } -Point2i TextEdit::get_line_column_at_pos(const Point2i &p_pos) const { +Point2i TextEdit::get_line_column_at_pos(const Point2i &p_pos, bool p_allow_out_of_bounds) const { float rows = p_pos.y; rows -= style_normal->get_margin(SIDE_TOP); rows /= get_line_height(); @@ -3396,8 +3722,9 @@ Point2i TextEdit::get_line_column_at_pos(const Point2i &p_pos) const { int wrap_index = 0; if (get_line_wrapping_mode() != LineWrappingMode::LINE_WRAPPING_NONE || _is_hiding_enabled()) { - Point2i f_ofs = get_next_visible_line_index_offset_from(first_vis_line, caret.wrap_ofs, rows + (1 * SGN(rows))); + Point2i f_ofs = get_next_visible_line_index_offset_from(first_vis_line, caret.wrap_ofs, rows + (1 * SIGN(rows))); wrap_index = f_ofs.y; + if (rows < 0) { row = first_vis_line - (f_ofs.x - 1); } else { @@ -3409,37 +3736,86 @@ Point2i TextEdit::get_line_column_at_pos(const Point2i &p_pos) const { row = 0; } - int col = 0; - if (row >= text.size()) { row = text.size() - 1; - col = text[row].size(); - } else { - int colx = p_pos.x - (style_normal->get_margin(SIDE_LEFT) + gutters_width + gutter_padding); - colx += caret.x_ofs; - col = _get_char_pos_for_line(colx, row, wrap_index); - if (get_line_wrapping_mode() != LineWrappingMode::LINE_WRAPPING_NONE && wrap_index < get_line_wrap_count(row)) { - // Move back one if we are at the end of the row. - Vector<String> rows2 = get_line_wrapped_text(row); - int row_end_col = 0; - for (int i = 0; i < wrap_index + 1; i++) { - row_end_col += rows2[i].length(); - } - if (col >= row_end_col) { - col -= 1; - } + } + + int visible_lines = get_visible_line_count_in_range(first_vis_line, row); + if (rows > visible_lines) { + if (!p_allow_out_of_bounds) { + return Point2i(-1, -1); } + return Point2i(text[row].size(), row); + } - RID text_rid = text.get_line_data(row)->get_line_rid(wrap_index); - if (is_layout_rtl()) { - colx = TS->shaped_text_get_size(text_rid).x - colx; + int col = 0; + int colx = p_pos.x - (style_normal->get_margin(SIDE_LEFT) + gutters_width + gutter_padding); + colx += caret.x_ofs; + col = _get_char_pos_for_line(colx, row, wrap_index); + if (get_line_wrapping_mode() != LineWrappingMode::LINE_WRAPPING_NONE && wrap_index < get_line_wrap_count(row)) { + // Move back one if we are at the end of the row. + Vector<String> rows2 = get_line_wrapped_text(row); + int row_end_col = 0; + for (int i = 0; i < wrap_index + 1; i++) { + row_end_col += rows2[i].length(); + } + if (col >= row_end_col) { + col -= 1; } - col = TS->shaped_text_hit_test_position(text_rid, colx); } + RID text_rid = text.get_line_data(row)->get_line_rid(wrap_index); + if (is_layout_rtl()) { + colx = TS->shaped_text_get_size(text_rid).x - colx; + } + col = TS->shaped_text_hit_test_position(text_rid, colx); + return Point2i(col, row); } +Point2i TextEdit::get_pos_at_line_column(int p_line, int p_column) const { + Rect2i rect = get_rect_at_line_column(p_line, p_column); + return rect.position + Vector2i(0, get_line_height()); +} + +Rect2i TextEdit::get_rect_at_line_column(int p_line, int p_column) const { + ERR_FAIL_INDEX_V(p_line, text.size(), Rect2i(-1, -1, 0, 0)); + ERR_FAIL_COND_V(p_column < 0, Rect2i(-1, -1, 0, 0)); + ERR_FAIL_COND_V(p_column > text[p_line].length(), Rect2i(-1, -1, 0, 0)); + + if (line_drawing_cache.size() == 0 || !line_drawing_cache.has(p_line)) { + // Line is not in the cache, which means it's outside of the viewing area. + return Rect2i(-1, -1, 0, 0); + } + LineDrawingCache cache_entry = line_drawing_cache[p_line]; + + int wrap_index = get_line_wrap_index_at_column(p_line, p_column); + if (wrap_index >= cache_entry.first_visible_chars.size()) { + // Line seems to be wrapped beyond the viewable area. + return Rect2i(-1, -1, 0, 0); + } + + int first_visible_char = cache_entry.first_visible_chars[wrap_index]; + int last_visible_char = cache_entry.last_visible_chars[wrap_index]; + if (p_column < first_visible_char || p_column > last_visible_char) { + // Character is outside of the viewing area, no point calculating its position. + return Rect2i(-1, -1, 0, 0); + } + + Point2i pos, size; + pos.y = cache_entry.y_offset + get_line_height() * wrap_index; + pos.x = get_total_gutter_width() + style_normal->get_margin(SIDE_LEFT) - get_h_scroll(); + + RID text_rid = text.get_line_data(p_line)->get_line_rid(wrap_index); + Vector2 col_bounds = TS->shaped_text_get_grapheme_bounds(text_rid, p_column); + pos.x += col_bounds.x; + size.x = col_bounds.y - col_bounds.x; + + size.y = get_line_height(); + + return Rect2i(pos, size); +} + int TextEdit::get_minimap_line_at_pos(const Point2i &p_pos) const { float rows = p_pos.y; rows -= style_normal->get_margin(SIDE_TOP); @@ -3471,7 +3847,7 @@ int TextEdit::get_minimap_line_at_pos(const Point2i &p_pos) const { int row = minimap_line + Math::floor(rows); if (get_line_wrapping_mode() != LineWrappingMode::LINE_WRAPPING_NONE || _is_hiding_enabled()) { - int f_ofs = get_next_visible_line_index_offset_from(minimap_line, caret.wrap_ofs, rows + (1 * SGN(rows))).x - 1; + int f_ofs = get_next_visible_line_index_offset_from(minimap_line, caret.wrap_ofs, rows + (1 * SIGN(rows))).x - 1; if (rows < 0) { row = minimap_line - f_ofs; } else { @@ -3494,6 +3870,21 @@ bool TextEdit::is_dragging_cursor() const { return dragging_selection || dragging_minimap; } +bool TextEdit::is_mouse_over_selection(bool p_edges) const { + if (!has_selection()) { + return false; + } + Point2i pos = get_line_column_at_pos(get_local_mouse_pos()); + int row = pos.y; + int col = pos.x; + if (p_edges) { + if ((row == selection.from_line && col == selection.from_column) || (row == selection.to_line && col == selection.to_column)) { + return true; + } + } + return (row >= selection.from_line && row <= selection.to_line && (row > selection.from_line || col > selection.from_column) && (row < selection.to_line || col < selection.to_column)); +} + /* Caret */ void TextEdit::set_caret_type(CaretType p_type) { caret_type = p_type; @@ -3583,6 +3974,7 @@ void TextEdit::set_caret_line(int p_line, bool p_adjust_viewport, bool p_can_be_ } } } + bool caret_moved = caret.line != p_line; caret.line = p_line; int n_col = _get_char_pos_for_line(caret.last_fit_x, p_line, p_wrap_index); @@ -3596,15 +3988,16 @@ void TextEdit::set_caret_line(int p_line, bool p_adjust_viewport, bool p_can_be_ n_col -= 1; } } + caret_moved = (caret_moved || caret.column != n_col); caret.column = n_col; - if (p_adjust_viewport) { + if (is_inside_tree() && p_adjust_viewport) { adjust_viewport_to_caret(); } setting_caret_line = false; - if (!caret_pos_dirty) { + if (caret_moved && !caret_pos_dirty) { if (is_inside_tree()) { MessageQueue::get_singleton()->push_call(this, "_emit_caret_changed"); } @@ -3621,6 +4014,7 @@ void TextEdit::set_caret_column(int p_col, bool p_adjust_viewport) { p_col = 0; } + bool caret_moved = caret.column != p_col; caret.column = p_col; if (caret.column > get_line(caret.line).length()) { caret.column = get_line(caret.line).length(); @@ -3628,11 +4022,11 @@ void TextEdit::set_caret_column(int p_col, bool p_adjust_viewport) { caret.last_fit_x = _get_column_x_offset_for_line(caret.column, caret.line); - if (p_adjust_viewport) { + if (is_inside_tree() && p_adjust_viewport) { adjust_viewport_to_caret(); } - if (!caret_pos_dirty) { + if (caret_moved && !caret_pos_dirty) { if (is_inside_tree()) { MessageQueue::get_singleton()->push_call(this, "_emit_caret_changed"); } @@ -3697,8 +4091,10 @@ void TextEdit::set_selection_mode(SelectionMode p_mode, int p_line, int p_column if (p_line >= 0) { ERR_FAIL_INDEX(p_line, text.size()); selection.selecting_line = p_line; + selection.selecting_column = CLAMP(selection.selecting_column, 0, text[selection.selecting_line].length()); } if (p_column >= 0) { + ERR_FAIL_INDEX(selection.selecting_line, text.size()); ERR_FAIL_INDEX(p_column, text[selection.selecting_line].length()); selection.selecting_column = p_column; } @@ -3878,7 +4274,7 @@ void TextEdit::delete_selection() { update(); } -/* line wrapping. */ +/* Line wrapping. */ void TextEdit::set_line_wrapping_mode(LineWrappingMode p_wrapping_mode) { if (line_wrapping_mode != p_wrapping_mode) { line_wrapping_mode = p_wrapping_mode; @@ -4010,14 +4406,9 @@ double TextEdit::get_scroll_pos_for_line(int p_line, int p_wrap_index) const { return p_line; } - // Count the number of visible lines up to this line. double new_line_scroll_pos = 0.0; - int to = CLAMP(p_line, 0, text.size() - 1); - for (int i = 0; i < to; i++) { - if (!text.is_hidden(i)) { - new_line_scroll_pos++; - new_line_scroll_pos += get_line_wrap_count(i); - } + if (p_line > 0) { + new_line_scroll_pos = get_visible_line_count_in_range(0, MIN(p_line - 1, text.size() - 1)); } new_line_scroll_pos += p_wrap_index; return new_line_scroll_pos; @@ -4075,14 +4466,22 @@ int TextEdit::get_visible_line_count() const { return _get_control_height() / get_line_height(); } -int TextEdit::get_total_visible_line_count() const { +int TextEdit::get_visible_line_count_in_range(int p_from_line, int p_to_line) const { + ERR_FAIL_INDEX_V(p_from_line, text.size(), 0); + ERR_FAIL_INDEX_V(p_to_line, text.size(), 0); + + // So we can handle inputs in whatever order + if (p_from_line > p_to_line) { + SWAP(p_from_line, p_to_line); + } + /* Returns the total number of (lines + wrapped - hidden). */ if (!_is_hiding_enabled() && get_line_wrapping_mode() == LineWrappingMode::LINE_WRAPPING_NONE) { - return text.size(); + return (p_to_line - p_from_line) + 1; } int total_rows = 0; - for (int i = 0; i < text.size(); i++) { + for (int i = p_from_line; i <= p_to_line; i++) { if (!text.is_hidden(i)) { total_rows++; total_rows += get_line_wrap_count(i); @@ -4091,6 +4490,10 @@ int TextEdit::get_total_visible_line_count() const { return total_rows; } +int TextEdit::get_total_visible_line_count() const { + return get_visible_line_count_in_range(0, text.size() - 1); +} + // Auto adjust void TextEdit::adjust_viewport_to_caret() { // Make sure Caret is visible on the screen. @@ -4259,7 +4662,7 @@ void TextEdit::add_gutter(int p_at) { void TextEdit::remove_gutter(int p_gutter) { ERR_FAIL_INDEX(p_gutter, gutters.size()); - gutters.remove(p_gutter); + gutters.remove_at(p_gutter); for (int i = 0; i < text.size() + 1; i++) { text.remove_gutter(p_gutter); @@ -4379,12 +4782,10 @@ void TextEdit::merge_gutters(int p_from_line, int p_to_line) { update(); } -void TextEdit::set_gutter_custom_draw(int p_gutter, Object *p_object, const StringName &p_callback) { +void TextEdit::set_gutter_custom_draw(int p_gutter, const Callable &p_draw_callback) { ERR_FAIL_INDEX(p_gutter, gutters.size()); - ERR_FAIL_NULL(p_object); - gutters.write[p_gutter].custom_draw_obj = p_object->get_instance_id(); - gutters.write[p_gutter].custom_draw_callback = p_callback; + gutters.write[p_gutter].custom_draw_callback = p_draw_callback; update(); } @@ -4503,7 +4904,8 @@ void TextEdit::set_draw_control_chars(bool p_enabled) { menu->set_item_checked(menu->get_item_index(MENU_DISPLAY_UCC), draw_control_chars); } text.set_draw_control_chars(draw_control_chars); - text.invalidate_all(); + text.invalidate_font(); + _update_placeholder(); update(); } } @@ -4583,6 +4985,9 @@ void TextEdit::_bind_methods() { ClassDB::bind_method(D_METHOD("get_text"), &TextEdit::get_text); ClassDB::bind_method(D_METHOD("get_line_count"), &TextEdit::get_line_count); + ClassDB::bind_method(D_METHOD("set_placeholder", "text"), &TextEdit::set_placeholder); + ClassDB::bind_method(D_METHOD("get_placeholder"), &TextEdit::get_placeholder); + ClassDB::bind_method(D_METHOD("set_line", "line", "new_text"), &TextEdit::set_line); ClassDB::bind_method(D_METHOD("get_line", "line"), &TextEdit::get_line); @@ -4674,17 +5079,21 @@ void TextEdit::_bind_methods() { ClassDB::bind_method(D_METHOD("search", "text", "flags", "from_line", "from_colum"), &TextEdit::search); /* Tooltip */ - ClassDB::bind_method(D_METHOD("set_tooltip_request_func", "object", "callback", "data"), &TextEdit::set_tooltip_request_func); + ClassDB::bind_method(D_METHOD("set_tooltip_request_func", "callback"), &TextEdit::set_tooltip_request_func); /* Mouse */ ClassDB::bind_method(D_METHOD("get_local_mouse_pos"), &TextEdit::get_local_mouse_pos); ClassDB::bind_method(D_METHOD("get_word_at_pos", "position"), &TextEdit::get_word_at_pos); - ClassDB::bind_method(D_METHOD("get_line_column_at_pos", "position"), &TextEdit::get_line_column_at_pos); + ClassDB::bind_method(D_METHOD("get_line_column_at_pos", "position", "allow_out_of_bounds"), &TextEdit::get_line_column_at_pos, DEFVAL(true)); + ClassDB::bind_method(D_METHOD("get_pos_at_line_column", "line", "column"), &TextEdit::get_pos_at_line_column); + ClassDB::bind_method(D_METHOD("get_rect_at_line_column", "line", "column"), &TextEdit::get_rect_at_line_column); + ClassDB::bind_method(D_METHOD("get_minimap_line_at_pos", "position"), &TextEdit::get_minimap_line_at_pos); ClassDB::bind_method(D_METHOD("is_dragging_cursor"), &TextEdit::is_dragging_cursor); + ClassDB::bind_method(D_METHOD("is_mouse_over_selection", "edges"), &TextEdit::is_mouse_over_selection); /* Caret. */ BIND_ENUM_CONSTANT(CARET_TYPE_LINE); @@ -4759,7 +5168,7 @@ void TextEdit::_bind_methods() { ClassDB::bind_method(D_METHOD("deselect"), &TextEdit::deselect); ClassDB::bind_method(D_METHOD("delete_selection"), &TextEdit::delete_selection); - /* line wrapping. */ + /* Line wrapping. */ BIND_ENUM_CONSTANT(LINE_WRAPPING_NONE); BIND_ENUM_CONSTANT(LINE_WRAPPING_BOUNDARY); @@ -4776,7 +5185,7 @@ void TextEdit::_bind_methods() { ClassDB::bind_method(D_METHOD("get_line_wrapped_text", "line"), &TextEdit::get_line_wrapped_text); /* Viewport. */ - // Scolling. + // Scrolling. ClassDB::bind_method(D_METHOD("set_smooth_scroll_enable", "enable"), &TextEdit::set_smooth_scroll_enabled); ClassDB::bind_method(D_METHOD("is_smooth_scroll_enabled"), &TextEdit::is_smooth_scroll_enabled); @@ -4805,6 +5214,7 @@ void TextEdit::_bind_methods() { ClassDB::bind_method(D_METHOD("get_last_full_visible_line_wrap_index"), &TextEdit::get_last_full_visible_line_wrap_index); ClassDB::bind_method(D_METHOD("get_visible_line_count"), &TextEdit::get_visible_line_count); + ClassDB::bind_method(D_METHOD("get_visible_line_count_in_range", "from_line", "to_line"), &TextEdit::get_visible_line_count_in_range); ClassDB::bind_method(D_METHOD("get_total_visible_line_count"), &TextEdit::get_total_visible_line_count); // Auto adjust @@ -4841,7 +5251,7 @@ void TextEdit::_bind_methods() { ClassDB::bind_method(D_METHOD("set_gutter_overwritable", "gutter", "overwritable"), &TextEdit::set_gutter_overwritable); ClassDB::bind_method(D_METHOD("is_gutter_overwritable", "gutter"), &TextEdit::is_gutter_overwritable); ClassDB::bind_method(D_METHOD("merge_gutters", "from_line", "to_line"), &TextEdit::merge_gutters); - ClassDB::bind_method(D_METHOD("set_gutter_custom_draw", "column", "object", "callback"), &TextEdit::set_gutter_custom_draw); + ClassDB::bind_method(D_METHOD("set_gutter_custom_draw", "column", "draw_callback"), &TextEdit::set_gutter_custom_draw); ClassDB::bind_method(D_METHOD("get_total_gutter_width"), &TextEdit::get_total_gutter_width); // Line gutters. @@ -4886,8 +5296,9 @@ void TextEdit::_bind_methods() { /* Inspector */ ADD_PROPERTY(PropertyInfo(Variant::STRING, "text", PROPERTY_HINT_MULTILINE_TEXT), "set_text", "get_text"); + ADD_PROPERTY(PropertyInfo(Variant::STRING, "placeholder_text", PROPERTY_HINT_MULTILINE_TEXT), "set_placeholder", "get_placeholder"); ADD_PROPERTY(PropertyInfo(Variant::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"), "set_language", "get_language"); + ADD_PROPERTY(PropertyInfo(Variant::STRING, "language", PROPERTY_HINT_LOCALE_ID, ""), "set_language", "get_language"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "editable"), "set_editable", "is_editable"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "context_menu_enabled"), "set_context_menu_enabled", "is_context_menu_enabled"); @@ -4955,19 +5366,21 @@ bool TextEdit::_set(const StringName &p_name, const Variant &p_value) { if (str.begins_with("opentype_features/")) { String name = str.get_slicec('/', 1); int32_t tag = TS->name_to_tag(name); - double value = p_value; + int value = p_value; if (value == -1) { if (opentype_features.has(tag)) { opentype_features.erase(tag); text.set_font_features(opentype_features); - text.invalidate_all(); + text.invalidate_font(); + _update_placeholder(); update(); } } else { - if ((double)opentype_features[tag] != value) { + if (!opentype_features.has(tag) || (int)opentype_features[tag] != value) { opentype_features[tag] = value; text.set_font_features(opentype_features); - text.invalidate_all(); + text.invalidate_font(); + _update_placeholder(); update(); } } @@ -4997,7 +5410,7 @@ bool TextEdit::_get(const StringName &p_name, Variant &r_ret) const { void TextEdit::_get_property_list(List<PropertyInfo> *p_list) const { for (const Variant *ftr = opentype_features.next(nullptr); ftr != nullptr; ftr = opentype_features.next(ftr)) { String name = TS->tag_to_name(*ftr); - p_list->push_back(PropertyInfo(Variant::FLOAT, "opentype_features/" + name)); + p_list->push_back(PropertyInfo(Variant::INT, "opentype_features/" + name)); } p_list->push_back(PropertyInfo(Variant::NIL, "opentype_features/_new", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_EDITOR)); } @@ -5137,7 +5550,7 @@ void TextEdit::_cut_internal() { set_caret_line(get_caret_line() + 1); } - // Correct the visualy perceived caret column taking care of identation level of the lines. + // Correct the visually perceived caret column taking care of indentation level of the lines. int diff_indent = indent_level - get_indent_level(get_caret_line()); cc += diff_indent; if (diff_indent != 0) { @@ -5249,21 +5662,23 @@ void TextEdit::_generate_context_menu() { // Reorganize context menu. menu->clear(); if (editable) { - menu->add_item(RTR("Cut"), MENU_CUT, is_shortcut_keys_enabled() ? _get_menu_action_accelerator("ui_cut") : 0); + menu->add_item(RTR("Cut"), MENU_CUT, is_shortcut_keys_enabled() ? _get_menu_action_accelerator("ui_cut") : Key::NONE); } - menu->add_item(RTR("Copy"), MENU_COPY, is_shortcut_keys_enabled() ? _get_menu_action_accelerator("ui_copy") : 0); + menu->add_item(RTR("Copy"), MENU_COPY, is_shortcut_keys_enabled() ? _get_menu_action_accelerator("ui_copy") : Key::NONE); if (editable) { - menu->add_item(RTR("Paste"), MENU_PASTE, is_shortcut_keys_enabled() ? _get_menu_action_accelerator("ui_paste") : 0); + menu->add_item(RTR("Paste"), MENU_PASTE, is_shortcut_keys_enabled() ? _get_menu_action_accelerator("ui_paste") : Key::NONE); } - menu->add_separator(); - if (is_selecting_enabled()) { - menu->add_item(RTR("Select All"), MENU_SELECT_ALL, is_shortcut_keys_enabled() ? _get_menu_action_accelerator("ui_text_select_all") : 0); + if (selecting_enabled || editable) { + menu->add_separator(); + } + if (selecting_enabled) { + menu->add_item(RTR("Select All"), MENU_SELECT_ALL, is_shortcut_keys_enabled() ? _get_menu_action_accelerator("ui_text_select_all") : Key::NONE); } if (editable) { menu->add_item(RTR("Clear"), MENU_CLEAR); menu->add_separator(); - menu->add_item(RTR("Undo"), MENU_UNDO, is_shortcut_keys_enabled() ? _get_menu_action_accelerator("ui_undo") : 0); - menu->add_item(RTR("Redo"), MENU_REDO, is_shortcut_keys_enabled() ? _get_menu_action_accelerator("ui_redo") : 0); + menu->add_item(RTR("Undo"), MENU_UNDO, is_shortcut_keys_enabled() ? _get_menu_action_accelerator("ui_undo") : Key::NONE); + menu->add_item(RTR("Redo"), MENU_REDO, is_shortcut_keys_enabled() ? _get_menu_action_accelerator("ui_redo") : Key::NONE); } menu->add_separator(); menu->add_submenu_item(RTR("Text Writing Direction"), "DirMenu"); @@ -5284,25 +5699,25 @@ void TextEdit::_generate_context_menu() { } } -int TextEdit::_get_menu_action_accelerator(const String &p_action) { +Key TextEdit::_get_menu_action_accelerator(const String &p_action) { const List<Ref<InputEvent>> *events = InputMap::get_singleton()->action_get_events(p_action); if (!events) { - return 0; + return Key::NONE; } // Use first event in the list for the accelerator. const List<Ref<InputEvent>>::Element *first_event = events->front(); if (!first_event) { - return 0; + return Key::NONE; } const Ref<InputEventKey> event = first_event->get(); if (event.is_null()) { - return 0; + return Key::NONE; } // Use physical keycode if non-zero - if (event->get_physical_keycode() != 0) { + if (event->get_physical_keycode() != Key::NONE) { return event->get_physical_keycode_with_modifiers(); } else { return event->get_keycode_with_modifiers(); @@ -5383,9 +5798,9 @@ int TextEdit::_get_column_pos_of_word(const String &p_key, const String &p_searc if (col != -1 && p_search_flags & SEARCH_WHOLE_WORDS) { p_from_column = col; - if (col > 0 && _is_text_char(p_search[col - 1])) { + if (col > 0 && !is_symbol(p_search[col - 1])) { col = -1; - } else if ((col + p_key.length()) < p_search.length() && _is_text_char(p_search[col + p_key.length()])) { + } else if ((col + p_key.length()) < p_search.length() && !is_symbol(p_search[col + p_key.length()])) { col = -1; } } @@ -5457,10 +5872,10 @@ int TextEdit::_get_column_x_offset_for_line(int p_char, int p_line) const { /* Selection */ void TextEdit::_click_selection_held() { - // Warning: is_mouse_button_pressed(MOUSE_BUTTON_LEFT) returns false for double+ clicks, so this doesn't work for MODE_WORD + // Warning: is_mouse_button_pressed(MouseButton::LEFT) returns false for double+ clicks, so this doesn't work for MODE_WORD // and MODE_LINE. However, moving the mouse triggers _gui_input, which calls these functions too, so that's not a huge problem. // I'm unsure if there's an actual fix that doesn't have a ton of side effects. - if (Input::get_singleton()->is_mouse_button_pressed(MOUSE_BUTTON_LEFT) && selection.selecting_mode != SelectionMode::SELECTION_MODE_NONE) { + if (Input::get_singleton()->is_mouse_button_pressed(MouseButton::LEFT) && selection.selecting_mode != SelectionMode::SELECTION_MODE_NONE) { switch (selection.selecting_mode) { case SelectionMode::SELECTION_MODE_POINTER: { _update_selection_mode_pointer(); @@ -5619,6 +6034,7 @@ void TextEdit::_update_wrap_at_column(bool p_force) { text.set_width(-1); } text.invalidate_all_lines(); + _update_placeholder(); } _update_caret_wrap_offset(); @@ -5646,14 +6062,16 @@ void TextEdit::_update_scrollbars() { h_scroll->set_begin(Point2(0, size.height - hmin.height)); h_scroll->set_end(Point2(size.width - vmin.width, size.height)); + bool draw_placeholder = text.size() == 1 && text[0].length() == 0; + int visible_rows = get_visible_line_count(); - int total_rows = get_total_visible_line_count(); + int total_rows = draw_placeholder ? placeholder_wraped_rows.size() - 1 : get_total_visible_line_count(); if (scroll_past_end_of_file_enabled) { total_rows += visible_rows - 1; } int visible_width = size.width - style_normal->get_minimum_size().width; - int total_width = text.get_max_width() + vmin.x + gutters_width + gutter_padding; + int total_width = (draw_placeholder ? placeholder_max_width : text.get_max_width()) + vmin.x + gutters_width + gutter_padding; if (draw_minimap) { total_width += minimap_width; @@ -5676,6 +6094,7 @@ void TextEdit::_update_scrollbars() { caret.line_ofs = 0; caret.wrap_ofs = 0; v_scroll->set_value(0); + v_scroll->set_max(0); v_scroll->hide(); } @@ -5693,6 +6112,7 @@ void TextEdit::_update_scrollbars() { } else { caret.x_ofs = 0; h_scroll->set_value(0); + h_scroll->set_max(0); h_scroll->hide(); } @@ -5723,20 +6143,22 @@ void TextEdit::_scroll_moved(double p_to_val) { } if (v_scroll->is_visible_in_tree()) { // Set line ofs and wrap ofs. + bool draw_placeholder = text.size() == 1 && text[0].length() == 0; + int v_scroll_i = floor(get_v_scroll()); int sc = 0; int n_line; for (n_line = 0; n_line < text.size(); n_line++) { if (!_is_line_hidden(n_line)) { sc++; - sc += get_line_wrap_count(n_line); + sc += draw_placeholder ? placeholder_wraped_rows.size() - 1 : get_line_wrap_count(n_line); if (sc > v_scroll_i) { break; } } } n_line = MIN(n_line, text.size() - 1); - int line_wrap_amount = get_line_wrap_count(n_line); + int line_wrap_amount = draw_placeholder ? placeholder_wraped_rows.size() - 1 : get_line_wrap_count(n_line); int wi = line_wrap_amount - (sc - v_scroll_i - 1); wi = CLAMP(wi, 0, line_wrap_amount); @@ -5760,7 +6182,7 @@ double TextEdit::_get_v_scroll_offset() const { } void TextEdit::_scroll_up(real_t p_delta) { - if (scrolling && smooth_scroll_enabled && SGN(target_v_scroll - v_scroll->get_value()) != SGN(-p_delta)) { + if (scrolling && smooth_scroll_enabled && SIGN(target_v_scroll - v_scroll->get_value()) != SIGN(-p_delta)) { scrolling = false; minimap_clicked = false; } @@ -5787,7 +6209,7 @@ void TextEdit::_scroll_up(real_t p_delta) { } void TextEdit::_scroll_down(real_t p_delta) { - if (scrolling && smooth_scroll_enabled && SGN(target_v_scroll - v_scroll->get_value()) != SGN(p_delta)) { + if (scrolling && smooth_scroll_enabled && SIGN(target_v_scroll - v_scroll->get_value()) != SIGN(p_delta)) { scrolling = false; minimap_clicked = false; } @@ -6068,11 +6490,11 @@ void TextEdit::_base_insert_text(int p_line, int p_char, const String &p_text, i ERR_FAIL_COND(p_char < 0); /* STEP 1: Remove \r from source text and separate in substrings. */ - - Vector<String> substrings = p_text.replace("\r", "").split("\n"); + const String text_to_insert = p_text.replace("\r", ""); + Vector<String> substrings = text_to_insert.split("\n"); // Is this just a new empty line? - bool shift_first_line = p_char == 0 && p_text.replace("\r", "") == "\n"; + bool shift_first_line = p_char == 0 && substrings.size() == 2 && text_to_insert == "\n"; /* STEP 2: Add spaces if the char is greater than the end of the line. */ while (p_char > text[p_line].length()) { @@ -6080,24 +6502,19 @@ void TextEdit::_base_insert_text(int p_line, int p_char, const String &p_text, i } /* STEP 3: Separate dest string in pre and post text. */ - - String preinsert_text = text[p_line].substr(0, p_char); String postinsert_text = text[p_line].substr(p_char, text[p_line].size()); - for (int j = 0; j < substrings.size(); j++) { - // Insert the substrings. - - if (j == 0) { - text.set(p_line, preinsert_text + substrings[j], structured_text_parser(st_parser, st_args, preinsert_text + substrings[j])); - } else { - text.insert(p_line + j, substrings[j], structured_text_parser(st_parser, st_args, substrings[j])); - } + substrings.write[0] = text[p_line].substr(0, p_char) + substrings[0]; + substrings.write[substrings.size() - 1] += postinsert_text; - if (j == substrings.size() - 1) { - text.set(p_line + j, text[p_line + j] + postinsert_text, structured_text_parser(st_parser, st_args, text[p_line + j] + postinsert_text)); - } + Vector<Array> bidi_override; + bidi_override.resize(substrings.size()); + for (int i = 0; i < substrings.size(); i++) { + bidi_override.write[i] = structured_text_parser(st_parser, st_args, substrings[i]); } + text.insert(p_line, substrings, bidi_override); + if (shift_first_line) { text.move_gutters(p_line, p_line + 1); text.set_hidden(p_line + 1, text.is_hidden(p_line)); @@ -6130,7 +6547,7 @@ String TextEdit::_base_get_text(int p_from_line, int p_from_column, int p_to_lin ERR_FAIL_COND_V(p_to_line < p_from_line, String()); // 'from > to'. ERR_FAIL_COND_V(p_to_line == p_from_line && p_to_column < p_from_column, String()); // 'from > to'. - String ret; + StringBuilder ret; for (int i = p_from_line; i <= p_to_line; i++) { int begin = (i == p_from_line) ? p_from_column : 0; @@ -6142,7 +6559,7 @@ String TextEdit::_base_get_text(int p_from_line, int p_from_column, int p_to_lin ret += text[i].substr(begin, end - begin); } - return ret; + return ret.as_string(); } void TextEdit::_base_remove_text(int p_from_line, int p_from_column, int p_to_line, int p_to_column) { @@ -6156,9 +6573,7 @@ void TextEdit::_base_remove_text(int p_from_line, int p_from_column, int p_to_li String pre_text = text[p_from_line].substr(0, p_from_column); String post_text = text[p_to_line].substr(p_to_column, text[p_to_line].length()); - for (int i = p_from_line; i < p_to_line; i++) { - text.remove(p_from_line + 1); - } + text.remove_range(p_from_line, p_to_line); text.set(p_from_line, pre_text + post_text, structured_text_parser(st_parser, st_args, pre_text + post_text)); if (!text_changed_dirty && !setting_text) { @@ -6170,7 +6585,9 @@ void TextEdit::_base_remove_text(int p_from_line, int p_from_column, int p_to_li emit_signal(SNAME("lines_edited_from"), p_to_line, p_from_line); } -TextEdit::TextEdit() { +TextEdit::TextEdit(const String &p_placeholder) { + placeholder_data_buf.instantiate(); + clear(); set_focus_mode(FOCUS_ALL); _update_caches(); @@ -6210,5 +6627,7 @@ TextEdit::TextEdit() { undo_stack_max_size = GLOBAL_GET("gui/common/text_edit_undo_stack_max_size"); + set_placeholder(p_placeholder); + set_editable(true); } diff --git a/scene/gui/text_edit.h b/scene/gui/text_edit.h index 23f80efce0..6deaf76e5e 100644 --- a/scene/gui/text_edit.h +++ b/scene/gui/text_edit.h @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ @@ -70,7 +70,7 @@ public: GUTTER_TYPE_CUSTOM }; - /* Contex Menu. */ + /* Context Menu. */ enum MenuItems { MENU_CUT, MENU_COPY, @@ -120,8 +120,7 @@ private: bool clickable = false; bool overwritable = false; - ObjectID custom_draw_obj = ObjectID(); - StringName custom_draw_callback; + Callable custom_draw_callback; }; class Text { @@ -159,6 +158,7 @@ private: mutable Vector<Line> text; Ref<Font> font; int font_size = -1; + int font_height = 0; Dictionary opentype_features; String language; @@ -189,6 +189,7 @@ private: int get_max_width() const; void set_width(float p_width); + float get_width() const; int get_line_wrap_amount(int p_line) const; Vector<Vector2i> get_line_wrap_ranges(int p_line) const; @@ -204,12 +205,13 @@ private: } } bool is_hidden(int p_line) const { return text[p_line].hidden; } - void insert(int p_at, const String &p_text, const Array &p_bidi_override); - void remove(int p_at); + void insert(int p_at, const Vector<String> &p_text, const Vector<Array> &p_bidi_override); + void remove_range(int p_from_line, int p_to_line); int size() const { return text.size(); } void clear(); - void invalidate_cache(int p_line, int p_column = -1, const String &p_ime_text = String(), const Array &p_bidi_override = Array()); + void invalidate_cache(int p_line, int p_column = -1, bool p_text_changed = false, const String &p_ime_text = String(), const Array &p_bidi_override = Array()); + void invalidate_font(); void invalidate_all(); void invalidate_all_lines(); @@ -249,6 +251,17 @@ private: String ime_text = ""; Point2 ime_selection; + // Placeholder + String placeholder_text = ""; + Array placeholder_bidi_override; + Ref<TextParagraph> placeholder_data_buf; + int placeholder_line_height = -1; + int placeholder_max_width = -1; + + Vector<String> placeholder_wraped_rows; + + void _update_placeholder(); + /* Initialise to opposite first, so we get past the early-out in set_editable. */ bool editable = false; @@ -271,7 +284,7 @@ private: bool virtual_keyboard_enabled = true; bool middle_mouse_paste_enabled = true; - // Overridable actions + // Overridable actions. String cut_copy_line = ""; // Context menu. @@ -280,7 +293,7 @@ private: PopupMenu *menu_ctl = nullptr; void _generate_context_menu(); - int _get_menu_action_accelerator(const String &p_action); + Key _get_menu_action_accelerator(const String &p_action); /* Versioning */ struct TextOperation { @@ -331,11 +344,17 @@ private: int _get_column_pos_of_word(const String &p_key, const String &p_search, uint32_t p_search_flags, int p_from_column) const; /* Tooltip. */ - ObjectID tooltip_obj_id; - StringName tooltip_func; - Variant tooltip_ud; + Callable tooltip_callback; /* Mouse */ + struct LineDrawingCache { + int y_offset = 0; + Vector<int> first_visible_chars; + Vector<int> last_visible_chars; + }; + + Map<int, LineDrawingCache> line_drawing_cache; + int _get_char_pos_for_line(int p_px, int p_line, int p_wrap_index = 0) const; /* Caret. */ @@ -365,7 +384,10 @@ private: bool move_caret_on_right_click = true; - bool caret_mid_grapheme_enabled = false; + bool caret_mid_grapheme_enabled = true; + + bool drag_action = false; + bool drag_caret_force_displayed = false; void _emit_caret_changed(); @@ -392,6 +414,7 @@ private: int to_column = 0; bool shiftclick_left = false; + bool drag_attempt = false; } selection; bool selecting_enabled = true; @@ -415,7 +438,7 @@ private: void _pre_shift_selection(); void _post_shift_selection(); - /* line wrapping. */ + /* Line wrapping. */ LineWrappingMode line_wrapping_mode = LineWrappingMode::LINE_WRAPPING_NONE; int wrap_at_column = 0; @@ -455,14 +478,14 @@ private: void _scroll_lines_up(); void _scroll_lines_down(); - // Minimap + // Minimap. bool draw_minimap = false; int minimap_width = 80; Point2 minimap_char_size = Point2(1, 2); int minimap_line_spacing = 1; - // minimap scroll + // Minimap scroll. bool minimap_clicked = false; bool hovering_minimap = false; bool dragging_minimap = false; @@ -501,6 +524,7 @@ private: int font_size = 16; Color font_color = Color(1, 1, 1); Color font_readonly_color = Color(1, 1, 1); + Color font_placeholder_color = Color(1, 1, 1, 0.6); int outline_size = 0; Color outline_color = Color(1, 1, 1); @@ -603,8 +627,11 @@ public: virtual Size2 get_minimum_size() const override; virtual bool is_text_field() const override; virtual CursorShape get_cursor_shape(const Point2 &p_pos = Point2i()) const override; + virtual Variant get_drag_data(const Point2 &p_point) override; + virtual bool can_drop_data(const Point2 &p_point, const Variant &p_data) const override; + virtual void drop_data(const Point2 &p_point, const Variant &p_data) override; virtual String get_tooltip(const Point2 &p_pos) const override; - void set_tooltip_request_func(Object *p_obj, const StringName &p_function, const Variant &p_udata); + void set_tooltip_request_func(const Callable &p_tooltip_callback); /* Text */ // Text properties. @@ -654,6 +681,9 @@ public: String get_text() const; int get_line_count() const; + void set_placeholder(const String &p_text); + String get_placeholder() const; + void set_line(int p_line, const String &p_new_text); String get_line(int p_line) const; @@ -716,10 +746,14 @@ public: String get_word_at_pos(const Vector2 &p_pos) const; - Point2i get_line_column_at_pos(const Point2i &p_pos) const; + Point2i get_line_column_at_pos(const Point2i &p_pos, bool p_allow_out_of_bounds = true) const; + Point2i get_pos_at_line_column(int p_line, int p_column) const; + Rect2i get_rect_at_line_column(int p_line, int p_column) const; + int get_minimap_line_at_pos(const Point2i &p_pos) const; bool is_dragging_cursor() const; + bool is_mouse_over_selection(bool p_edges = true) const; /* Caret */ void set_caret_type(CaretType p_type); @@ -782,7 +816,7 @@ public: void deselect(); void delete_selection(); - /* line wrapping. */ + /* Line wrapping. */ void set_line_wrapping_mode(LineWrappingMode p_wrapping_mode); LineWrappingMode get_line_wrapping_mode() const; @@ -822,6 +856,7 @@ public: int get_last_full_visible_line_wrap_index() const; int get_visible_line_count() const; + int get_visible_line_count_in_range(int p_from, int p_to) const; int get_total_visible_line_count() const; // Auto Adjust @@ -863,7 +898,7 @@ public: void merge_gutters(int p_from_line, int p_to_line); - void set_gutter_custom_draw(int p_gutter, Object *p_object, const StringName &p_callback); + void set_gutter_custom_draw(int p_gutter, const Callable &p_draw_callback); // Line gutters. void set_line_gutter_metadata(int p_line, int p_gutter, const Variant &p_metadata); @@ -905,7 +940,7 @@ public: void set_draw_spaces(bool p_enabled); bool is_drawing_spaces() const; - TextEdit(); + TextEdit(const String &p_placeholder = String()); }; VARIANT_ENUM_CAST(TextEdit::CaretType); diff --git a/scene/gui/texture_button.cpp b/scene/gui/texture_button.cpp index 8659ea06a2..26acfaaa70 100644 --- a/scene/gui/texture_button.cpp +++ b/scene/gui/texture_button.cpp @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ @@ -37,7 +37,7 @@ Size2 TextureButton::get_minimum_size() const { Size2 rscale = Control::get_minimum_size(); - if (!expand) { + if (!ignore_texture_size) { if (normal.is_null()) { if (pressed.is_null()) { if (hover.is_null()) { @@ -170,55 +170,60 @@ void TextureButton::_notification(int p_what) { Point2 ofs; Size2 size; + bool draw_focus = (has_focus() && focused.is_valid()); + + // If no other texture is valid, try using focused texture. + bool draw_focus_only = draw_focus && !texdraw.is_valid(); + if (draw_focus_only) { + texdraw = focused; + } if (texdraw.is_valid()) { size = texdraw->get_size(); _texture_region = Rect2(Point2(), texdraw->get_size()); _tile = false; - if (expand) { - switch (stretch_mode) { - case STRETCH_KEEP: - size = texdraw->get_size(); - break; - case STRETCH_SCALE: - size = get_size(); - break; - case STRETCH_TILE: - size = get_size(); - _tile = true; - break; - case STRETCH_KEEP_CENTERED: - ofs = (get_size() - texdraw->get_size()) / 2; - size = texdraw->get_size(); - break; - case STRETCH_KEEP_ASPECT_CENTERED: - case STRETCH_KEEP_ASPECT: { - Size2 _size = get_size(); - float tex_width = texdraw->get_width() * _size.height / texdraw->get_height(); - float tex_height = _size.height; - - if (tex_width > _size.width) { - tex_width = _size.width; - tex_height = texdraw->get_height() * tex_width / texdraw->get_width(); - } + switch (stretch_mode) { + case STRETCH_KEEP: + size = texdraw->get_size(); + break; + case STRETCH_SCALE: + size = get_size(); + break; + case STRETCH_TILE: + size = get_size(); + _tile = true; + break; + case STRETCH_KEEP_CENTERED: + ofs = (get_size() - texdraw->get_size()) / 2; + size = texdraw->get_size(); + break; + case STRETCH_KEEP_ASPECT_CENTERED: + case STRETCH_KEEP_ASPECT: { + Size2 _size = get_size(); + float tex_width = texdraw->get_width() * _size.height / texdraw->get_height(); + float tex_height = _size.height; + + if (tex_width > _size.width) { + tex_width = _size.width; + tex_height = texdraw->get_height() * tex_width / texdraw->get_width(); + } - if (stretch_mode == STRETCH_KEEP_ASPECT_CENTERED) { - ofs.x = (_size.width - tex_width) / 2; - ofs.y = (_size.height - tex_height) / 2; - } - size.width = tex_width; - size.height = tex_height; - } break; - case STRETCH_KEEP_ASPECT_COVERED: { - size = get_size(); - Size2 tex_size = texdraw->get_size(); - Size2 scale_size(size.width / tex_size.width, size.height / tex_size.height); - float scale = scale_size.width > scale_size.height ? scale_size.width : scale_size.height; - Size2 scaled_tex_size = tex_size * scale; - Point2 ofs2 = ((scaled_tex_size - size) / scale).abs() / 2.0f; - _texture_region = Rect2(ofs2, size / scale); - } break; - } + if (stretch_mode == STRETCH_KEEP_ASPECT_CENTERED) { + ofs.x = (_size.width - tex_width) / 2; + ofs.y = (_size.height - tex_height) / 2; + } + size.width = tex_width; + size.height = tex_height; + } break; + case STRETCH_KEEP_ASPECT_COVERED: { + size = get_size(); + Size2 tex_size = texdraw->get_size(); + Size2 scale_size(size.width / tex_size.width, size.height / tex_size.height); + float scale = scale_size.width > scale_size.height ? scale_size.width : scale_size.height; + Size2 scaled_tex_size = tex_size * scale; + Point2 ofs2 = ((scaled_tex_size - size) / scale).abs() / 2.0f; + _texture_region = Rect2(ofs2, size / scale); + } break; } _position_rect = Rect2(ofs, size); @@ -226,7 +231,9 @@ void TextureButton::_notification(int p_what) { size.width *= hflip ? -1.0f : 1.0f; size.height *= vflip ? -1.0f : 1.0f; - if (_tile) { + if (draw_focus_only) { + // Do nothing, we only needed to calculate the rectangle. + } else if (_tile) { draw_texture_rect(texdraw, Rect2(ofs, size), _tile); } else { draw_texture_rect_region(texdraw, Rect2(ofs, size), _texture_region); @@ -235,7 +242,7 @@ void TextureButton::_notification(int p_what) { _position_rect = Rect2(); } - if (has_focus() && focused.is_valid()) { + if (draw_focus) { draw_texture_rect(focused, Rect2(ofs, size), false); }; } break; @@ -249,7 +256,7 @@ void TextureButton::_bind_methods() { ClassDB::bind_method(D_METHOD("set_disabled_texture", "texture"), &TextureButton::set_disabled_texture); ClassDB::bind_method(D_METHOD("set_focused_texture", "texture"), &TextureButton::set_focused_texture); ClassDB::bind_method(D_METHOD("set_click_mask", "mask"), &TextureButton::set_click_mask); - ClassDB::bind_method(D_METHOD("set_expand", "expand"), &TextureButton::set_expand); + ClassDB::bind_method(D_METHOD("set_ignore_texture_size", "ignore"), &TextureButton::set_ignore_texture_size); ClassDB::bind_method(D_METHOD("set_stretch_mode", "mode"), &TextureButton::set_stretch_mode); ClassDB::bind_method(D_METHOD("set_flip_h", "enable"), &TextureButton::set_flip_h); ClassDB::bind_method(D_METHOD("is_flipped_h"), &TextureButton::is_flipped_h); @@ -262,7 +269,7 @@ void TextureButton::_bind_methods() { ClassDB::bind_method(D_METHOD("get_disabled_texture"), &TextureButton::get_disabled_texture); ClassDB::bind_method(D_METHOD("get_focused_texture"), &TextureButton::get_focused_texture); ClassDB::bind_method(D_METHOD("get_click_mask"), &TextureButton::get_click_mask); - ClassDB::bind_method(D_METHOD("get_expand"), &TextureButton::get_expand); + ClassDB::bind_method(D_METHOD("get_ignore_texture_size"), &TextureButton::get_ignore_texture_size); ClassDB::bind_method(D_METHOD("get_stretch_mode"), &TextureButton::get_stretch_mode); ADD_GROUP("Textures", "texture_"); @@ -272,7 +279,7 @@ void TextureButton::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "texture_disabled", PROPERTY_HINT_RESOURCE_TYPE, "Texture2D"), "set_disabled_texture", "get_disabled_texture"); ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "texture_focused", PROPERTY_HINT_RESOURCE_TYPE, "Texture2D"), "set_focused_texture", "get_focused_texture"); ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "texture_click_mask", PROPERTY_HINT_RESOURCE_TYPE, "BitMap"), "set_click_mask", "get_click_mask"); - ADD_PROPERTY(PropertyInfo(Variant::BOOL, "expand", PROPERTY_HINT_RESOURCE_TYPE, "bool"), "set_expand", "get_expand"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "ignore_texture_size", PROPERTY_HINT_RESOURCE_TYPE, "bool"), "set_ignore_texture_size", "get_ignore_texture_size"); ADD_PROPERTY(PropertyInfo(Variant::INT, "stretch_mode", PROPERTY_HINT_ENUM, "Scale,Tile,Keep,Keep Centered,Keep Aspect,Keep Aspect Centered,Keep Aspect Covered"), "set_stretch_mode", "get_stretch_mode"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "flip_h", PROPERTY_HINT_RESOURCE_TYPE, "bool"), "set_flip_h", "is_flipped_h"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "flip_v", PROPERTY_HINT_RESOURCE_TYPE, "bool"), "set_flip_v", "is_flipped_v"); @@ -289,19 +296,19 @@ void TextureButton::_bind_methods() { void TextureButton::set_normal_texture(const Ref<Texture2D> &p_normal) { normal = p_normal; update(); - minimum_size_changed(); + update_minimum_size(); } void TextureButton::set_pressed_texture(const Ref<Texture2D> &p_pressed) { pressed = p_pressed; update(); - minimum_size_changed(); + update_minimum_size(); } void TextureButton::set_hover_texture(const Ref<Texture2D> &p_hover) { hover = p_hover; update(); - minimum_size_changed(); + update_minimum_size(); } void TextureButton::set_disabled_texture(const Ref<Texture2D> &p_disabled) { @@ -312,7 +319,7 @@ void TextureButton::set_disabled_texture(const Ref<Texture2D> &p_disabled) { void TextureButton::set_click_mask(const Ref<BitMap> &p_click_mask) { click_mask = p_click_mask; update(); - minimum_size_changed(); + update_minimum_size(); } Ref<Texture2D> TextureButton::get_normal_texture() const { @@ -343,13 +350,13 @@ void TextureButton::set_focused_texture(const Ref<Texture2D> &p_focused) { focused = p_focused; }; -bool TextureButton::get_expand() const { - return expand; +bool TextureButton::get_ignore_texture_size() const { + return ignore_texture_size; } -void TextureButton::set_expand(bool p_expand) { - expand = p_expand; - minimum_size_changed(); +void TextureButton::set_ignore_texture_size(bool p_ignore) { + ignore_texture_size = p_ignore; + update_minimum_size(); update(); } diff --git a/scene/gui/texture_button.h b/scene/gui/texture_button.h index 8361f3c341..5762949acd 100644 --- a/scene/gui/texture_button.h +++ b/scene/gui/texture_button.h @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ @@ -54,8 +54,8 @@ private: Ref<Texture2D> disabled; Ref<Texture2D> focused; Ref<BitMap> click_mask; - bool expand = false; - StretchMode stretch_mode = STRETCH_SCALE; + bool ignore_texture_size = false; + StretchMode stretch_mode = STRETCH_KEEP; Rect2 _texture_region; Rect2 _position_rect; @@ -85,8 +85,8 @@ public: Ref<Texture2D> get_focused_texture() const; Ref<BitMap> get_click_mask() const; - bool get_expand() const; - void set_expand(bool p_expand); + bool get_ignore_texture_size() const; + void set_ignore_texture_size(bool p_ignore); void set_stretch_mode(StretchMode p_stretch_mode); StretchMode get_stretch_mode() const; diff --git a/scene/gui/texture_progress_bar.cpp b/scene/gui/texture_progress_bar.cpp index fe11de128a..043c0f464c 100644 --- a/scene/gui/texture_progress_bar.cpp +++ b/scene/gui/texture_progress_bar.cpp @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ @@ -35,7 +35,7 @@ void TextureProgressBar::set_under_texture(const Ref<Texture2D> &p_texture) { under = p_texture; update(); - minimum_size_changed(); + update_minimum_size(); } Ref<Texture2D> TextureProgressBar::get_under_texture() const { @@ -46,7 +46,7 @@ void TextureProgressBar::set_over_texture(const Ref<Texture2D> &p_texture) { over = p_texture; update(); if (under.is_null()) { - minimum_size_changed(); + update_minimum_size(); } } @@ -58,7 +58,7 @@ void TextureProgressBar::set_stretch_margin(Side p_side, int p_size) { ERR_FAIL_INDEX((int)p_side, 4); stretch_margin[p_side] = p_size; update(); - minimum_size_changed(); + update_minimum_size(); } int TextureProgressBar::get_stretch_margin(Side p_side) const { @@ -69,7 +69,7 @@ int TextureProgressBar::get_stretch_margin(Side p_side) const { void TextureProgressBar::set_nine_patch_stretch(bool p_stretch) { nine_patch_stretch = p_stretch; update(); - minimum_size_changed(); + update_minimum_size(); } bool TextureProgressBar::get_nine_patch_stretch() const { @@ -93,7 +93,7 @@ Size2 TextureProgressBar::get_minimum_size() const { void TextureProgressBar::set_progress_texture(const Ref<Texture2D> &p_texture) { progress = p_texture; update(); - minimum_size_changed(); + update_minimum_size(); } Ref<Texture2D> TextureProgressBar::get_progress_texture() const { @@ -387,7 +387,6 @@ void TextureProgressBar::draw_nine_patch_stretched(const Ref<Texture2D> &p_textu } void TextureProgressBar::_notification(int p_what) { - const float corners[12] = { -0.125, -0.375, -0.625, -0.875, 0.125, 0.375, 0.625, 0.875, 1.125, 1.375, 1.625, 1.875 }; switch (p_what) { case NOTIFICATION_DRAW: { if (nine_patch_stretch && (mode == FILL_LEFT_TO_RIGHT || mode == FILL_RIGHT_TO_LEFT || mode == FILL_TOP_TO_BOTTOM || mode == FILL_BOTTOM_TO_TOP || mode == FILL_BILINEAR_LEFT_AND_RIGHT || mode == FILL_BILINEAR_TOP_AND_BOTTOM)) { @@ -452,7 +451,7 @@ void TextureProgressBar::_notification(int p_what) { float val = get_as_ratio() * rad_max_degrees / 360; if (val == 1) { Rect2 region = Rect2(progress_offset, s); - Rect2 source = Rect2(Point2(), s); + Rect2 source = Rect2(Point2(), progress->get_size()); draw_texture_rect_region(progress, region, source, tint_progress); } else if (val != 0) { Array pts; @@ -466,16 +465,14 @@ void TextureProgressBar::_notification(int p_what) { } float end = start + direction * val; - pts.append(start); - pts.append(end); float from = MIN(start, end); float to = MAX(start, end); - for (int i = 0; i < 12; i++) { - if (corners[i] > from && corners[i] < to) { - pts.append(corners[i]); - } + pts.append(from); + for (float corner = Math::floor(from * 4 + 0.5) * 0.25 + 0.125; corner < to; corner += 0.25) { + pts.append(corner); } - pts.sort(); + pts.append(to); + Vector<Point2> uvs; Vector<Point2> points; uvs.push_back(get_relative_center()); @@ -492,6 +489,8 @@ void TextureProgressBar::_notification(int p_what) { colors.push_back(tint_progress); draw_polygon(points, colors, uvs, progress); } + + // Draw a reference cross. if (Engine::get_singleton()->is_editor_hint()) { Point2 p; @@ -502,6 +501,7 @@ void TextureProgressBar::_notification(int p_what) { } p *= get_relative_center(); + p += progress_offset; p = p.floor(); draw_line(p - Point2(8, 0), p + Point2(8, 0), Color(0.9, 0.5, 0.5), 2); draw_line(p - Point2(0, 8), p + Point2(0, 8), Color(0.9, 0.5, 0.5), 2); diff --git a/scene/gui/texture_progress_bar.h b/scene/gui/texture_progress_bar.h index c508f41387..4d3e38e006 100644 --- a/scene/gui/texture_progress_bar.h +++ b/scene/gui/texture_progress_bar.h @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ diff --git a/scene/gui/texture_rect.cpp b/scene/gui/texture_rect.cpp index 1cba88e06f..ecdf55caf0 100644 --- a/scene/gui/texture_rect.cpp +++ b/scene/gui/texture_rect.cpp @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ @@ -29,92 +29,92 @@ /*************************************************************************/ #include "texture_rect.h" + #include "core/core_string_names.h" #include "servers/rendering_server.h" void TextureRect::_notification(int p_what) { - if (p_what == NOTIFICATION_DRAW) { - if (texture.is_null()) { - return; - } - - Size2 size; - Point2 offset; - Rect2 region; - bool tile = false; - - switch (stretch_mode) { - case STRETCH_SCALE_ON_EXPAND: { - size = expand ? get_size() : texture->get_size(); - } break; - case STRETCH_SCALE: { - size = get_size(); - } break; - case STRETCH_TILE: { - size = get_size(); - tile = true; - } break; - case STRETCH_KEEP: { - size = texture->get_size(); - } break; - case STRETCH_KEEP_CENTERED: { - offset = (get_size() - texture->get_size()) / 2; - size = texture->get_size(); - } break; - case STRETCH_KEEP_ASPECT_CENTERED: - case STRETCH_KEEP_ASPECT: { - size = get_size(); - int tex_width = texture->get_width() * size.height / texture->get_height(); - int tex_height = size.height; - - if (tex_width > size.width) { - tex_width = size.width; - tex_height = texture->get_height() * tex_width / texture->get_width(); - } - - if (stretch_mode == STRETCH_KEEP_ASPECT_CENTERED) { - offset.x += (size.width - tex_width) / 2; - offset.y += (size.height - tex_height) / 2; - } - - size.width = tex_width; - size.height = tex_height; - } break; - case STRETCH_KEEP_ASPECT_COVERED: { - size = get_size(); - - Size2 tex_size = texture->get_size(); - Size2 scale_size(size.width / tex_size.width, size.height / tex_size.height); - float scale = scale_size.width > scale_size.height ? scale_size.width : scale_size.height; - Size2 scaled_tex_size = tex_size * scale; - - region.position = ((scaled_tex_size - size) / scale).abs() / 2.0f; - region.size = size / scale; - } break; - } - - Ref<AtlasTexture> p_atlas = texture; - - if (p_atlas.is_valid() && region.has_no_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; - offset.height += vflip ? p_atlas->get_margin().get_position().height * scale_size.height * 2 : 0; - } - - size.width *= hflip ? -1.0f : 1.0f; - size.height *= vflip ? -1.0f : 1.0f; - - if (region.has_no_area()) { - draw_texture_rect(texture, Rect2(offset, size), tile); - } else { - draw_texture_rect_region(texture, Rect2(offset, size), region); - } + switch (p_what) { + case NOTIFICATION_DRAW: { + if (texture.is_null()) { + return; + } + + Size2 size; + Point2 offset; + Rect2 region; + bool tile = false; + + switch (stretch_mode) { + case STRETCH_SCALE: { + size = get_size(); + } break; + case STRETCH_TILE: { + size = get_size(); + tile = true; + } break; + case STRETCH_KEEP: { + size = texture->get_size(); + } break; + case STRETCH_KEEP_CENTERED: { + offset = (get_size() - texture->get_size()) / 2; + size = texture->get_size(); + } break; + case STRETCH_KEEP_ASPECT_CENTERED: + case STRETCH_KEEP_ASPECT: { + size = get_size(); + int tex_width = texture->get_width() * size.height / texture->get_height(); + int tex_height = size.height; + + if (tex_width > size.width) { + tex_width = size.width; + tex_height = texture->get_height() * tex_width / texture->get_width(); + } + + if (stretch_mode == STRETCH_KEEP_ASPECT_CENTERED) { + offset.x += (size.width - tex_width) / 2; + offset.y += (size.height - tex_height) / 2; + } + + size.width = tex_width; + size.height = tex_height; + } break; + case STRETCH_KEEP_ASPECT_COVERED: { + size = get_size(); + + Size2 tex_size = texture->get_size(); + Size2 scale_size(size.width / tex_size.width, size.height / tex_size.height); + float scale = scale_size.width > scale_size.height ? scale_size.width : scale_size.height; + Size2 scaled_tex_size = tex_size * scale; + + region.position = ((scaled_tex_size - size) / scale).abs() / 2.0f; + region.size = size / scale; + } break; + } + + Ref<AtlasTexture> p_atlas = texture; + + if (p_atlas.is_valid() && region.has_no_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; + offset.height += vflip ? p_atlas->get_margin().get_position().height * scale_size.height * 2 : 0; + } + + size.width *= hflip ? -1.0f : 1.0f; + size.height *= vflip ? -1.0f : 1.0f; + + if (region.has_no_area()) { + draw_texture_rect(texture, Rect2(offset, size), tile); + } else { + draw_texture_rect_region(texture, Rect2(offset, size), region); + } + } break; } } Size2 TextureRect::get_minimum_size() const { - if (!expand && !texture.is_null()) { + if (!ignore_texture_size && !texture.is_null()) { return texture->get_size(); } else { return Size2(); @@ -124,8 +124,8 @@ Size2 TextureRect::get_minimum_size() const { void TextureRect::_bind_methods() { ClassDB::bind_method(D_METHOD("set_texture", "texture"), &TextureRect::set_texture); ClassDB::bind_method(D_METHOD("get_texture"), &TextureRect::get_texture); - ClassDB::bind_method(D_METHOD("set_expand", "enable"), &TextureRect::set_expand); - ClassDB::bind_method(D_METHOD("has_expand"), &TextureRect::has_expand); + ClassDB::bind_method(D_METHOD("set_ignore_texture_size", "ignore"), &TextureRect::set_ignore_texture_size); + ClassDB::bind_method(D_METHOD("get_ignore_texture_size"), &TextureRect::get_ignore_texture_size); ClassDB::bind_method(D_METHOD("set_flip_h", "enable"), &TextureRect::set_flip_h); ClassDB::bind_method(D_METHOD("is_flipped_h"), &TextureRect::is_flipped_h); ClassDB::bind_method(D_METHOD("set_flip_v", "enable"), &TextureRect::set_flip_v); @@ -134,12 +134,11 @@ void TextureRect::_bind_methods() { ClassDB::bind_method(D_METHOD("get_stretch_mode"), &TextureRect::get_stretch_mode); ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "texture", PROPERTY_HINT_RESOURCE_TYPE, "Texture2D"), "set_texture", "get_texture"); - ADD_PROPERTY(PropertyInfo(Variant::BOOL, "expand"), "set_expand", "has_expand"); - ADD_PROPERTY(PropertyInfo(Variant::INT, "stretch_mode", PROPERTY_HINT_ENUM, "Scale On Expand (Compat),Scale,Tile,Keep,Keep Centered,Keep Aspect,Keep Aspect Centered,Keep Aspect Covered"), "set_stretch_mode", "get_stretch_mode"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "ignore_texture_size"), "set_ignore_texture_size", "get_ignore_texture_size"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "stretch_mode", PROPERTY_HINT_ENUM, "Scale,Tile,Keep,Keep Centered,Keep Aspect,Keep Aspect Centered,Keep Aspect Covered"), "set_stretch_mode", "get_stretch_mode"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "flip_h"), "set_flip_h", "is_flipped_h"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "flip_v"), "set_flip_v", "is_flipped_v"); - BIND_ENUM_CONSTANT(STRETCH_SCALE_ON_EXPAND); BIND_ENUM_CONSTANT(STRETCH_SCALE); BIND_ENUM_CONSTANT(STRETCH_TILE); BIND_ENUM_CONSTANT(STRETCH_KEEP); @@ -152,7 +151,7 @@ void TextureRect::_bind_methods() { void TextureRect::_texture_changed() { if (texture.is_valid()) { update(); - minimum_size_changed(); + update_minimum_size(); } } @@ -172,21 +171,21 @@ void TextureRect::set_texture(const Ref<Texture2D> &p_tex) { } update(); - minimum_size_changed(); + update_minimum_size(); } Ref<Texture2D> TextureRect::get_texture() const { return texture; } -void TextureRect::set_expand(bool p_expand) { - expand = p_expand; +void TextureRect::set_ignore_texture_size(bool p_ignore) { + ignore_texture_size = p_ignore; update(); - minimum_size_changed(); + update_minimum_size(); } -bool TextureRect::has_expand() const { - return expand; +bool TextureRect::get_ignore_texture_size() const { + return ignore_texture_size; } void TextureRect::set_stretch_mode(StretchMode p_mode) { diff --git a/scene/gui/texture_rect.h b/scene/gui/texture_rect.h index 0f93d5732f..7d667b25a8 100644 --- a/scene/gui/texture_rect.h +++ b/scene/gui/texture_rect.h @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ @@ -38,7 +38,6 @@ class TextureRect : public Control { public: enum StretchMode { - STRETCH_SCALE_ON_EXPAND, //default, for backwards compatibility STRETCH_SCALE, STRETCH_TILE, STRETCH_KEEP, @@ -49,11 +48,11 @@ public: }; private: - bool expand = false; + bool ignore_texture_size = false; bool hflip = false; bool vflip = false; Ref<Texture2D> texture; - StretchMode stretch_mode = STRETCH_SCALE_ON_EXPAND; + StretchMode stretch_mode = STRETCH_SCALE; void _texture_changed(); @@ -66,8 +65,8 @@ public: void set_texture(const Ref<Texture2D> &p_tex); Ref<Texture2D> get_texture() const; - void set_expand(bool p_expand); - bool has_expand() const; + void set_ignore_texture_size(bool p_ignore); + bool get_ignore_texture_size() const; void set_stretch_mode(StretchMode p_mode); StretchMode get_stretch_mode() const; diff --git a/scene/gui/tree.cpp b/scene/gui/tree.cpp index 1245a37c4d..ff8d2b88b1 100644 --- a/scene/gui/tree.cpp +++ b/scene/gui/tree.cpp @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ @@ -37,10 +37,9 @@ #include "core/os/os.h" #include "core/string/print_string.h" #include "core/string/translation.h" +#include "scene/gui/box_container.h" #include "scene/main/window.h" -#include "box_container.h" - #include <limits.h> Size2 TreeItem::Cell::get_icon_size() const { @@ -198,6 +197,65 @@ bool TreeItem::is_indeterminate(int p_column) const { return cells[p_column].indeterminate; } +void TreeItem::propagate_check(int p_column, bool p_emit_signal) { + bool ch = cells[p_column].checked; + + if (p_emit_signal) { + tree->emit_signal(SNAME("check_propagated_to_item"), this, p_column); + } + _propagate_check_through_children(p_column, ch, p_emit_signal); + _propagate_check_through_parents(p_column, p_emit_signal); +} + +void TreeItem::_propagate_check_through_children(int p_column, bool p_checked, bool p_emit_signal) { + TreeItem *current = get_first_child(); + while (current) { + current->set_checked(p_column, p_checked); + if (p_emit_signal) { + current->tree->emit_signal(SNAME("check_propagated_to_item"), current, p_column); + } + current->_propagate_check_through_children(p_column, p_checked, p_emit_signal); + current = current->get_next(); + } +} + +void TreeItem::_propagate_check_through_parents(int p_column, bool p_emit_signal) { + TreeItem *current = get_parent(); + if (!current) { + return; + } + + bool all_unchecked_and_not_indeterminate = true; + bool any_unchecked_or_indeterminate = false; + + TreeItem *child_item = current->get_first_child(); + while (child_item) { + if (!child_item->is_checked(p_column)) { + any_unchecked_or_indeterminate = true; + if (child_item->is_indeterminate(p_column)) { + all_unchecked_and_not_indeterminate = false; + break; + } + } else { + all_unchecked_and_not_indeterminate = false; + } + child_item = child_item->get_next(); + } + + if (all_unchecked_and_not_indeterminate) { + current->set_checked(p_column, false); + } else if (any_unchecked_or_indeterminate) { + current->set_indeterminate(p_column, true); + } else { + current->set_checked(p_column, true); + } + + if (p_emit_signal) { + current->tree->emit_signal(SNAME("check_propagated_to_item"), current, p_column); + } + current->_propagate_check_through_parents(p_column, p_emit_signal); +} + void TreeItem::set_text(int p_column, String p_text) { ERR_FAIL_INDEX(p_column, cells.size()); cells.write[p_column].text = p_text; @@ -496,8 +554,9 @@ void TreeItem::uncollapse_tree() { void TreeItem::set_custom_minimum_height(int p_height) { custom_min_height = p_height; - for (Cell &c : cells) + for (Cell &c : cells) { c.cached_minimum_size_dirty = true; + } _changed_notify(); } @@ -845,10 +904,16 @@ String TreeItem::get_button_tooltip(int p_column, int p_idx) const { return cells[p_column].buttons[p_idx].tooltip; } +int TreeItem::get_button_id(int p_column, int p_idx) const { + ERR_FAIL_INDEX_V(p_column, cells.size(), -1); + ERR_FAIL_INDEX_V(p_idx, cells[p_column].buttons.size(), -1); + return cells[p_column].buttons[p_idx].id; +} + void TreeItem::erase_button(int p_column, int p_idx) { ERR_FAIL_INDEX(p_column, cells.size()); ERR_FAIL_INDEX(p_idx, cells[p_column].buttons.size()); - cells.write[p_column].buttons.remove(p_idx); + cells.write[p_column].buttons.remove_at(p_idx); _changed_notify(p_column); } @@ -1002,18 +1067,18 @@ bool TreeItem::is_custom_set_as_button(int p_column) const { return cells[p_column].custom_button; } -void TreeItem::set_text_align(int p_column, TextAlign p_align) { +void TreeItem::set_text_alignment(int p_column, HorizontalAlignment p_alignment) { ERR_FAIL_INDEX(p_column, cells.size()); - cells.write[p_column].text_align = p_align; + cells.write[p_column].text_alignment = p_alignment; cells.write[p_column].cached_minimum_size_dirty = true; _changed_notify(p_column); } -TreeItem::TextAlign TreeItem::get_text_align(int p_column) const { - ERR_FAIL_INDEX_V(p_column, cells.size(), ALIGN_LEFT); - return cells[p_column].text_align; +HorizontalAlignment TreeItem::get_text_alignment(int p_column) const { + ERR_FAIL_INDEX_V(p_column, cells.size(), HORIZONTAL_ALIGNMENT_LEFT); + return cells[p_column].text_alignment; } void TreeItem::set_expand_right(int p_column, bool p_enable) { @@ -1033,8 +1098,9 @@ bool TreeItem::get_expand_right(int p_column) const { void TreeItem::set_disable_folding(bool p_disable) { disable_folding = p_disable; - for (Cell &c : cells) + for (Cell &c : cells) { c.cached_minimum_size_dirty = true; + } _changed_notify(0); } @@ -1120,7 +1186,7 @@ void recursive_call_aux(TreeItem *p_item, const StringName &p_method, const Vari if (!p_item) { return; } - p_item->call(p_method, p_args, p_argcount, r_error); + p_item->callp(p_method, p_args, p_argcount, r_error); TreeItem *c = p_item->get_first_child(); while (c) { recursive_call_aux(c, p_method, p_args, p_argcount, r_error); @@ -1141,6 +1207,8 @@ void TreeItem::_bind_methods() { ClassDB::bind_method(D_METHOD("is_checked", "column"), &TreeItem::is_checked); ClassDB::bind_method(D_METHOD("is_indeterminate", "column"), &TreeItem::is_indeterminate); + ClassDB::bind_method(D_METHOD("propagate_check", "column", "emit_signal"), &TreeItem::propagate_check, DEFVAL(true)); + ClassDB::bind_method(D_METHOD("set_text", "column", "text"), &TreeItem::set_text); ClassDB::bind_method(D_METHOD("get_text", "column"), &TreeItem::get_text); @@ -1220,9 +1288,11 @@ 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", "button_idx", "disabled", "tooltip"), &TreeItem::add_button, DEFVAL(-1), DEFVAL(false), DEFVAL("")); + 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("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_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); ClassDB::bind_method(D_METHOD("set_button", "column", "button_idx", "button"), &TreeItem::set_button); ClassDB::bind_method(D_METHOD("erase_button", "column", "button_idx"), &TreeItem::erase_button); @@ -1231,8 +1301,8 @@ void TreeItem::_bind_methods() { 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_text_align", "column", "text_align"), &TreeItem::set_text_align); - ClassDB::bind_method(D_METHOD("get_text_align", "column"), &TreeItem::get_text_align); + 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); ClassDB::bind_method(D_METHOD("set_expand_right", "column", "enable"), &TreeItem::set_expand_right); ClassDB::bind_method(D_METHOD("get_expand_right", "column"), &TreeItem::get_expand_right); @@ -1256,10 +1326,10 @@ void TreeItem::_bind_methods() { ClassDB::bind_method(D_METHOD("get_children"), &TreeItem::get_children); ClassDB::bind_method(D_METHOD("get_index"), &TreeItem::get_index); - ClassDB::bind_method(D_METHOD("move_before", "item"), &TreeItem::_move_before); - ClassDB::bind_method(D_METHOD("move_after", "item"), &TreeItem::_move_after); + ClassDB::bind_method(D_METHOD("move_before", "item"), &TreeItem::move_before); + ClassDB::bind_method(D_METHOD("move_after", "item"), &TreeItem::move_after); - ClassDB::bind_method(D_METHOD("remove_child", "child"), &TreeItem::_remove_child); + ClassDB::bind_method(D_METHOD("remove_child", "child"), &TreeItem::remove_child); { MethodInfo mi; @@ -1278,10 +1348,6 @@ void TreeItem::_bind_methods() { BIND_ENUM_CONSTANT(CELL_MODE_RANGE); BIND_ENUM_CONSTANT(CELL_MODE_ICON); BIND_ENUM_CONSTANT(CELL_MODE_CUSTOM); - - BIND_ENUM_CONSTANT(ALIGN_LEFT); - BIND_ENUM_CONSTANT(ALIGN_CENTER); - BIND_ENUM_CONSTANT(ALIGN_RIGHT); } void TreeItem::clear_children() { @@ -1477,16 +1543,17 @@ void Tree::draw_item_rect(TreeItem::Cell &p_cell, const Rect2i &p_rect, const Co } w += ts.width; - switch (p_cell.text_align) { - case TreeItem::ALIGN_LEFT: + switch (p_cell.text_alignment) { + case HORIZONTAL_ALIGNMENT_FILL: + case HORIZONTAL_ALIGNMENT_LEFT: { if (rtl) { rect.position.x += MAX(0, (rect.size.width - w)); } - break; - case TreeItem::ALIGN_CENTER: + } break; + case HORIZONTAL_ALIGNMENT_CENTER: rect.position.x += MAX(0, (rect.size.width - w) / 2); break; - case TreeItem::ALIGN_RIGHT: + case HORIZONTAL_ALIGNMENT_RIGHT: if (!rtl) { rect.position.x += MAX(0, (rect.size.width - w)); } @@ -1539,7 +1606,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].opentype_features, (columns[p_col].language != "") ? columns[p_col].language : TranslationServer::get_singleton()->get_tool_locale()); + columns.write[p_col].text_buf->add_string(columns[p_col].title, cache.font, cache.font_size, columns[p_col].opentype_features, !columns[p_col].language.is_empty() ? columns[p_col].language : TranslationServer::get_singleton()->get_tool_locale()); } void Tree::update_item_cell(TreeItem *p_item, int p_col) { @@ -1547,7 +1614,7 @@ void Tree::update_item_cell(TreeItem *p_item, int p_col) { p_item->cells.write[p_col].text_buf->clear(); if (p_item->cells[p_col].mode == TreeItem::CELL_MODE_RANGE) { - if (p_item->cells[p_col].text != "") { + if (!p_item->cells[p_col].text.is_empty()) { if (!p_item->cells[p_col].editable) { return; } @@ -1574,7 +1641,7 @@ void Tree::update_item_cell(TreeItem *p_item, int p_col) { valtext = p_item->cells[p_col].text; } - if (p_item->cells[p_col].suffix != String()) { + if (!p_item->cells[p_col].suffix.is_empty()) { valtext += " " + p_item->cells[p_col].suffix; } @@ -1597,7 +1664,7 @@ void Tree::update_item_cell(TreeItem *p_item, int p_col) { } else { font_size = cache.font_size; } - p_item->cells.write[p_col].text_buf->add_string(valtext, font, font_size, p_item->cells[p_col].opentype_features, (p_item->cells[p_col].language != "") ? p_item->cells[p_col].language : TranslationServer::get_singleton()->get_tool_locale()); + p_item->cells.write[p_col].text_buf->add_string(valtext, font, font_size, p_item->cells[p_col].opentype_features, !p_item->cells[p_col].language.is_empty() ? p_item->cells[p_col].language : TranslationServer::get_singleton()->get_tool_locale()); 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)); p_item->cells.write[p_col].dirty = false; } @@ -1634,8 +1701,7 @@ int Tree::draw_item(const Point2i &p_pos, const Point2 &p_draw_ofs, const Size2 bool skip = (p_item == root && hide_root); if (!skip && (p_pos.y + label_h - cache.offset.y) > 0) { - //draw separation. - //if (p_item->get_parent()!=root || !hide_root) + // Draw separation. ERR_FAIL_COND_V(cache.font.is_null(), -1); @@ -1663,7 +1729,7 @@ int Tree::draw_item(const Point2i &p_pos, const Point2 &p_draw_ofs, const Size2 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 == "" && p_item->cells[i + plus].icon.is_null()) { + 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()) { w += get_column_width(i + plus); plus++; skip2++; @@ -1860,7 +1926,7 @@ int Tree::draw_item(const Point2i &p_pos, const Point2 &p_draw_ofs, const Size2 } break; case TreeItem::CELL_MODE_RANGE: { - if (p_item->cells[i].text != "") { + if (!p_item->cells[i].text.is_empty()) { if (!p_item->cells[i].editable) { break; } @@ -1955,7 +2021,7 @@ 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(MOUSE_BUTTON_LEFT)) { + if (Input::get_singleton()->is_mouse_button_pressed(MouseButton::LEFT)) { draw_style_box(cache.custom_button_pressed, ir); } else { draw_style_box(cache.custom_button_hover, ir); @@ -2192,14 +2258,11 @@ void Tree::select_single_item(TreeItem *p_selected, TreeItem *p_current, int p_c emit_signal(SNAME("item_selected")); emitted_row = true; } - /* - if (p_col==i) - p_current->selected_signal.call(p_col); - */ - } else if (c.selected) { - c.selected = false; - //p_current->deselected_signal.call(p_col); + if (p_selected != p_current) { + // Deselect other rows. + c.selected = false; + } } } else if (select_mode == SELECT_SINGLE || select_mode == SELECT_MULTI) { if (!r_in_range && &selected_cell == &c) { @@ -2256,7 +2319,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(MOUSE_BUTTON_LEFT)) { + 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(); if (show_column_titles) { pos.y -= _get_title_button_height(); @@ -2284,7 +2347,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, MOUSE_BUTTON_LEFT, mb); + propagate_mouse_event(pos + cache.offset, 0, 0, x_limit + cache.offset.width, false, root, MouseButton::LEFT, mb); blocked--; if (range_click_timer->is_one_shot()) { @@ -2307,7 +2370,7 @@ void Tree::_range_click_timeout() { } } -int Tree::propagate_mouse_event(const Point2i &p_pos, int x_ofs, int y_ofs, int x_limit, bool p_double_click, TreeItem *p_item, int p_button, const Ref<InputEventWithModifiers> &p_mod) { +int Tree::propagate_mouse_event(const Point2i &p_pos, int x_ofs, int y_ofs, int x_limit, bool p_double_click, TreeItem *p_item, MouseButton p_button, const Ref<InputEventWithModifiers> &p_mod) { int item_h = compute_item_height(p_item) + cache.vseparation; bool skip = (p_item == root && hide_root); @@ -2319,12 +2382,9 @@ 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_pos.x >= x_ofs && p_pos.x < (x_ofs + cache.item_margin))) { - if (p_item->first_child) { - p_item->set_collapsed(!p_item->is_collapsed()); - } - - return -1; //handled! + if (!p_item->disable_folding && !hide_folding && p_item->first_child && (p_pos.x >= x_ofs && p_pos.x < (x_ofs + cache.item_margin))) { + p_item->set_collapsed(!p_item->is_collapsed()); + return -1; } int x = p_pos.x; @@ -2340,7 +2400,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 == "" && p_item->cells[i + plus].icon.is_null()) { + 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 += get_column_width(i + plus); plus++; @@ -2427,11 +2487,11 @@ int Tree::propagate_mouse_event(const Point2i &p_pos, int x_ofs, int y_ofs, int col_width -= w + cache.button_margin; } - if (p_button == MOUSE_BUTTON_LEFT || (p_button == MOUSE_BUTTON_RIGHT && allow_rmb_select)) { + if (p_button == MouseButton::LEFT || (p_button == MouseButton::RIGHT && allow_rmb_select)) { /* process selection */ if (p_double_click && (!c.editable || c.mode == TreeItem::CELL_MODE_CUSTOM || c.mode == TreeItem::CELL_MODE_ICON /*|| c.mode==TreeItem::CELL_MODE_CHECK*/)) { //it's confusing for check - + // Emits the "item_activated" signal. propagate_mouse_activated = true; incr_search.clear(); @@ -2439,10 +2499,10 @@ int Tree::propagate_mouse_event(const Point2i &p_pos, int x_ofs, int y_ofs, int } if (select_mode == SELECT_MULTI && p_mod->is_command_pressed() && c.selectable) { - if (!c.selected || p_button == MOUSE_BUTTON_RIGHT) { + if (!c.selected || p_button == MouseButton::RIGHT) { p_item->select(col); emit_signal(SNAME("multi_selected"), p_item, col, true); - if (p_button == MOUSE_BUTTON_RIGHT) { + if (p_button == MouseButton::RIGHT) { emit_signal(SNAME("item_rmb_selected"), get_local_mouse_position()); } @@ -2459,21 +2519,21 @@ int Tree::propagate_mouse_event(const Point2i &p_pos, int x_ofs, int y_ofs, int bool inrange = false; select_single_item(p_item, root, col, selected_item, &inrange); - if (p_button == MOUSE_BUTTON_RIGHT) { + if (p_button == MouseButton::RIGHT) { emit_signal(SNAME("item_rmb_selected"), get_local_mouse_position()); } } else { int icount = _count_selected_items(root); - if (select_mode == SELECT_MULTI && icount > 1 && p_button != MOUSE_BUTTON_RIGHT) { + if (select_mode == SELECT_MULTI && icount > 1 && p_button != MouseButton::RIGHT) { single_select_defer = p_item; single_select_defer_column = col; } else { - if (p_button != MOUSE_BUTTON_RIGHT || !c.selected) { + if (p_button != MouseButton::RIGHT || !c.selected) { select_single_item(p_item, root, col); } - if (p_button == MOUSE_BUTTON_RIGHT) { + if (p_button == MouseButton::RIGHT) { emit_signal(SNAME("item_rmb_selected"), get_local_mouse_position()); } } @@ -2523,7 +2583,7 @@ int Tree::propagate_mouse_event(const Point2i &p_pos, int x_ofs, int y_ofs, int } break; case TreeItem::CELL_MODE_RANGE: { - if (c.text != "") { + if (!c.text.is_empty()) { //if (x >= (get_column_width(col)-item_h/2)) { popup_menu->clear(); for (int i = 0; i < c.text.get_slice_count(","); i++) { @@ -2532,7 +2592,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_global_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) - cache.offset); popup_menu->popup(); popup_edited_item = p_item; popup_edited_item_col = col; @@ -2543,7 +2603,7 @@ int Tree::propagate_mouse_event(const Point2i &p_pos, int x_ofs, int y_ofs, int /* touching the combo */ bool up = p_pos.y < (item_h / 2); - if (p_button == MOUSE_BUTTON_LEFT) { + if (p_button == MouseButton::LEFT) { if (range_click_timer->get_time_left() == 0) { range_item_last = p_item; range_up_last = up; @@ -2560,13 +2620,13 @@ int Tree::propagate_mouse_event(const Point2i &p_pos, int x_ofs, int y_ofs, int item_edited(col, p_item); - } else if (p_button == MOUSE_BUTTON_RIGHT) { + } else if (p_button == MouseButton::RIGHT) { p_item->set_range(col, (up ? c.max : c.min)); item_edited(col, p_item); - } else if (p_button == MOUSE_BUTTON_WHEEL_UP) { + } else if (p_button == MouseButton::WHEEL_UP) { p_item->set_range(col, c.val + c.step); item_edited(col, p_item); - } else if (p_button == MOUSE_BUTTON_WHEEL_DOWN) { + } else if (p_button == MouseButton::WHEEL_DOWN) { p_item->set_range(col, c.val - c.step); item_edited(col, p_item); } @@ -2599,14 +2659,14 @@ int Tree::propagate_mouse_event(const Point2i &p_pos, int x_ofs, int y_ofs, int } if (!p_item->cells[col].custom_button || !on_arrow) { - item_edited(col, p_item, p_button == MOUSE_BUTTON_LEFT); + item_edited(col, p_item, p_button == MouseButton::LEFT); } click_handled = true; return -1; } break; }; - if (!bring_up_editor || p_button != MOUSE_BUTTON_LEFT) { + if (!bring_up_editor || p_button != MouseButton::LEFT) { return -1; } @@ -2646,7 +2706,7 @@ int Tree::propagate_mouse_event(const Point2i &p_pos, int x_ofs, int y_ofs, int item_h += child_h; } } - if (p_item == root && p_button == MOUSE_BUTTON_RIGHT) { + if (p_item == root && p_button == MouseButton::RIGHT) { emit_signal(SNAME("empty_rmb"), get_local_mouse_position()); } } @@ -2655,9 +2715,9 @@ int Tree::propagate_mouse_event(const Point2i &p_pos, int x_ofs, int y_ofs, int } void Tree::_text_editor_modal_close() { - if (Input::get_singleton()->is_key_pressed(KEY_ESCAPE) || - Input::get_singleton()->is_key_pressed(KEY_KP_ENTER) || - Input::get_singleton()->is_key_pressed(KEY_ENTER)) { + if (Input::get_singleton()->is_key_pressed(Key::ESCAPE) || + Input::get_singleton()->is_key_pressed(Key::KP_ENTER) || + Input::get_singleton()->is_key_pressed(Key::ENTER)) { return; } @@ -3048,7 +3108,7 @@ void Tree::gui_input(const Ref<InputEvent> &p_event) { return; } else { - if (k->get_keycode() != KEY_SHIFT) { + if (k->get_keycode() != Key::SHIFT) { last_keypress = 0; } } @@ -3164,7 +3224,7 @@ void Tree::gui_input(const Ref<InputEvent> &p_event) { } else { const TreeItem::Cell &c = popup_edited_item->cells[popup_edited_item_col]; float diff_y = -mm->get_relative().y; - diff_y = Math::pow(ABS(diff_y), 1.8f) * SGN(diff_y); + diff_y = Math::pow(ABS(diff_y), 1.8f) * SIGN(diff_y); diff_y *= 0.1; range_drag_base = CLAMP(range_drag_base + c.step * diff_y, c.min, c.max); popup_edited_item->set_range(popup_edited_item_col, range_drag_base); @@ -3175,7 +3235,7 @@ void Tree::gui_input(const Ref<InputEvent> &p_event) { if (drag_touching && !drag_touching_deaccel) { drag_accum -= mm->get_relative().y; v_scroll->set_value(drag_from + drag_accum); - drag_speed = -mm->get_speed().y; + drag_speed = -mm->get_velocity().y; } } @@ -3189,7 +3249,7 @@ void Tree::gui_input(const Ref<InputEvent> &p_event) { bool rtl = is_layout_rtl(); if (!b->is_pressed()) { - if (b->get_button_index() == MOUSE_BUTTON_LEFT) { + if (b->get_button_index() == MouseButton::LEFT) { Point2 pos = b->get_position(); if (rtl) { pos.x = get_size().width - pos.x; @@ -3226,8 +3286,21 @@ void Tree::gui_input(const Ref<InputEvent> &p_event) { } else { Rect2 rect = get_selected()->get_meta("__focus_rect"); Point2 mpos = b->get_position(); + int icon_size_x = 0; + Ref<Texture2D> icon = get_selected()->get_icon(selected_col); + if (icon.is_valid()) { + Rect2i icon_region = get_selected()->get_icon_region(selected_col); + if (icon_region == Rect2i()) { + icon_size_x = icon->get_width(); + } else { + icon_size_x = icon_region.size.width; + } + } + // Icon is treated as if it is outside of the rect so that double clicking on it will emit the item_double_clicked signal. if (rtl) { - mpos.x = get_size().width - mpos.x; + mpos.x = get_size().width - (mpos.x + icon_size_x); + } else { + mpos.x -= icon_size_x; } if (rect.has_point(mpos)) { if (!edit_selected()) { @@ -3270,8 +3343,8 @@ void Tree::gui_input(const Ref<InputEvent> &p_event) { } switch (b->get_button_index()) { - case MOUSE_BUTTON_RIGHT: - case MOUSE_BUTTON_LEFT: { + case MouseButton::RIGHT: + case MouseButton::LEFT: { Ref<StyleBox> bg = cache.bg; Point2 pos = b->get_position(); @@ -3284,7 +3357,7 @@ void Tree::gui_input(const Ref<InputEvent> &p_event) { pos.y -= _get_title_button_height(); if (pos.y < 0) { - if (b->get_button_index() == MOUSE_BUTTON_LEFT) { + if (b->get_button_index() == MouseButton::LEFT) { pos.x += cache.offset.x; int len = 0; for (int i = 0; i < columns.size(); i++) { @@ -3302,7 +3375,7 @@ void Tree::gui_input(const Ref<InputEvent> &p_event) { } } if (!root || (!root->get_first_child() && hide_root)) { - if (b->get_button_index() == MOUSE_BUTTON_RIGHT && allow_rmb_select) { + if (b->get_button_index() == MouseButton::RIGHT && allow_rmb_select) { emit_signal(SNAME("empty_tree_rmb_selected"), get_local_mouse_position()); } break; @@ -3329,7 +3402,7 @@ void Tree::gui_input(const Ref<InputEvent> &p_event) { } } - if (b->get_button_index() == MOUSE_BUTTON_RIGHT) { + if (b->get_button_index() == MouseButton::RIGHT) { break; } @@ -3352,7 +3425,7 @@ void Tree::gui_input(const Ref<InputEvent> &p_event) { set_physics_process_internal(true); } - if (b->get_button_index() == MOUSE_BUTTON_LEFT) { + if (b->get_button_index() == MouseButton::LEFT) { if (get_item_at_position(b->get_position()) == nullptr && !b->is_shift_pressed() && !b->is_ctrl_pressed() && !b->is_command_pressed()) { emit_signal(SNAME("nothing_selected")); } @@ -3365,7 +3438,7 @@ void Tree::gui_input(const Ref<InputEvent> &p_event) { } } break; - case MOUSE_BUTTON_WHEEL_UP: { + case MouseButton::WHEEL_UP: { double prev_value = v_scroll->get_value(); v_scroll->set_value(v_scroll->get_value() - v_scroll->get_page() * b->get_factor() / 8); if (v_scroll->get_value() != prev_value) { @@ -3373,7 +3446,7 @@ void Tree::gui_input(const Ref<InputEvent> &p_event) { } } break; - case MOUSE_BUTTON_WHEEL_DOWN: { + case MouseButton::WHEEL_DOWN: { double prev_value = v_scroll->get_value(); v_scroll->set_value(v_scroll->get_value() + v_scroll->get_page() * b->get_factor() / 8); if (v_scroll->get_value() != prev_value) { @@ -3433,7 +3506,7 @@ bool Tree::edit_selected() { item_edited(col, s); return true; - } else if (c.mode == TreeItem::CELL_MODE_RANGE && c.text != "") { + } else if (c.mode == TreeItem::CELL_MODE_RANGE && !c.text.is_empty()) { popup_menu->clear(); for (int i = 0; i < c.text.get_slice_count(","); i++) { String s2 = c.text.get_slicec(',', i); @@ -3441,7 +3514,7 @@ bool Tree::edit_selected() { } popup_menu->set_size(Size2(rect.size.width, 0)); - popup_menu->set_position(get_global_position() + rect.position + Point2i(0, rect.size.height)); + popup_menu->set_position(get_screen_position() + rect.position + Point2i(0, rect.size.height)); popup_menu->popup(); popup_edited_item = s; popup_edited_item_col = col; @@ -3569,178 +3642,187 @@ int Tree::_get_title_button_height() const { } void Tree::_notification(int p_what) { - if (p_what == NOTIFICATION_FOCUS_ENTER) { - if (get_viewport()) { - focus_in_id = get_viewport()->get_processed_events_count(); - } - } - if (p_what == NOTIFICATION_MOUSE_EXIT) { - if (cache.hover_type != Cache::CLICK_NONE) { - cache.hover_type = Cache::CLICK_NONE; - update(); - } - } - - if (p_what == NOTIFICATION_VISIBILITY_CHANGED) { - drag_touching = false; - } + switch (p_what) { + case NOTIFICATION_FOCUS_ENTER: { + if (get_viewport()) { + focus_in_id = get_viewport()->get_processed_events_count(); + } + } break; - if (p_what == NOTIFICATION_ENTER_TREE) { - update_cache(); - } - if (p_what == NOTIFICATION_DRAG_END) { - drop_mode_flags = 0; - scrolling = false; - set_physics_process_internal(false); - update(); - } - if (p_what == NOTIFICATION_DRAG_BEGIN) { - single_select_defer = nullptr; - if (cache.scroll_speed > 0) { - scrolling = true; - set_physics_process_internal(true); - } - } - if (p_what == NOTIFICATION_INTERNAL_PHYSICS_PROCESS) { - if (drag_touching) { - if (drag_touching_deaccel) { - float pos = v_scroll->get_value(); - pos += drag_speed * get_physics_process_delta_time(); + case NOTIFICATION_MOUSE_EXIT: { + if (cache.hover_type != Cache::CLICK_NONE) { + cache.hover_type = Cache::CLICK_NONE; + update(); + } + } break; - bool turnoff = false; - if (pos < 0) { - pos = 0; - turnoff = true; - set_physics_process_internal(false); - drag_touching = false; - drag_touching_deaccel = false; - } - if (pos > (v_scroll->get_max() - v_scroll->get_page())) { - pos = v_scroll->get_max() - v_scroll->get_page(); - turnoff = true; - } + case NOTIFICATION_VISIBILITY_CHANGED: { + drag_touching = false; + } break; - v_scroll->set_value(pos); - float sgn = drag_speed < 0 ? -1 : 1; - float val = Math::abs(drag_speed); - val -= 1000 * get_physics_process_delta_time(); + case NOTIFICATION_ENTER_TREE: { + update_cache(); + } break; - if (val < 0) { - turnoff = true; - } - drag_speed = sgn * val; + case NOTIFICATION_DRAG_END: { + drop_mode_flags = 0; + scrolling = false; + set_physics_process_internal(false); + update(); + } break; - if (turnoff) { - set_physics_process_internal(false); - drag_touching = false; - drag_touching_deaccel = false; - } + case NOTIFICATION_DRAG_BEGIN: { + single_select_defer = nullptr; + if (cache.scroll_speed > 0) { + scrolling = true; + set_physics_process_internal(true); } - } + } break; - Point2 mouse_position = get_viewport()->get_mouse_position() - get_global_position(); - if (scrolling && get_rect().grow(cache.scroll_border).has_point(mouse_position)) { - Point2 point; + case NOTIFICATION_INTERNAL_PHYSICS_PROCESS: { + if (drag_touching) { + if (drag_touching_deaccel) { + float pos = v_scroll->get_value(); + pos += drag_speed * get_physics_process_delta_time(); - 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); - } + bool turnoff = false; + if (pos < 0) { + pos = 0; + turnoff = true; + set_physics_process_internal(false); + drag_touching = false; + drag_touching_deaccel = false; + } + if (pos > (v_scroll->get_max() - v_scroll->get_page())) { + pos = v_scroll->get_max() - v_scroll->get_page(); + turnoff = true; + } - 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); + v_scroll->set_value(pos); + float sgn = drag_speed < 0 ? -1 : 1; + float val = Math::abs(drag_speed); + val -= 1000 * get_physics_process_delta_time(); + + if (val < 0) { + turnoff = true; + } + drag_speed = sgn * val; + + if (turnoff) { + set_physics_process_internal(false); + drag_touching = false; + drag_touching_deaccel = false; + } + } } - point *= cache.scroll_speed * get_physics_process_delta_time(); - point += get_scroll(); - h_scroll->set_value(point.x); - v_scroll->set_value(point.y); - } - } + Point2 mouse_position = get_viewport()->get_mouse_position() - get_global_position(); + if (scrolling && get_rect().grow(cache.scroll_border).has_point(mouse_position)) { + Point2 point; - if (p_what == NOTIFICATION_DRAW) { - update_cache(); - update_scrollbars(); - RID ci = get_canvas_item(); + 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); + } - 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")); + 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); + } - Point2 draw_ofs; - draw_ofs += bg->get_offset(); - Size2 draw_size = get_size() - bg->get_minimum_size(); - if (h_scroll->is_visible()) { - draw_size.width -= h_scroll->get_minimum_size().width; - } + point *= cache.scroll_speed * get_physics_process_delta_time(); + point += get_scroll(); + h_scroll->set_value(point.x); + v_scroll->set_value(point.y); + } + } break; - bg->draw(ci, Rect2(Point2(), get_size())); + case NOTIFICATION_DRAW: { + update_cache(); + 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")); + + Point2 draw_ofs; + draw_ofs += bg->get_offset(); + Size2 draw_size = get_size() - bg->get_minimum_size(); + if (h_scroll->is_visible()) { + draw_size.width -= h_scroll->get_minimum_size().width; + } - int tbh = _get_title_button_height(); + bg->draw(ci, Rect2(Point2(), get_size())); - draw_ofs.y += tbh; - draw_size.y -= tbh; + int tbh = _get_title_button_height(); - cache.rtl = is_layout_rtl(); + draw_ofs.y += tbh; + draw_size.y -= tbh; - if (root && get_size().x > 0 && get_size().y > 0) { - draw_item(Point2(), draw_ofs, draw_size, root); - } + cache.rtl = is_layout_rtl(); - if (show_column_titles) { - //title buttons - int ofs2 = cache.bg->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); - if (cache.rtl) { - tbrect.position.x = get_size().width - tbrect.size.x - tbrect.position.x; - } - sb->draw(ci, tbrect); - ofs2 += tbrect.size.width; - //text - int clip_w = tbrect.size.width - sb->get_minimum_size().width; - 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); - } - columns[i].text_buf->draw(ci, text_pos, cache.title_button_color); + if (root && get_size().x > 0 && get_size().y > 0) { + draw_item(Point2(), draw_ofs, draw_size, root); } - } - // Draw the background 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())); - RenderingServer::get_singleton()->canvas_item_add_clip_ignore(ci, false); - } - } + if (show_column_titles) { + //title buttons + int ofs2 = cache.bg->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); + if (cache.rtl) { + tbrect.position.x = get_size().width - tbrect.size.x - tbrect.position.x; + } + sb->draw(ci, tbrect); + ofs2 += tbrect.size.width; + //text + int clip_w = tbrect.size.width - sb->get_minimum_size().width; + 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); + } + columns[i].text_buf->draw(ci, text_pos, cache.title_button_color); + } + } - if (p_what == NOTIFICATION_THEME_CHANGED || p_what == NOTIFICATION_LAYOUT_DIRECTION_CHANGED || p_what == NOTIFICATION_TRANSLATION_CHANGED) { - update_cache(); - _update_all(); - } + // Draw the background 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())); + RenderingServer::get_singleton()->canvas_item_add_clip_ignore(ci, false); + } + } break; - if (p_what == NOTIFICATION_RESIZED || p_what == NOTIFICATION_TRANSFORM_CHANGED) { - if (popup_edited_item != nullptr) { - Rect2 rect = popup_edited_item->get_meta("__focus_rect"); - Vector2 ofs(0, (text_editor->get_size().height - rect.size.height) / 2); - Point2i textedpos = get_global_position() + rect.position - ofs; + case NOTIFICATION_THEME_CHANGED: + case NOTIFICATION_LAYOUT_DIRECTION_CHANGED: + case NOTIFICATION_TRANSLATION_CHANGED: { + update_cache(); + _update_all(); + } break; - if (cache.text_editor_position != textedpos) { - cache.text_editor_position = textedpos; - text_editor->set_position(textedpos); - value_editor->set_position(textedpos + Point2i(0, text_editor->get_size().height)); + case NOTIFICATION_RESIZED: + case NOTIFICATION_TRANSFORM_CHANGED: { + if (popup_edited_item != nullptr) { + Rect2 rect = popup_edited_item->get_meta("__focus_rect"); + Vector2 ofs(0, (text_editor->get_size().height - rect.size.height) / 2); + Point2i textedpos = get_global_position() + rect.position - ofs; + + if (cache.text_editor_position != textedpos) { + cache.text_editor_position = textedpos; + text_editor->set_position(textedpos); + value_editor->set_position(textedpos + Point2i(0, text_editor->get_size().height)); + } } - } + } break; } } @@ -3990,10 +4072,6 @@ int Tree::get_edited_column() const { } TreeItem *Tree::get_next_selected(TreeItem *p_item) { - /* - if (!p_item) - return nullptr; - */ if (!root) { return nullptr; } @@ -4350,27 +4428,35 @@ Point2 Tree::get_scroll() const { return ofs; } -void Tree::scroll_to_item(TreeItem *p_item) { +void Tree::scroll_to_item(TreeItem *p_item, bool p_center_on_item) { if (!is_visible_in_tree()) { - // hack to work around crash in get_item_rect() if Tree is not in tree. - return; + return; // Hack to work around crash in get_item_rect() if Tree is not in tree. } - // make sure the scrollbar min and max are up to date with latest changes. update_scrollbars(); - const Rect2 r = get_item_rect(p_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; - if (r.position.y <= v_scroll->get_value()) { - v_scroll->set_value(r.position.y); - } else if (r.position.y + r.size.y + 2 * cache.vseparation > v_scroll->get_value() + get_size().y) { - v_scroll->set_value(r.position.y + r.size.y + 2 * cache.vseparation - get_size().y); + if (p_center_on_item) { + v_scroll->set_value(item_y - (tree_height - item_height) / 2.0f); + } else { + if (item_y < v_scroll->get_value()) { + v_scroll->set_value(item_y); + } else { + const real_t new_position = item_y + item_height - tree_height; + if (new_position > v_scroll->get_value()) { + v_scroll->set_value(new_position); + } + } } } void Tree::set_h_scroll_enabled(bool p_enable) { h_scroll_enabled = p_enable; - minimum_size_changed(); + update_minimum_size(); } bool Tree::is_h_scroll_enabled() const { @@ -4379,7 +4465,7 @@ bool Tree::is_h_scroll_enabled() const { void Tree::set_v_scroll_enabled(bool p_enable) { v_scroll_enabled = p_enable; - minimum_size_changed(); + update_minimum_size(); } bool Tree::is_v_scroll_enabled() const { @@ -4682,7 +4768,7 @@ String Tree::get_tooltip(const Point2 &p_pos) const { Size2 size = b->get_size() + cache.button_pressed->get_minimum_size(); if (pos.x > col_width - size.width) { String tooltip = c.buttons[j].tooltip; - if (tooltip != "") { + if (!tooltip.is_empty()) { return tooltip; } } @@ -4756,7 +4842,7 @@ bool Tree::get_allow_reselect() const { void Tree::_bind_methods() { ClassDB::bind_method(D_METHOD("clear"), &Tree::clear); - ClassDB::bind_method(D_METHOD("create_item", "parent", "idx"), &Tree::_create_item, DEFVAL(Variant()), DEFVAL(-1)); + ClassDB::bind_method(D_METHOD("create_item", "parent", "idx"), &Tree::create_item, DEFVAL(Variant()), DEFVAL(-1)); ClassDB::bind_method(D_METHOD("get_root"), &Tree::get_root); ClassDB::bind_method(D_METHOD("set_column_custom_minimum_width", "column", "min_width"), &Tree::set_column_custom_minimum_width); @@ -4771,7 +4857,7 @@ void Tree::_bind_methods() { ClassDB::bind_method(D_METHOD("set_hide_root", "enable"), &Tree::set_hide_root); ClassDB::bind_method(D_METHOD("is_root_hidden"), &Tree::is_root_hidden); - ClassDB::bind_method(D_METHOD("get_next_selected", "from"), &Tree::_get_next_selected); + ClassDB::bind_method(D_METHOD("get_next_selected", "from"), &Tree::get_next_selected); ClassDB::bind_method(D_METHOD("get_selected"), &Tree::get_selected); ClassDB::bind_method(D_METHOD("get_selected_column"), &Tree::get_selected_column); ClassDB::bind_method(D_METHOD("get_pressed_button"), &Tree::get_pressed_button); @@ -4785,10 +4871,11 @@ void Tree::_bind_methods() { ClassDB::bind_method(D_METHOD("get_edited_column"), &Tree::get_edited_column); ClassDB::bind_method(D_METHOD("edit_selected"), &Tree::edit_selected); ClassDB::bind_method(D_METHOD("get_custom_popup_rect"), &Tree::get_custom_popup_rect); - ClassDB::bind_method(D_METHOD("get_item_area_rect", "item", "column"), &Tree::_get_item_rect, DEFVAL(-1)); + ClassDB::bind_method(D_METHOD("get_item_area_rect", "item", "column"), &Tree::get_item_rect, DEFVAL(-1)); ClassDB::bind_method(D_METHOD("get_item_at_position", "position"), &Tree::get_item_at_position); ClassDB::bind_method(D_METHOD("get_column_at_position", "position"), &Tree::get_column_at_position); ClassDB::bind_method(D_METHOD("get_drop_section_at_position", "position"), &Tree::get_drop_section_at_position); + ClassDB::bind_method(D_METHOD("get_button_id_at_position", "position"), &Tree::get_button_id_at_position); ClassDB::bind_method(D_METHOD("ensure_cursor_is_visible"), &Tree::ensure_cursor_is_visible); @@ -4809,7 +4896,7 @@ void Tree::_bind_methods() { ClassDB::bind_method(D_METHOD("get_column_title_language", "column"), &Tree::get_column_title_language); ClassDB::bind_method(D_METHOD("get_scroll"), &Tree::get_scroll); - ClassDB::bind_method(D_METHOD("scroll_to_item", "item"), &Tree::_scroll_to_item); + ClassDB::bind_method(D_METHOD("scroll_to_item", "item", "center_on_item"), &Tree::scroll_to_item, DEFVAL(false)); ClassDB::bind_method(D_METHOD("set_h_scroll_enabled", "h_scroll"), &Tree::set_h_scroll_enabled); ClassDB::bind_method(D_METHOD("is_h_scroll_enabled"), &Tree::is_h_scroll_enabled); @@ -4830,6 +4917,7 @@ void Tree::_bind_methods() { ClassDB::bind_method(D_METHOD("get_allow_reselect"), &Tree::get_allow_reselect); ADD_PROPERTY(PropertyInfo(Variant::INT, "columns"), "set_columns", "get_columns"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "column_titles_visible"), "set_column_titles_visible", "are_column_titles_visible"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "allow_reselect"), "set_allow_reselect", "get_allow_reselect"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "allow_rmb_select"), "set_allow_rmb_select", "get_allow_rmb_select"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "hide_folding"), "set_hide_folding", "is_folding_hidden"); @@ -4850,6 +4938,7 @@ void Tree::_bind_methods() { ADD_SIGNAL(MethodInfo("item_custom_button_pressed")); ADD_SIGNAL(MethodInfo("item_double_clicked")); ADD_SIGNAL(MethodInfo("item_collapsed", PropertyInfo(Variant::OBJECT, "item", PROPERTY_HINT_RESOURCE_TYPE, "TreeItem"))); + ADD_SIGNAL(MethodInfo("check_propagated_to_item", PropertyInfo(Variant::OBJECT, "item", PROPERTY_HINT_RESOURCE_TYPE, "TreeItem"), PropertyInfo(Variant::INT, "column"))); //ADD_SIGNAL( MethodInfo("item_double_clicked" ) ); ADD_SIGNAL(MethodInfo("button_pressed", PropertyInfo(Variant::OBJECT, "item", PROPERTY_HINT_RESOURCE_TYPE, "TreeItem"), PropertyInfo(Variant::INT, "column"), PropertyInfo(Variant::INT, "id"))); ADD_SIGNAL(MethodInfo("custom_popup_edited", PropertyInfo(Variant::BOOL, "arrow_clicked"))); diff --git a/scene/gui/tree.h b/scene/gui/tree.h index 6ca9458e9b..dc786de6dc 100644 --- a/scene/gui/tree.h +++ b/scene/gui/tree.h @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ @@ -52,12 +52,6 @@ public: CELL_MODE_CUSTOM, ///< Contains a custom value, show a string, and an edit button }; - enum TextAlign { - ALIGN_LEFT, - ALIGN_CENTER, - ALIGN_RIGHT - }; - private: friend class Tree; @@ -98,7 +92,7 @@ private: Size2i cached_minimum_size; bool cached_minimum_size_dirty = true; - TextAlign text_align = ALIGN_LEFT; + HorizontalAlignment text_alignment = HORIZONTAL_ALIGNMENT_LEFT; Variant meta; String tooltip; @@ -171,7 +165,7 @@ private: } if (parent) { if (!parent->children_cache.is_empty()) { - parent->children_cache.remove(get_index()); + parent->children_cache.remove_at(get_index()); } if (parent->first_child == this) { parent->first_child = next; @@ -194,16 +188,6 @@ protected: return d; } - void _remove_child(Object *p_child) { - remove_child(Object::cast_to<TreeItem>(p_child)); - } - - void _move_before(Object *p_item) { - move_before(Object::cast_to<TreeItem>(p_item)); - } - void _move_after(Object *p_item) { - move_after(Object::cast_to<TreeItem>(p_item)); - } Variant _call_recursive_bind(const Variant **p_args, int p_argcount, Callable::CallError &r_error); @@ -218,6 +202,14 @@ public: bool is_checked(int p_column) const; bool is_indeterminate(int p_column) const; + void propagate_check(int p_column, bool p_emit_signal = true); + +private: + // Check helpers. + void _propagate_check_through_children(int p_column, bool p_checked, bool p_emit_signal); + void _propagate_check_through_parents(int p_column, bool p_emit_signal); + +public: void set_text(int p_column, String p_text); String get_text(int p_column) const; @@ -256,6 +248,7 @@ public: int get_button_count(int p_column) const; String get_button_tooltip(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); int get_button_by_id(int p_column, int p_id) const; void set_button(int p_column, int p_idx, const Ref<Texture2D> &p_button); @@ -316,8 +309,8 @@ public: void set_tooltip(int p_column, const String &p_tooltip); String get_tooltip(int p_column) const; - void set_text_align(int p_column, TextAlign p_align); - TextAlign get_text_align(int p_column) const; + void set_text_alignment(int p_column, HorizontalAlignment p_alignment); + HorizontalAlignment get_text_alignment(int p_column) const; void set_expand_right(int p_column, bool p_enable); bool get_expand_right(int p_column) const; @@ -359,7 +352,6 @@ public: }; VARIANT_ENUM_CAST(TreeItem::TreeCellMode); -VARIANT_ENUM_CAST(TreeItem::TextAlign); class VBoxContainer; @@ -462,7 +454,7 @@ private: void 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); int draw_item(const Point2i &p_pos, const Point2 &p_draw_ofs, const Size2 &p_draw_size, TreeItem *p_item); void select_single_item(TreeItem *p_selected, TreeItem *p_current, int p_col, TreeItem *p_prev = nullptr, bool *r_in_range = nullptr, bool p_force_deselect = false); - int propagate_mouse_event(const Point2i &p_pos, int x_ofs, int y_ofs, int x_limit, bool p_double_click, TreeItem *p_item, int p_button, const Ref<InputEventWithModifiers> &p_mod); + int propagate_mouse_event(const Point2i &p_pos, int x_ofs, int y_ofs, int x_limit, bool p_double_click, TreeItem *p_item, MouseButton p_button, const Ref<InputEventWithModifiers> &p_mod); void _text_editor_submit(String p_text); void _text_editor_modal_close(); void value_editor_changed(double p_value); @@ -616,23 +608,6 @@ private: protected: static void _bind_methods(); - //bind helpers - TreeItem *_create_item(Object *p_parent, int p_idx = -1) { - return create_item(Object::cast_to<TreeItem>(p_parent), p_idx); - } - - TreeItem *_get_next_selected(Object *p_item) { - return get_next_selected(Object::cast_to<TreeItem>(p_item)); - } - - Rect2 _get_item_rect(Object *p_item, int p_column) const { - return get_item_rect(Object::cast_to<TreeItem>(p_item), p_column); - } - - void _scroll_to_item(Object *p_item) { - scroll_to_item(Object::cast_to<TreeItem>(p_item)); - } - public: virtual void gui_input(const Ref<InputEvent> &p_event) override; @@ -708,7 +683,7 @@ public: TreeItem *get_item_with_text(const String &p_find) const; Point2 get_scroll() const; - void scroll_to_item(TreeItem *p_item); + void scroll_to_item(TreeItem *p_item, bool p_center_on_item = false); void set_h_scroll_enabled(bool p_enable); bool is_h_scroll_enabled() const; void set_v_scroll_enabled(bool p_enable); diff --git a/scene/gui/video_player.cpp b/scene/gui/video_stream_player.cpp index 989aabc549..d7c76aa070 100644 --- a/scene/gui/video_player.cpp +++ b/scene/gui/video_stream_player.cpp @@ -1,12 +1,12 @@ /*************************************************************************/ -/* video_player.cpp */ +/* video_stream_player.cpp */ /*************************************************************************/ /* This file is part of: */ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ @@ -28,13 +28,13 @@ /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /*************************************************************************/ -#include "video_player.h" +#include "video_stream_player.h" #include "core/os/os.h" #include "scene/scene_string_names.h" #include "servers/audio_server.h" -int VideoPlayer::sp_get_channel_count() const { +int VideoStreamPlayer::sp_get_channel_count() const { if (playback.is_null()) { return 0; } @@ -42,7 +42,7 @@ int VideoPlayer::sp_get_channel_count() const { return playback->get_channels(); } -bool VideoPlayer::mix(AudioFrame *p_buffer, int p_frames) { +bool VideoStreamPlayer::mix(AudioFrame *p_buffer, int p_frames) { // Check the amount resampler can really handle. // If it cannot, wait "wait_resampler_phase_limit" times. // This mechanism contributes to smoother pause/unpause operation. @@ -56,11 +56,11 @@ bool VideoPlayer::mix(AudioFrame *p_buffer, int p_frames) { } // Called from main thread (e.g. VideoStreamPlaybackTheora::update). -int VideoPlayer::_audio_mix_callback(void *p_udata, const float *p_data, int p_frames) { +int VideoStreamPlayer::_audio_mix_callback(void *p_udata, const float *p_data, int p_frames) { ERR_FAIL_NULL_V(p_udata, 0); ERR_FAIL_NULL_V(p_data, 0); - VideoPlayer *vp = (VideoPlayer *)p_udata; + VideoStreamPlayer *vp = (VideoStreamPlayer *)p_udata; int todo = MIN(vp->resampler.get_writer_space(), p_frames); @@ -75,13 +75,13 @@ int VideoPlayer::_audio_mix_callback(void *p_udata, const float *p_data, int p_f return todo; } -void VideoPlayer::_mix_audios(void *p_self) { +void VideoStreamPlayer::_mix_audios(void *p_self) { ERR_FAIL_NULL(p_self); - reinterpret_cast<VideoPlayer *>(p_self)->_mix_audio(); + reinterpret_cast<VideoStreamPlayer *>(p_self)->_mix_audio(); } // Called from audio thread -void VideoPlayer::_mix_audio() { +void VideoStreamPlayer::_mix_audio() { if (!stream.is_valid()) { return; } @@ -126,7 +126,7 @@ void VideoPlayer::_mix_audio() { } } -void VideoPlayer::_notification(int p_notification) { +void VideoStreamPlayer::_notification(int p_notification) { switch (p_notification) { case NOTIFICATION_ENTER_TREE: { AudioServer::get_singleton()->add_mix_callback(_mix_audios, this); @@ -134,7 +134,6 @@ void VideoPlayer::_notification(int p_notification) { if (stream.is_valid() && autoplay && !Engine::get_singleton()->is_editor_hint()) { play(); } - } break; case NOTIFICATION_EXIT_TREE: { @@ -162,7 +161,6 @@ void VideoPlayer::_notification(int p_notification) { if (!playback->is_playing()) { emit_signal(SceneStringNames::get_singleton()->finished); } - } break; case NOTIFICATION_DRAW: { @@ -175,12 +173,11 @@ void VideoPlayer::_notification(int p_notification) { Size2 s = expand ? get_size() : texture->get_size(); draw_texture_rect(texture, Rect2(Point2(), s), false); - } break; - }; -}; + } +} -Size2 VideoPlayer::get_minimum_size() const { +Size2 VideoStreamPlayer::get_minimum_size() const { if (!expand && !texture.is_null()) { return texture->get_size(); } else { @@ -188,17 +185,17 @@ Size2 VideoPlayer::get_minimum_size() const { } } -void VideoPlayer::set_expand(bool p_expand) { +void VideoStreamPlayer::set_expand(bool p_expand) { expand = p_expand; update(); - minimum_size_changed(); + update_minimum_size(); } -bool VideoPlayer::has_expand() const { +bool VideoStreamPlayer::has_expand() const { return expand; } -void VideoPlayer::set_stream(const Ref<VideoStream> &p_stream) { +void VideoStreamPlayer::set_stream(const Ref<VideoStream> &p_stream) { stop(); AudioServer::get_singleton()->lock(); @@ -241,15 +238,15 @@ void VideoPlayer::set_stream(const Ref<VideoStream> &p_stream) { update(); if (!expand) { - minimum_size_changed(); + update_minimum_size(); } -}; +} -Ref<VideoStream> VideoPlayer::get_stream() const { +Ref<VideoStream> VideoStreamPlayer::get_stream() const { return stream; -}; +} -void VideoPlayer::play() { +void VideoStreamPlayer::play() { ERR_FAIL_COND(!is_inside_tree()); if (playback.is_null()) { return; @@ -257,12 +254,10 @@ void VideoPlayer::play() { playback->stop(); playback->play(); set_process_internal(true); - // AudioServer::get_singleton()->stream_set_active(stream_rid,true); - // AudioServer::get_singleton()->stream_set_volume_scale(stream_rid,volume); last_audio_time = 0; -}; +} -void VideoPlayer::stop() { +void VideoStreamPlayer::stop() { if (!is_inside_tree()) { return; } @@ -271,94 +266,93 @@ void VideoPlayer::stop() { } playback->stop(); - // AudioServer::get_singleton()->stream_set_active(stream_rid,false); resampler.flush(); set_process_internal(false); last_audio_time = 0; -}; +} -bool VideoPlayer::is_playing() const { +bool VideoStreamPlayer::is_playing() const { if (playback.is_null()) { return false; } return playback->is_playing(); -}; +} -void VideoPlayer::set_paused(bool p_paused) { +void VideoStreamPlayer::set_paused(bool p_paused) { paused = p_paused; if (playback.is_valid()) { playback->set_paused(p_paused); set_process_internal(!p_paused); - }; + } last_audio_time = 0; -}; +} -bool VideoPlayer::is_paused() const { +bool VideoStreamPlayer::is_paused() const { return paused; } -void VideoPlayer::set_buffering_msec(int p_msec) { +void VideoStreamPlayer::set_buffering_msec(int p_msec) { buffering_ms = p_msec; } -int VideoPlayer::get_buffering_msec() const { +int VideoStreamPlayer::get_buffering_msec() const { return buffering_ms; } -void VideoPlayer::set_audio_track(int p_track) { +void VideoStreamPlayer::set_audio_track(int p_track) { audio_track = p_track; } -int VideoPlayer::get_audio_track() const { +int VideoStreamPlayer::get_audio_track() const { return audio_track; } -void VideoPlayer::set_volume(float p_vol) { +void VideoStreamPlayer::set_volume(float p_vol) { volume = p_vol; -}; +} -float VideoPlayer::get_volume() const { +float VideoStreamPlayer::get_volume() const { return volume; -}; +} -void VideoPlayer::set_volume_db(float p_db) { +void VideoStreamPlayer::set_volume_db(float p_db) { if (p_db < -79) { set_volume(0); } else { set_volume(Math::db2linear(p_db)); } -}; +} -float VideoPlayer::get_volume_db() const { +float VideoStreamPlayer::get_volume_db() const { if (volume == 0) { return -80; } else { return Math::linear2db(volume); } -}; +} -String VideoPlayer::get_stream_name() const { +String VideoStreamPlayer::get_stream_name() const { if (stream.is_null()) { return "<No Stream>"; } return stream->get_name(); -}; +} -float VideoPlayer::get_stream_position() const { +float VideoStreamPlayer::get_stream_position() const { if (playback.is_null()) { return 0; } return playback->get_playback_position(); -}; +} -void VideoPlayer::set_stream_position(float p_position) { +void VideoStreamPlayer::set_stream_position(float p_position) { if (playback.is_valid()) { playback->seek(p_position); } } -Ref<Texture2D> VideoPlayer::get_video_texture() const { +Ref<Texture2D> VideoStreamPlayer::get_video_texture() const { if (playback.is_valid()) { return playback->get_texture(); } @@ -366,22 +360,22 @@ Ref<Texture2D> VideoPlayer::get_video_texture() const { return Ref<Texture2D>(); } -void VideoPlayer::set_autoplay(bool p_enable) { +void VideoStreamPlayer::set_autoplay(bool p_enable) { autoplay = p_enable; -}; +} -bool VideoPlayer::has_autoplay() const { +bool VideoStreamPlayer::has_autoplay() const { return autoplay; -}; +} -void VideoPlayer::set_bus(const StringName &p_bus) { - //if audio is active, must lock this +void VideoStreamPlayer::set_bus(const StringName &p_bus) { + // If audio is active, must lock this. AudioServer::get_singleton()->lock(); bus = p_bus; AudioServer::get_singleton()->unlock(); } -StringName VideoPlayer::get_bus() const { +StringName VideoStreamPlayer::get_bus() const { for (int i = 0; i < AudioServer::get_singleton()->get_bus_count(); i++) { if (AudioServer::get_singleton()->get_bus_name(i) == bus) { return bus; @@ -390,7 +384,7 @@ StringName VideoPlayer::get_bus() const { return "Master"; } -void VideoPlayer::_validate_property(PropertyInfo &p_property) const { +void VideoStreamPlayer::_validate_property(PropertyInfo &p_property) const { if (p_property.name == "bus") { String options; for (int i = 0; i < AudioServer::get_singleton()->get_bus_count(); i++) { @@ -405,51 +399,50 @@ void VideoPlayer::_validate_property(PropertyInfo &p_property) const { } } -void VideoPlayer::_bind_methods() { - ClassDB::bind_method(D_METHOD("set_stream", "stream"), &VideoPlayer::set_stream); - ClassDB::bind_method(D_METHOD("get_stream"), &VideoPlayer::get_stream); +void VideoStreamPlayer::_bind_methods() { + ClassDB::bind_method(D_METHOD("set_stream", "stream"), &VideoStreamPlayer::set_stream); + ClassDB::bind_method(D_METHOD("get_stream"), &VideoStreamPlayer::get_stream); - ClassDB::bind_method(D_METHOD("play"), &VideoPlayer::play); - ClassDB::bind_method(D_METHOD("stop"), &VideoPlayer::stop); + ClassDB::bind_method(D_METHOD("play"), &VideoStreamPlayer::play); + ClassDB::bind_method(D_METHOD("stop"), &VideoStreamPlayer::stop); - ClassDB::bind_method(D_METHOD("is_playing"), &VideoPlayer::is_playing); + ClassDB::bind_method(D_METHOD("is_playing"), &VideoStreamPlayer::is_playing); - ClassDB::bind_method(D_METHOD("set_paused", "paused"), &VideoPlayer::set_paused); - ClassDB::bind_method(D_METHOD("is_paused"), &VideoPlayer::is_paused); + ClassDB::bind_method(D_METHOD("set_paused", "paused"), &VideoStreamPlayer::set_paused); + ClassDB::bind_method(D_METHOD("is_paused"), &VideoStreamPlayer::is_paused); - ClassDB::bind_method(D_METHOD("set_volume", "volume"), &VideoPlayer::set_volume); - ClassDB::bind_method(D_METHOD("get_volume"), &VideoPlayer::get_volume); + ClassDB::bind_method(D_METHOD("set_volume", "volume"), &VideoStreamPlayer::set_volume); + ClassDB::bind_method(D_METHOD("get_volume"), &VideoStreamPlayer::get_volume); - ClassDB::bind_method(D_METHOD("set_volume_db", "db"), &VideoPlayer::set_volume_db); - ClassDB::bind_method(D_METHOD("get_volume_db"), &VideoPlayer::get_volume_db); + ClassDB::bind_method(D_METHOD("set_volume_db", "db"), &VideoStreamPlayer::set_volume_db); + ClassDB::bind_method(D_METHOD("get_volume_db"), &VideoStreamPlayer::get_volume_db); - ClassDB::bind_method(D_METHOD("set_audio_track", "track"), &VideoPlayer::set_audio_track); - ClassDB::bind_method(D_METHOD("get_audio_track"), &VideoPlayer::get_audio_track); + ClassDB::bind_method(D_METHOD("set_audio_track", "track"), &VideoStreamPlayer::set_audio_track); + ClassDB::bind_method(D_METHOD("get_audio_track"), &VideoStreamPlayer::get_audio_track); - ClassDB::bind_method(D_METHOD("get_stream_name"), &VideoPlayer::get_stream_name); + ClassDB::bind_method(D_METHOD("get_stream_name"), &VideoStreamPlayer::get_stream_name); - ClassDB::bind_method(D_METHOD("set_stream_position", "position"), &VideoPlayer::set_stream_position); - ClassDB::bind_method(D_METHOD("get_stream_position"), &VideoPlayer::get_stream_position); + ClassDB::bind_method(D_METHOD("set_stream_position", "position"), &VideoStreamPlayer::set_stream_position); + ClassDB::bind_method(D_METHOD("get_stream_position"), &VideoStreamPlayer::get_stream_position); - ClassDB::bind_method(D_METHOD("set_autoplay", "enabled"), &VideoPlayer::set_autoplay); - ClassDB::bind_method(D_METHOD("has_autoplay"), &VideoPlayer::has_autoplay); + ClassDB::bind_method(D_METHOD("set_autoplay", "enabled"), &VideoStreamPlayer::set_autoplay); + ClassDB::bind_method(D_METHOD("has_autoplay"), &VideoStreamPlayer::has_autoplay); - ClassDB::bind_method(D_METHOD("set_expand", "enable"), &VideoPlayer::set_expand); - ClassDB::bind_method(D_METHOD("has_expand"), &VideoPlayer::has_expand); + ClassDB::bind_method(D_METHOD("set_expand", "enable"), &VideoStreamPlayer::set_expand); + ClassDB::bind_method(D_METHOD("has_expand"), &VideoStreamPlayer::has_expand); - ClassDB::bind_method(D_METHOD("set_buffering_msec", "msec"), &VideoPlayer::set_buffering_msec); - ClassDB::bind_method(D_METHOD("get_buffering_msec"), &VideoPlayer::get_buffering_msec); + ClassDB::bind_method(D_METHOD("set_buffering_msec", "msec"), &VideoStreamPlayer::set_buffering_msec); + ClassDB::bind_method(D_METHOD("get_buffering_msec"), &VideoStreamPlayer::get_buffering_msec); - ClassDB::bind_method(D_METHOD("set_bus", "bus"), &VideoPlayer::set_bus); - ClassDB::bind_method(D_METHOD("get_bus"), &VideoPlayer::get_bus); + ClassDB::bind_method(D_METHOD("set_bus", "bus"), &VideoStreamPlayer::set_bus); + ClassDB::bind_method(D_METHOD("get_bus"), &VideoStreamPlayer::get_bus); - ClassDB::bind_method(D_METHOD("get_video_texture"), &VideoPlayer::get_video_texture); + ClassDB::bind_method(D_METHOD("get_video_texture"), &VideoStreamPlayer::get_video_texture); ADD_SIGNAL(MethodInfo("finished")); ADD_PROPERTY(PropertyInfo(Variant::INT, "audio_track", PROPERTY_HINT_RANGE, "0,128,1"), "set_audio_track", "get_audio_track"); ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "stream", PROPERTY_HINT_RESOURCE_TYPE, "VideoStream"), "set_stream", "get_stream"); - //ADD_PROPERTY( PropertyInfo(Variant::BOOL, "stream/loop"), "set_loop", "has_loop") ; ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "volume_db", PROPERTY_HINT_RANGE, "-80,24,0.01"), "set_volume_db", "get_volume_db"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "volume", PROPERTY_HINT_RANGE, "0,15,0.01,exp", PROPERTY_USAGE_NONE), "set_volume", "get_volume"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "autoplay"), "set_autoplay", "has_autoplay"); @@ -461,10 +454,8 @@ void VideoPlayer::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::STRING_NAME, "bus", PROPERTY_HINT_ENUM, ""), "set_bus", "get_bus"); } -VideoPlayer::VideoPlayer() {} +VideoStreamPlayer::VideoStreamPlayer() {} -VideoPlayer::~VideoPlayer() { - // if (stream_rid.is_valid()) - // AudioServer::get_singleton()->free(stream_rid); - resampler.clear(); //Not necessary here, but make in consistent with other "stream_player" classes -}; +VideoStreamPlayer::~VideoStreamPlayer() { + resampler.clear(); // Not necessary here, but make in consistent with other "stream_player" classes. +} diff --git a/scene/gui/video_player.h b/scene/gui/video_stream_player.h index 0edad296a1..130b2901f1 100644 --- a/scene/gui/video_player.h +++ b/scene/gui/video_stream_player.h @@ -1,12 +1,12 @@ /*************************************************************************/ -/* video_player.h */ +/* video_stream_player.h */ /*************************************************************************/ /* This file is part of: */ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ @@ -28,16 +28,16 @@ /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /*************************************************************************/ -#ifndef VIDEO_PLAYER_H -#define VIDEO_PLAYER_H +#ifndef VIDEO_STREAM_PLAYER_H +#define VIDEO_STREAM_PLAYER_H #include "scene/gui/control.h" #include "scene/resources/video_stream.h" #include "servers/audio/audio_rb_resampler.h" #include "servers/audio_server.h" -class VideoPlayer : public Control { - GDCLASS(VideoPlayer, Control); +class VideoStreamPlayer : public Control { + GDCLASS(VideoStreamPlayer, Control); struct Output { AudioFrame vol; @@ -119,8 +119,8 @@ public: void set_bus(const StringName &p_bus); StringName get_bus() const; - VideoPlayer(); - ~VideoPlayer(); + VideoStreamPlayer(); + ~VideoStreamPlayer(); }; -#endif // VIDEO_PLAYER_H +#endif // VIDEO_STREAM_PLAYER_H diff --git a/scene/gui/view_panner.cpp b/scene/gui/view_panner.cpp new file mode 100644 index 0000000000..71865b4864 --- /dev/null +++ b/scene/gui/view_panner.cpp @@ -0,0 +1,184 @@ +/*************************************************************************/ +/* view_panner.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 "view_panner.h" + +#include "core/input/input.h" +#include "core/input/shortcut.h" +#include "core/os/keyboard.h" + +bool ViewPanner::gui_input(const Ref<InputEvent> &p_event, Rect2 p_canvas_rect) { + Ref<InputEventMouseButton> mb = p_event; + if (mb.is_valid()) { + Vector2i scroll_vec = Vector2((mb->get_button_index() == MouseButton::WHEEL_RIGHT) - (mb->get_button_index() == MouseButton::WHEEL_LEFT), (mb->get_button_index() == MouseButton::WHEEL_DOWN) - (mb->get_button_index() == MouseButton::WHEEL_UP)); + if (scroll_vec != Vector2()) { + if (control_scheme == SCROLL_PANS) { + if (mb->is_ctrl_pressed()) { + scroll_vec.y *= mb->get_factor(); + callback_helper(zoom_callback, varray(scroll_vec, mb->get_position(), mb->is_alt_pressed())); + return true; + } else { + Vector2 panning; + if (mb->is_shift_pressed()) { + panning.x += mb->get_factor() * scroll_vec.y; + panning.y += mb->get_factor() * scroll_vec.x; + } else { + panning.y += mb->get_factor() * scroll_vec.y; + panning.x += mb->get_factor() * scroll_vec.x; + } + callback_helper(scroll_callback, varray(panning, mb->is_alt_pressed())); + return true; + } + } else { + if (mb->is_ctrl_pressed()) { + Vector2 panning; + if (mb->is_shift_pressed()) { + panning.x += mb->get_factor() * scroll_vec.y; + panning.y += mb->get_factor() * scroll_vec.x; + } else { + panning.y += mb->get_factor() * scroll_vec.y; + panning.x += mb->get_factor() * scroll_vec.x; + } + callback_helper(scroll_callback, varray(panning, mb->is_alt_pressed())); + return true; + } else if (!mb->is_shift_pressed()) { + scroll_vec.y *= mb->get_factor(); + callback_helper(zoom_callback, varray(scroll_vec, mb->get_position(), mb->is_alt_pressed())); + return true; + } + } + } + + // Alt is not used for button presses, so ignore it. + if (mb->is_alt_pressed()) { + return false; + } + + bool is_drag_event = mb->get_button_index() == MouseButton::MIDDLE || + (enable_rmb && mb->get_button_index() == MouseButton::RIGHT) || + (!simple_panning_enabled && mb->get_button_index() == MouseButton::LEFT && is_panning()) || + (force_drag && mb->get_button_index() == MouseButton::LEFT); + + if (is_drag_event) { + if (mb->is_pressed()) { + is_dragging = true; + } else { + is_dragging = false; + } + return mb->get_button_index() != MouseButton::LEFT || mb->is_pressed(); // Don't consume LMB release events (it fixes some selection problems). + } + } + + Ref<InputEventMouseMotion> mm = p_event; + if (mm.is_valid()) { + if (is_dragging) { + if (p_canvas_rect != Rect2()) { + callback_helper(pan_callback, varray(Input::get_singleton()->warp_mouse_motion(mm, p_canvas_rect))); + } else { + callback_helper(pan_callback, varray(mm->get_relative())); + } + return true; + } + } + + Ref<InputEventKey> k = p_event; + if (k.is_valid()) { + if (pan_view_shortcut.is_valid() && pan_view_shortcut->matches_event(k)) { + pan_key_pressed = k->is_pressed(); + if (simple_panning_enabled || (Input::get_singleton()->get_mouse_button_mask() & MouseButton::LEFT) != MouseButton::NONE) { + is_dragging = pan_key_pressed; + } + return true; + } + } + + return false; +} + +void ViewPanner::release_pan_key() { + pan_key_pressed = false; + is_dragging = false; +} + +void ViewPanner::callback_helper(Callable p_callback, Vector<Variant> p_args) { + const Variant **argptr = (const Variant **)alloca(sizeof(Variant *) * p_args.size()); + for (int i = 0; i < p_args.size(); i++) { + argptr[i] = &p_args[i]; + } + + Variant result; + Callable::CallError ce; + p_callback.call(argptr, p_args.size(), result, ce); +} + +void ViewPanner::set_callbacks(Callable p_scroll_callback, Callable p_pan_callback, Callable p_zoom_callback) { + scroll_callback = p_scroll_callback; + pan_callback = p_pan_callback; + zoom_callback = p_zoom_callback; +} + +void ViewPanner::set_control_scheme(ControlScheme p_scheme) { + control_scheme = p_scheme; +} + +void ViewPanner::set_enable_rmb(bool p_enable) { + enable_rmb = p_enable; +} + +void ViewPanner::set_pan_shortcut(Ref<Shortcut> p_shortcut) { + pan_view_shortcut = p_shortcut; + pan_key_pressed = false; +} + +void ViewPanner::set_simple_panning_enabled(bool p_enabled) { + simple_panning_enabled = p_enabled; +} + +void ViewPanner::setup(ControlScheme p_scheme, Ref<Shortcut> p_shortcut, bool p_simple_panning) { + set_control_scheme(p_scheme); + set_pan_shortcut(p_shortcut); + set_simple_panning_enabled(p_simple_panning); +} + +bool ViewPanner::is_panning() const { + return is_dragging || pan_key_pressed; +} + +void ViewPanner::set_force_drag(bool p_force) { + force_drag = p_force; +} + +ViewPanner::ViewPanner() { + Array inputs; + inputs.append(InputEventKey::create_reference(Key::SPACE)); + + pan_view_shortcut.instantiate(); + pan_view_shortcut->set_events(inputs); +} diff --git a/scene/gui/view_panner.h b/scene/gui/view_panner.h new file mode 100644 index 0000000000..5b820c5f8f --- /dev/null +++ b/scene/gui/view_panner.h @@ -0,0 +1,83 @@ +/*************************************************************************/ +/* view_panner.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 VIEW_PANNER_H +#define VIEW_PANNER_H + +#include "core/object/ref_counted.h" + +class InputEvent; +class Shortcut; + +class ViewPanner : public RefCounted { + GDCLASS(ViewPanner, RefCounted); + +public: + enum ControlScheme { + SCROLL_ZOOMS, + SCROLL_PANS, + }; + +private: + bool is_dragging = false; + bool pan_key_pressed = false; + bool force_drag = false; + + bool enable_rmb = false; + bool simple_panning_enabled = false; + + Ref<Shortcut> pan_view_shortcut; + + Callable scroll_callback; + Callable pan_callback; + Callable zoom_callback; + + void callback_helper(Callable p_callback, Vector<Variant> p_args); + ControlScheme control_scheme = SCROLL_ZOOMS; + +public: + void set_callbacks(Callable p_scroll_callback, Callable p_pan_callback, Callable p_zoom_callback); + void set_control_scheme(ControlScheme p_scheme); + void set_enable_rmb(bool p_enable); + void set_pan_shortcut(Ref<Shortcut> p_shortcut); + void set_simple_panning_enabled(bool p_enabled); + + void setup(ControlScheme p_scheme, Ref<Shortcut> p_shortcut, bool p_simple_panning); + + bool is_panning() const; + void set_force_drag(bool p_force); + + bool gui_input(const Ref<InputEvent> &p_ev, Rect2 p_canvas_rect = Rect2()); + void release_pan_key(); + + ViewPanner(); +}; + +#endif // VIEW_PANNER_H |