summaryrefslogtreecommitdiff
path: root/scene/gui
diff options
context:
space:
mode:
Diffstat (limited to 'scene/gui')
-rw-r--r--scene/gui/aspect_ratio_container.cpp24
-rw-r--r--scene/gui/aspect_ratio_container.h7
-rw-r--r--scene/gui/base_button.cpp117
-rw-r--r--scene/gui/base_button.h11
-rw-r--r--scene/gui/box_container.cpp33
-rw-r--r--scene/gui/box_container.h7
-rw-r--r--scene/gui/button.cpp60
-rw-r--r--scene/gui/button.h4
-rw-r--r--scene/gui/center_container.cpp42
-rw-r--r--scene/gui/center_container.h7
-rw-r--r--scene/gui/check_box.cpp68
-rw-r--r--scene/gui/check_box.h10
-rw-r--r--scene/gui/check_button.cpp99
-rw-r--r--scene/gui/check_button.h12
-rw-r--r--scene/gui/code_edit.cpp265
-rw-r--r--scene/gui/code_edit.h20
-rw-r--r--scene/gui/color_picker.cpp27
-rw-r--r--scene/gui/color_picker.h6
-rw-r--r--scene/gui/color_rect.cpp10
-rw-r--r--scene/gui/color_rect.h4
-rw-r--r--scene/gui/container.cpp44
-rw-r--r--scene/gui/container.h10
-rw-r--r--scene/gui/control.cpp738
-rw-r--r--scene/gui/control.h67
-rw-r--r--scene/gui/dialogs.cpp33
-rw-r--r--scene/gui/dialogs.h18
-rw-r--r--scene/gui/file_dialog.cpp107
-rw-r--r--scene/gui/file_dialog.h61
-rw-r--r--scene/gui/flow_container.cpp281
-rw-r--r--scene/gui/flow_container.h79
-rw-r--r--scene/gui/gradient_edit.cpp137
-rw-r--r--scene/gui/gradient_edit.h10
-rw-r--r--scene/gui/graph_edit.cpp560
-rw-r--r--scene/gui/graph_edit.h108
-rw-r--r--scene/gui/graph_node.cpp150
-rw-r--r--scene/gui/graph_node.h25
-rw-r--r--scene/gui/grid_container.cpp102
-rw-r--r--scene/gui/grid_container.h4
-rw-r--r--scene/gui/item_list.cpp859
-rw-r--r--scene/gui/item_list.h12
-rw-r--r--scene/gui/label.cpp511
-rw-r--r--scene/gui/label.h24
-rw-r--r--scene/gui/line_edit.cpp145
-rw-r--r--scene/gui/line_edit.h22
-rw-r--r--scene/gui/link_button.cpp26
-rw-r--r--scene/gui/link_button.h13
-rw-r--r--scene/gui/margin_container.cpp23
-rw-r--r--scene/gui/margin_container.h7
-rw-r--r--scene/gui/menu_button.cpp30
-rw-r--r--scene/gui/menu_button.h10
-rw-r--r--scene/gui/nine_patch_rect.cpp28
-rw-r--r--scene/gui/nine_patch_rect.h4
-rw-r--r--scene/gui/option_button.cpp251
-rw-r--r--scene/gui/option_button.h24
-rw-r--r--scene/gui/panel.cpp35
-rw-r--r--scene/gui/panel.h19
-rw-r--r--scene/gui/panel_container.cpp90
-rw-r--r--scene/gui/panel_container.h7
-rw-r--r--scene/gui/popup.cpp92
-rw-r--r--scene/gui/popup.h14
-rw-r--r--scene/gui/popup_menu.cpp344
-rw-r--r--scene/gui/popup_menu.h21
-rw-r--r--scene/gui/progress_bar.cpp110
-rw-r--r--scene/gui/progress_bar.h20
-rw-r--r--scene/gui/range.cpp19
-rw-r--r--scene/gui/range.h12
-rw-r--r--scene/gui/reference_rect.cpp20
-rw-r--r--scene/gui/reference_rect.h4
-rw-r--r--scene/gui/rich_text_effect.cpp4
-rw-r--r--scene/gui/rich_text_effect.h4
-rw-r--r--scene/gui/rich_text_label.cpp1835
-rw-r--r--scene/gui/rich_text_label.h156
-rw-r--r--scene/gui/scroll_bar.cpp306
-rw-r--r--scene/gui/scroll_bar.h4
-rw-r--r--scene/gui/scroll_container.cpp193
-rw-r--r--scene/gui/scroll_container.h12
-rw-r--r--scene/gui/separator.cpp5
-rw-r--r--scene/gui/separator.h4
-rw-r--r--scene/gui/slider.cpp20
-rw-r--r--scene/gui/slider.h4
-rw-r--r--scene/gui/spin_box.cpp73
-rw-r--r--scene/gui/spin_box.h10
-rw-r--r--scene/gui/split_container.cpp32
-rw-r--r--scene/gui/split_container.h7
-rw-r--r--scene/gui/subviewport_container.cpp127
-rw-r--r--scene/gui/subviewport_container.h10
-rw-r--r--scene/gui/tab_bar.cpp1126
-rw-r--r--scene/gui/tab_bar.h46
-rw-r--r--scene/gui/tab_container.cpp1186
-rw-r--r--scene/gui/tab_container.h71
-rw-r--r--scene/gui/text_edit.cpp656
-rw-r--r--scene/gui/text_edit.h64
-rw-r--r--scene/gui/texture_button.cpp117
-rw-r--r--scene/gui/texture_button.h12
-rw-r--r--scene/gui/texture_progress_bar.cpp26
-rw-r--r--scene/gui/texture_progress_bar.h4
-rw-r--r--scene/gui/texture_rect.cpp177
-rw-r--r--scene/gui/texture_rect.h13
-rw-r--r--scene/gui/tree.cpp856
-rw-r--r--scene/gui/tree.h79
-rw-r--r--scene/gui/video_stream_player.cpp59
-rw-r--r--scene/gui/video_stream_player.h4
-rw-r--r--scene/gui/view_panner.cpp184
-rw-r--r--scene/gui/view_panner.h83
104 files changed, 8802 insertions, 4930 deletions
diff --git a/scene/gui/aspect_ratio_container.cpp b/scene/gui/aspect_ratio_container.cpp
index 9526c5e793..75f19ac452 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 */
@@ -70,6 +70,24 @@ void AspectRatioContainer::set_alignment_vertical(AlignmentMode p_alignment_vert
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: {
@@ -154,7 +172,7 @@ void AspectRatioContainer::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_alignment_vertical", "alignment_vertical"), &AspectRatioContainer::set_alignment_vertical);
ClassDB::bind_method(D_METHOD("get_alignment_vertical"), &AspectRatioContainer::get_alignment_vertical);
- ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "ratio"), "set_ratio", "get_ratio");
+ ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "ratio", PROPERTY_HINT_RANGE, "0.001,10.0,0.0001,or_greater"), "set_ratio", "get_ratio");
ADD_PROPERTY(PropertyInfo(Variant::INT, "stretch_mode", PROPERTY_HINT_ENUM, "Width Controls Height,Height Controls Width,Fit,Cover"), "set_stretch_mode", "get_stretch_mode");
ADD_GROUP("Alignment", "alignment_");
diff --git a/scene/gui/aspect_ratio_container.h b/scene/gui/aspect_ratio_container.h
index 3cdfc75734..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 */
@@ -72,6 +72,9 @@ public:
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);
diff --git a/scene/gui/base_button.cpp b/scene/gui/base_button.cpp
index f4de48689e..776623f7ce 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 */
@@ -43,12 +43,12 @@ void BaseButton::_unpress_group() {
status.pressed = true;
}
- for (Set<BaseButton *>::Element *E = button_group->buttons.front(); E; E = E->next()) {
- if (E->get() == this) {
+ for (BaseButton *E : button_group->buttons) {
+ if (E == this) {
continue;
}
- E->get()->set_pressed(false);
+ E->set_pressed(false);
}
}
@@ -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;
}
}
@@ -331,17 +339,17 @@ bool BaseButton::is_keep_pressed_outside() const {
void BaseButton::set_shortcut(const Ref<Shortcut> &p_shortcut) {
shortcut = p_shortcut;
- set_process_unhandled_key_input(shortcut.is_valid());
+ set_process_shortcut_input(shortcut.is_valid());
}
Ref<Shortcut> BaseButton::get_shortcut() const {
return shortcut;
}
-void BaseButton::unhandled_key_input(const Ref<InputEvent> &p_event) {
+void BaseButton::shortcut_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;
}
@@ -356,7 +364,7 @@ String BaseButton::get_tooltip(const Point2 &p_pos) const {
if (shortcut_in_tooltip && shortcut.is_valid() && shortcut->has_valid_event()) {
String text = shortcut->get_name() + " (" + shortcut->get_as_text() + ")";
if (!tooltip.is_empty() && shortcut->get_name().nocasecmp_to(tooltip) != 0) {
- text += "\n" + tooltip;
+ 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");
@@ -473,24 +485,24 @@ BaseButton::~BaseButton() {
}
void ButtonGroup::get_buttons(List<BaseButton *> *r_buttons) {
- for (Set<BaseButton *>::Element *E = buttons.front(); E; E = E->next()) {
- r_buttons->push_back(E->get());
+ for (BaseButton *E : buttons) {
+ r_buttons->push_back(E);
}
}
Array ButtonGroup::_get_buttons() {
Array btns;
- for (Set<BaseButton *>::Element *E = buttons.front(); E; E = E->next()) {
- btns.push_back(E->get());
+ for (const BaseButton *E : buttons) {
+ btns.push_back(E);
}
return btns;
}
BaseButton *ButtonGroup::get_pressed_button() {
- for (Set<BaseButton *>::Element *E = buttons.front(); E; E = E->next()) {
- if (E->get()->is_pressed()) {
- return E->get();
+ for (BaseButton *E : buttons) {
+ if (E->is_pressed()) {
+ return E;
}
}
@@ -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 3ea59c3ff9..ba3852ec98 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;
@@ -76,10 +77,10 @@ protected:
virtual void toggled(bool p_pressed);
static void _bind_methods();
virtual void gui_input(const Ref<InputEvent> &p_event) override;
- virtual void unhandled_key_input(const Ref<InputEvent> &p_event) override;
+ virtual void shortcut_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)
@@ -142,7 +143,7 @@ VARIANT_ENUM_CAST(BaseButton::ActionMode)
class ButtonGroup : public Resource {
GDCLASS(ButtonGroup, Resource);
friend class BaseButton;
- Set<BaseButton *> buttons;
+ HashSet<BaseButton *> buttons;
protected:
static void _bind_methods();
diff --git a/scene/gui/box_container.cpp b/scene/gui/box_container.cpp
index 6aaa8433ec..df695feba8 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"
@@ -51,7 +52,7 @@ void BoxContainer::_resort() {
int stretch_min = 0;
int stretch_avail = 0;
float stretch_ratio_total = 0.0;
- Map<Control *, _MinSizeCache> min_size_cache;
+ HashMap<Control *, _MinSizeCache> min_size_cache;
for (int i = 0; i < get_child_count(); i++) {
Control *c = Object::cast_to<Control>(get_child(i));
@@ -294,9 +295,11 @@ void BoxContainer::_notification(int p_what) {
case NOTIFICATION_SORT_CHILDREN: {
_resort();
} break;
+
case NOTIFICATION_THEME_CHANGED: {
update_minimum_size();
} break;
+
case NOTIFICATION_TRANSLATION_CHANGED:
case NOTIFICATION_LAYOUT_DIRECTION_CHANGED: {
queue_sort();
@@ -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;
}
diff --git a/scene/gui/box_container.h b/scene/gui/box_container.h
index 9bb26ec32c..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 */
@@ -62,6 +62,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;
+
BoxContainer(bool p_vertical = false);
};
diff --git a/scene/gui/button.cpp b/scene/gui/button.cpp
index 8bb41e7abf..c54897035a 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 */
@@ -53,18 +53,18 @@ Size2 Button::get_minimum_size() const {
if (icon_alignment != HORIZONTAL_ALIGNMENT_CENTER) {
minsize.width += _icon->get_width();
if (!xl_text.is_empty()) {
- minsize.width += get_theme_constant(SNAME("hseparation"));
+ minsize.width += get_theme_constant(SNAME("h_separation"));
}
} else {
minsize.width = MAX(minsize.width, _icon->get_width());
}
}
}
-
- Ref<Font> font = get_theme_font(SNAME("font"));
- float font_height = font->get_height(get_theme_font_size(SNAME("font_size")));
-
- minsize.height = MAX(font_height, minsize.height);
+ if (!xl_text.is_empty()) {
+ Ref<Font> font = get_theme_font(SNAME("font"));
+ float font_height = font->get_height(get_theme_font_size(SNAME("font_size")));
+ minsize.height = MAX(font_height, minsize.height);
+ }
return get_theme_stylebox(SNAME("normal"))->get_minimum_size() + minsize;
}
@@ -78,6 +78,7 @@ void Button::_notification(int p_what) {
case NOTIFICATION_LAYOUT_DIRECTION_CHANGED: {
update();
} break;
+
case NOTIFICATION_TRANSLATION_CHANGED: {
xl_text = atr(text);
_shape();
@@ -85,12 +86,14 @@ void Button::_notification(int p_what) {
update_minimum_size();
update();
} break;
+
case NOTIFICATION_THEME_CHANGED: {
_shape();
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;
@@ -233,9 +237,6 @@ void Button::_notification(int p_what) {
}
if (!_icon.is_null()) {
int valign = size.height - style->get_minimum_size().y;
- if (is_disabled()) {
- color_icon.a = 0.4;
- }
float icon_ofs_region = 0.0;
Point2 style_offset;
@@ -243,21 +244,22 @@ void Button::_notification(int p_what) {
if (icon_align_rtl_checked == HORIZONTAL_ALIGNMENT_LEFT) {
style_offset.x = style->get_margin(SIDE_LEFT);
if (_internal_margin[SIDE_LEFT] > 0) {
- icon_ofs_region = _internal_margin[SIDE_LEFT] + get_theme_constant(SNAME("hseparation"));
+ icon_ofs_region = _internal_margin[SIDE_LEFT] + get_theme_constant(SNAME("h_separation"));
}
} else if (icon_align_rtl_checked == HORIZONTAL_ALIGNMENT_CENTER) {
style_offset.x = 0.0;
} else if (icon_align_rtl_checked == HORIZONTAL_ALIGNMENT_RIGHT) {
style_offset.x = -style->get_margin(SIDE_RIGHT);
if (_internal_margin[SIDE_RIGHT] > 0) {
- icon_ofs_region = -_internal_margin[SIDE_RIGHT] - get_theme_constant(SNAME("hseparation"));
+ icon_ofs_region = -_internal_margin[SIDE_RIGHT] - get_theme_constant(SNAME("h_separation"));
}
}
style_offset.y = style->get_margin(SIDE_TOP);
if (expand_icon) {
Size2 _size = get_size() - style->get_offset() * 2;
- _size.width -= get_theme_constant(SNAME("hseparation")) + icon_ofs_region;
+ int icon_text_separation = text.is_empty() ? 0 : get_theme_constant(SNAME("h_separation"));
+ _size.width -= icon_text_separation + icon_ofs_region;
if (!clip_text && icon_align_rtl_checked != HORIZONTAL_ALIGNMENT_CENTER) {
_size.width -= text_buf->get_size().width;
}
@@ -285,24 +287,26 @@ void Button::_notification(int p_what) {
}
}
- Point2 icon_ofs = !_icon.is_null() ? Point2(icon_region.size.width + get_theme_constant(SNAME("hseparation")), 0) : Point2();
+ Point2 icon_ofs = !_icon.is_null() ? Point2(icon_region.size.width + get_theme_constant(SNAME("h_separation")), 0) : Point2();
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;
text_buf->set_width(clip_text ? text_clip : -1);
- int text_width = clip_text ? MIN(text_clip, text_buf->get_size().x) : text_buf->get_size().x;
+ int text_width = MAX(1, clip_text ? MIN(text_clip, text_buf->get_size().x) : text_buf->get_size().x);
if (_internal_margin[SIDE_LEFT] > 0) {
- text_clip -= _internal_margin[SIDE_LEFT] + get_theme_constant(SNAME("hseparation"));
+ text_clip -= _internal_margin[SIDE_LEFT] + get_theme_constant(SNAME("h_separation"));
}
if (_internal_margin[SIDE_RIGHT] > 0) {
- text_clip -= _internal_margin[SIDE_RIGHT] + get_theme_constant(SNAME("hseparation"));
+ text_clip -= _internal_margin[SIDE_RIGHT] + get_theme_constant(SNAME("h_separation"));
}
Point2 text_ofs = (size - style->get_minimum_size() - icon_ofs - text_buf->get_size() - Point2(_internal_margin[SIDE_RIGHT] - _internal_margin[SIDE_LEFT], 0)) / 2.0;
+ text_buf->set_alignment(align_rtl_checked);
+ text_buf->set_width(text_width);
switch (align_rtl_checked) {
case HORIZONTAL_ALIGNMENT_FILL:
case HORIZONTAL_ALIGNMENT_LEFT: {
@@ -310,7 +314,7 @@ void Button::_notification(int p_what) {
icon_ofs.x = 0.0;
}
if (_internal_margin[SIDE_LEFT] > 0) {
- text_ofs.x = style->get_margin(SIDE_LEFT) + icon_ofs.x + _internal_margin[SIDE_LEFT] + get_theme_constant(SNAME("hseparation"));
+ text_ofs.x = style->get_margin(SIDE_LEFT) + icon_ofs.x + _internal_margin[SIDE_LEFT] + get_theme_constant(SNAME("h_separation"));
} else {
text_ofs.x = style->get_margin(SIDE_LEFT) + icon_ofs.x;
}
@@ -327,7 +331,7 @@ void Button::_notification(int p_what) {
} break;
case HORIZONTAL_ALIGNMENT_RIGHT: {
if (_internal_margin[SIDE_RIGHT] > 0) {
- text_ofs.x = size.x - style->get_margin(SIDE_RIGHT) - text_width - _internal_margin[SIDE_RIGHT] - get_theme_constant(SNAME("hseparation"));
+ text_ofs.x = size.x - style->get_margin(SIDE_RIGHT) - text_width - _internal_margin[SIDE_RIGHT] - get_theme_constant(SNAME("h_separation"));
} else {
text_ofs.x = size.x - style->get_margin(SIDE_RIGHT) - text_width;
}
@@ -498,7 +502,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);
@@ -506,7 +510,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();
@@ -538,7 +542,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));
}
@@ -568,7 +572,7 @@ void Button::_bind_methods() {
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");
@@ -580,8 +584,8 @@ void Button::_bind_methods() {
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 92d86542a6..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 */
diff --git a/scene/gui/center_container.cpp b/scene/gui/center_container.cpp
index e17552006f..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 */
@@ -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 134766b05f..cb80f5b5ef 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 */
@@ -75,7 +75,7 @@ Size2 CheckBox::get_minimum_size() const {
Size2 tex_size = get_icon_size();
minsize.width += tex_size.width;
if (get_text().length() > 0) {
- minsize.width += get_theme_constant(SNAME("hseparation"));
+ minsize.width += get_theme_constant(SNAME("h_separation"));
}
Ref<StyleBox> sb = get_theme_stylebox(SNAME("normal"));
minsize.height = MAX(minsize.height, tex_size.height + sb->get_margin(SIDE_TOP) + sb->get_margin(SIDE_BOTTOM));
@@ -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_v_adjust"));
+
+ if (is_pressed()) {
+ on->draw(ci, ofs);
+ } else {
+ off->draw(ci, ofs);
+ }
+ } break;
}
}
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 499bedda52..a09873ea4f 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 */
@@ -52,7 +52,7 @@ Size2 CheckButton::get_minimum_size() const {
Size2 tex_size = get_icon_size();
minsize.width += tex_size.width;
if (get_text().length() > 0) {
- minsize.width += get_theme_constant(SNAME("hseparation"));
+ minsize.width += get_theme_constant(SNAME("h_separation"));
}
Ref<StyleBox> sb = get_theme_stylebox(SNAME("normal"));
minsize.height = MAX(minsize.height, tex_size.height + sb->get_margin(SIDE_TOP) + sb->get_margin(SIDE_BOTTOM));
@@ -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_v_adjust"));
+
+ 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_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 ba37d51e24..22e9763929 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();
@@ -113,7 +106,7 @@ void CodeEdit::_notification(int p_what) {
const int code_completion_options_count = code_completion_options.size();
const int lines = MIN(code_completion_options_count, code_completion_max_lines);
- const int icon_hsep = get_theme_constant(SNAME("hseparation"), SNAME("ItemList"));
+ const int icon_hsep = get_theme_constant(SNAME("h_separation"), SNAME("ItemList"));
const Size2 icon_area_size(row_height, row_height);
code_completion_rect.size.width = code_completion_longest_line + icon_hsep + icon_area_size.width + 2;
@@ -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;
@@ -177,6 +169,17 @@ void CodeEdit::_notification(int p_what) {
}
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);
}
@@ -222,7 +225,7 @@ 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;
}
@@ -354,6 +357,11 @@ void CodeEdit::gui_input(const Ref<InputEvent> &p_gui_input) {
}
Ref<InputEventKey> k = p_gui_input;
+ if (TextEdit::alt_input(p_gui_input)) {
+ accept_event();
+ return;
+ }
+
bool update_code_completion = false;
if (!k.is_valid()) {
TextEdit::gui_input(p_gui_input);
@@ -561,6 +569,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();
@@ -581,27 +591,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);
}
@@ -719,8 +740,8 @@ void CodeEdit::set_auto_indent_prefixes(const TypedArray<String> &p_prefixes) {
TypedArray<String> CodeEdit::get_auto_indent_prefixes() const {
TypedArray<String> prefixes;
- for (const Set<char32_t>::Element *E = auto_indent_prefixes.front(); E; E = E->next()) {
- prefixes.push_back(String::chr(E->get()));
+ for (const char32_t &E : auto_indent_prefixes) {
+ prefixes.push_back(String::chr(E));
}
return prefixes;
}
@@ -927,8 +948,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";
@@ -976,7 +999,8 @@ 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)) {
+ /* Increment column for comments because the delimiter (#) should be ignored. */
+ if (should_indent && (!is_whitespace(c) && is_in_comment(cl, line_col + 1) == -1)) {
should_indent = false;
}
}
@@ -1002,6 +1026,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);
@@ -1602,7 +1628,7 @@ Point2 CodeEdit::get_delimiter_start_position(int p_line, int p_column) const {
start_position.y = -1;
start_position.x = -1;
- bool in_region = ((p_line <= 0 || delimiter_cache[p_line - 1].size() < 1) ? -1 : delimiter_cache[p_line - 1].back()->value()) != -1;
+ bool in_region = ((p_line <= 0 || delimiter_cache[p_line - 1].size() < 1) ? -1 : delimiter_cache[p_line - 1].back()->get()) != -1;
/* Check the keys for this line. */
for (const KeyValue<int, int> &E : delimiter_cache[p_line]) {
@@ -1726,8 +1752,8 @@ void CodeEdit::set_code_completion_prefixes(const TypedArray<String> &p_prefixes
TypedArray<String> CodeEdit::get_code_completion_prefixes() const {
TypedArray<String> prefixes;
- for (const Set<char32_t>::Element *E = code_completion_prefixes.front(); E; E = E->next()) {
- prefixes.push_back(String::chr(E->get()));
+ for (const char32_t &E : code_completion_prefixes) {
+ prefixes.push_back(String::chr(E));
}
return prefixes;
}
@@ -1762,10 +1788,10 @@ void CodeEdit::request_code_completion(bool p_force) {
/* Don't re-query if all existing options are quoted types, eg path, signal. */
bool ignored = code_completion_active && !code_completion_options.is_empty();
if (ignored) {
- ScriptCodeCompletionOption::Kind kind = ScriptCodeCompletionOption::KIND_PLAIN_TEXT;
- const ScriptCodeCompletionOption *previous_option = nullptr;
+ ScriptLanguage::CodeCompletionKind kind = ScriptLanguage::CODE_COMPLETION_KIND_PLAIN_TEXT;
+ const ScriptLanguage::CodeCompletionOption *previous_option = nullptr;
for (int i = 0; i < code_completion_options.size(); i++) {
- const ScriptCodeCompletionOption &current_option = code_completion_options[i];
+ const ScriptLanguage::CodeCompletionOption &current_option = code_completion_options[i];
if (!previous_option) {
previous_option = &current_option;
kind = current_option.kind;
@@ -1775,7 +1801,7 @@ void CodeEdit::request_code_completion(bool p_force) {
break;
}
}
- ignored = ignored && (kind == ScriptCodeCompletionOption::KIND_FILE_PATH || kind == ScriptCodeCompletionOption::KIND_NODE_PATH || kind == ScriptCodeCompletionOption::KIND_SIGNAL);
+ ignored = ignored && (kind == ScriptLanguage::CODE_COMPLETION_KIND_FILE_PATH || kind == ScriptLanguage::CODE_COMPLETION_KIND_NODE_PATH || kind == ScriptLanguage::CODE_COMPLETION_KIND_SIGNAL);
}
if (ignored) {
@@ -1783,23 +1809,23 @@ 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"));
}
}
-void CodeEdit::add_code_completion_option(CodeCompletionKind p_type, const String &p_display_text, const String &p_insert_text, const Color &p_text_color, const RES &p_icon, const Variant &p_value) {
- ScriptCodeCompletionOption completion_option;
- completion_option.kind = (ScriptCodeCompletionOption::Kind)p_type;
+void CodeEdit::add_code_completion_option(CodeCompletionKind p_type, const String &p_display_text, const String &p_insert_text, const Color &p_text_color, const Ref<Resource> &p_icon, const Variant &p_value) {
+ ScriptLanguage::CodeCompletionOption completion_option;
+ completion_option.kind = (ScriptLanguage::CodeCompletionKind)p_type;
completion_option.display = p_display_text;
completion_option.insert_text = p_insert_text;
completion_option.font_color = p_text_color;
@@ -1899,7 +1925,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;
}
}
@@ -2075,8 +2101,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);
@@ -2105,16 +2129,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);
@@ -2181,7 +2201,7 @@ void CodeEdit::_bind_methods() {
ClassDB::bind_method(D_METHOD("get_text_for_code_completion"), &CodeEdit::get_text_for_code_completion);
ClassDB::bind_method(D_METHOD("request_code_completion", "force"), &CodeEdit::request_code_completion, DEFVAL(false));
- ClassDB::bind_method(D_METHOD("add_code_completion_option", "type", "display_text", "insert_text", "text_color", "icon", "value"), &CodeEdit::add_code_completion_option, DEFVAL(Color(1, 1, 1)), DEFVAL(RES()), DEFVAL(Variant::NIL));
+ ClassDB::bind_method(D_METHOD("add_code_completion_option", "type", "display_text", "insert_text", "text_color", "icon", "value"), &CodeEdit::add_code_completion_option, DEFVAL(Color(1, 1, 1)), DEFVAL(Ref<Resource>()), DEFVAL(Variant::NIL));
ClassDB::bind_method(D_METHOD("update_code_completion_options", "force"), &CodeEdit::update_code_completion_options);
ClassDB::bind_method(D_METHOD("get_code_completion_options"), &CodeEdit::get_code_completion_options);
ClassDB::bind_method(D_METHOD("get_code_completion_option", "index"), &CodeEdit::get_code_completion_option);
@@ -2247,7 +2267,7 @@ void CodeEdit::_bind_methods() {
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "indent_automatic"), "set_auto_indent_enabled", "is_auto_indent_enabled");
ADD_PROPERTY(PropertyInfo(Variant::PACKED_STRING_ARRAY, "indent_automatic_prefixes"), "set_auto_indent_prefixes", "get_auto_indent_prefixes");
- ADD_GROUP("Auto brace completion", "auto_brace_completion_");
+ ADD_GROUP("Auto Brace Completion", "auto_brace_completion_");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "auto_brace_completion_enabled"), "set_auto_brace_completion_enabled", "is_auto_brace_completion_enabled");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "auto_brace_completion_highlight_matching"), "set_highlight_matching_braces_enabled", "is_highlight_matching_braces_enabled");
ADD_PROPERTY(PropertyInfo(Variant::DICTIONARY, "auto_brace_completion_pairs"), "set_auto_brace_completion_pairs", "get_auto_brace_completion_pairs");
@@ -2257,7 +2277,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")));
@@ -2382,7 +2402,7 @@ void CodeEdit::_update_delimiter_cache(int p_from_line, int p_to_line) {
}
} else {
for (int i = start_line; i < end_line; i++) {
- delimiter_cache.insert(i, Map<int, int>());
+ delimiter_cache.insert(i, RBMap<int, int>());
}
}
}
@@ -2519,7 +2539,7 @@ int CodeEdit::_is_in_delimiter(int p_line, int p_column, DelimiterType p_type) c
int region = (p_line <= 0 || delimiter_cache[p_line - 1].size() < 1) ? -1 : delimiter_cache[p_line - 1].back()->value();
bool in_region = region != -1 && delimiters[region].type == p_type;
- for (Map<int, int>::Element *E = delimiter_cache[p_line].front(); E; E = E->next()) {
+ for (RBMap<int, int>::Element *E = delimiter_cache[p_line].front(); E; E = E->next()) {
/* If column is specified, loop until the key is larger then the column. */
if (p_column != -1) {
if (E->key() > p_column) {
@@ -2541,7 +2561,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;
}
}
@@ -2560,7 +2580,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;
}
}
@@ -2688,7 +2708,7 @@ void CodeEdit::_filter_code_completion_candidates_impl() {
TypedArray<Dictionary> completion_options_sources;
completion_options_sources.resize(code_completion_option_sources.size());
int i = 0;
- for (const ScriptCodeCompletionOption &E : code_completion_option_sources) {
+ for (const ScriptLanguage::CodeCompletionOption &E : code_completion_option_sources) {
Dictionary option;
option["kind"] = E.kind;
option["display_text"] = E.display;
@@ -2713,8 +2733,8 @@ void CodeEdit::_filter_code_completion_candidates_impl() {
/* Convert back into options. */
int max_width = 0;
for (i = 0; i < completion_options.size(); i++) {
- ScriptCodeCompletionOption option;
- option.kind = (ScriptCodeCompletionOption::Kind)(int)completion_options[i].get("kind");
+ ScriptLanguage::CodeCompletionOption option;
+ option.kind = (ScriptLanguage::CodeCompletionKind)(int)completion_options[i].get("kind");
option.display = completion_options[i].get("display_text");
option.insert_text = completion_options[i].get("insert_text");
option.font_color = completion_options[i].get("font_color");
@@ -2776,11 +2796,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);
@@ -2807,13 +2827,15 @@ void CodeEdit::_filter_code_completion_candidates_impl() {
code_completion_options.clear();
code_completion_base = string_to_complete;
- Vector<ScriptCodeCompletionOption> completion_options_casei;
- Vector<ScriptCodeCompletionOption> completion_options_subseq;
- Vector<ScriptCodeCompletionOption> completion_options_subseq_casei;
+ Vector<ScriptLanguage::CodeCompletionOption> completion_options_casei;
+ Vector<ScriptLanguage::CodeCompletionOption> completion_options_substr;
+ Vector<ScriptLanguage::CodeCompletionOption> completion_options_substr_casei;
+ Vector<ScriptLanguage::CodeCompletionOption> completion_options_subseq;
+ Vector<ScriptLanguage::CodeCompletionOption> completion_options_subseq_casei;
int max_width = 0;
String string_to_complete_lower = string_to_complete.to_lower();
- for (ScriptCodeCompletionOption &option : code_completion_option_sources) {
+ for (ScriptLanguage::CodeCompletionOption &option : code_completion_option_sources) {
if (single_quote && option.display.is_quoted()) {
option.display = option.display.unquote().quote("'");
}
@@ -2847,7 +2869,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);
}
@@ -2862,35 +2884,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 (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;
+ }
- for (; *tgt; tgt++, tgt_lower++) {
+ // 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);
@@ -2954,7 +3041,9 @@ void CodeEdit::_text_changed() {
lc = get_line_count();
List<int> breakpoints;
- breakpointed_lines.get_key_list(&breakpoints);
+ for (const KeyValue<int, bool> &E : breakpointed_lines) {
+ breakpoints.push_back(E.key);
+ }
for (const int &line : breakpoints) {
if (line < lines_edited_from || (line < lc && is_line_breakpointed(line))) {
continue;
@@ -2990,7 +3079,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);
@@ -3007,7 +3096,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 */
@@ -3015,7 +3104,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 */
@@ -3023,7 +3112,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..ccf046c612 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 */
@@ -38,7 +38,7 @@ class CodeEdit : public TextEdit {
public:
/* Keep enum in sync with: */
- /* /core/object/script_language.h - ScriptCodeCompletionOption::Kind */
+ /* /core/object/script_language.h - ScriptLanguage::CodeCompletionKind */
enum CodeCompletionKind {
KIND_CLASS,
KIND_FUNCTION,
@@ -58,7 +58,7 @@ private:
String indent_text = "\t";
bool auto_indent = false;
- Set<char32_t> auto_indent_prefixes;
+ HashSet<char32_t> auto_indent_prefixes;
bool indent_using_spaces = false;
int _calculate_spaces_till_next_left_indent(int p_column) const;
@@ -176,7 +176,7 @@ private:
* ]
* ]
*/
- Vector<Map<int, int>> delimiter_cache;
+ Vector<RBMap<int, int>> delimiter_cache;
void _update_delimiter_cache(int p_from_line = 0, int p_to_line = -1);
int _is_in_delimiter(int p_line, int p_column, DelimiterType p_type) const;
@@ -208,15 +208,15 @@ private:
Color code_completion_existing_color = Color(0, 0, 0, 0);
bool code_completion_active = false;
- Vector<ScriptCodeCompletionOption> code_completion_options;
+ Vector<ScriptLanguage::CodeCompletionOption> code_completion_options;
int code_completion_line_ofs = 0;
int code_completion_current_selected = 0;
int code_completion_longest_line = 0;
Rect2i code_completion_rect;
- Set<char32_t> code_completion_prefixes;
- List<ScriptCodeCompletionOption> code_completion_option_submitted;
- List<ScriptCodeCompletionOption> code_completion_option_sources;
+ HashSet<char32_t> code_completion_prefixes;
+ List<ScriptLanguage::CodeCompletionOption> code_completion_option_submitted;
+ List<ScriptLanguage::CodeCompletionOption> code_completion_option_sources;
String code_completion_base;
void _filter_code_completion_candidates_impl();
@@ -398,7 +398,7 @@ public:
void request_code_completion(bool p_force = false);
- void add_code_completion_option(CodeCompletionKind p_type, const String &p_display_text, const String &p_insert_text, const Color &p_text_color = Color(1, 1, 1), const RES &p_icon = RES(), const Variant &p_value = Variant::NIL);
+ void add_code_completion_option(CodeCompletionKind p_type, const String &p_display_text, const String &p_insert_text, const Color &p_text_color = Color(1, 1, 1), const Ref<Resource> &p_icon = Ref<Resource>(), const Variant &p_value = Variant::NIL);
void update_code_completion_options(bool p_forced = false);
TypedArray<Dictionary> get_code_completion_options() const;
diff --git a/scene/gui/color_picker.cpp b/scene/gui/color_picker.cpp
index 63ffe7bf95..6f7ad94139 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()) {
@@ -437,7 +436,7 @@ ColorPicker::PickerShapeType ColorPicker::get_picker_shape() const {
}
inline int ColorPicker::_get_preset_size() {
- return (int(get_minimum_size().width) - (preset_container->get_theme_constant(SNAME("hseparation")) * (preset_column_count - 1))) / preset_column_count;
+ return (int(get_minimum_size().width) - (preset_container->get_theme_constant(SNAME("h_separation")) * (preset_column_count - 1))) / preset_column_count;
}
void ColorPicker::_add_preset_button(int p_size, const Color &p_color) {
@@ -1222,7 +1221,7 @@ ColorPicker::ColorPicker() :
hhb->add_child(text_type);
text_type->set_text("#");
- text_type->set_tooltip(TTR("Switch between hexadecimal and code values."));
+ text_type->set_tooltip(RTR("Switch between hexadecimal and code values."));
if (Engine::get_singleton()->is_editor_hint()) {
text_type->connect("pressed", callable_mp(this, &ColorPicker::_text_type_toggled));
} else {
@@ -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 81afa53d85..5512c0f1fd 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,6 +29,7 @@
/*************************************************************************/
#include "container.h"
+
#include "core/object/message_queue.h"
#include "scene/scene_string_names.h"
@@ -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();
@@ -167,7 +196,7 @@ TypedArray<String> Container::get_configuration_warnings() const {
TypedArray<String> warnings = Control::get_configuration_warnings();
if (get_class() == "Container" && get_script().is_null()) {
- warnings.push_back(TTR("Container by itself serves no purpose unless a script configures its children placement behavior.\nIf you don't intend to add a script, use a plain Control node instead."));
+ warnings.push_back(RTR("Container by itself serves no purpose unless a script configures its children placement behavior.\nIf you don't intend to add a script, use a plain Control node instead."));
}
return warnings;
@@ -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 1fdc30eb6d..db78b4adb6 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() {
@@ -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 {
@@ -353,7 +367,7 @@ bool Control::_get(const StringName &p_name, Variant &r_ret) const {
void Control::_get_property_list(List<PropertyInfo> *p_list) const {
Ref<Theme> theme = Theme::get_default();
- p_list->push_back(PropertyInfo(Variant::NIL, "Theme Overrides", PROPERTY_HINT_NONE, "theme_override_", PROPERTY_USAGE_GROUP));
+ p_list->push_back(PropertyInfo(Variant::NIL, TTRC("Theme Overrides"), PROPERTY_HINT_NONE, "theme_override_", PROPERTY_USAGE_GROUP));
{
List<StringName> names;
@@ -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,106 @@ void Control::_validate_property(PropertyInfo &property) const {
property.hint_string = hint_string;
}
- if (!Object::cast_to<Container>(get_parent())) {
- return;
+
+ if (property.name == "mouse_force_pass_scroll_events") {
+ // Disable force pass if the control is not stopping the event.
+ if (data.mouse_filter != MOUSE_FILTER_STOP) {
+ property.usage |= PROPERTY_USAGE_READ_ONLY;
+ }
}
+
+ // 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 +687,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);
@@ -602,35 +719,25 @@ void Control::_notification(int p_notification) {
data.parent_window = Object::cast_to<Window>(get_parent());
data.is_rtl_dirty = true;
- Node *parent = this; //meh
+ CanvasItem *node = this;
Control *parent_control = nullptr;
- bool subwindow = false;
-
- while (parent) {
- parent = parent->get_parent();
+ while (!node->is_set_as_top_level()) {
+ CanvasItem *parent = Object::cast_to<CanvasItem>(node->get_parent());
if (!parent) {
break;
}
- CanvasItem *ci = Object::cast_to<CanvasItem>(parent);
- if (ci && ci->is_set_as_top_level()) {
- subwindow = true;
- break;
- }
-
parent_control = Object::cast_to<Control>(parent);
-
if (parent_control) {
break;
- } else if (ci) {
- } else {
- break;
}
+
+ node = parent;
}
- if (parent_control && !subwindow) {
- //do nothing, has a parent control and not top_level
+ if (parent_control) {
+ // Do nothing, has a parent control.
if (data.theme.is_null() && parent_control->data.theme_owner) {
data.theme_owner = parent_control->data.theme_owner;
notification(NOTIFICATION_THEME_CHANGED);
@@ -653,6 +760,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 +781,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 +794,59 @@ 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: {
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;
+ _update_minimum_size();
_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;
}
}
@@ -1148,12 +1261,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();
@@ -1175,13 +1288,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 {
@@ -1196,12 +1312,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();
@@ -1223,13 +1339,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 {
@@ -1244,12 +1363,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();
@@ -1271,13 +1390,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 {
@@ -1296,7 +1418,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();
}
@@ -1373,6 +1495,55 @@ 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 ((int)get_meta("_edit_layout_mode", p_mode) != (int)p_mode) {
+ list_changed = true;
+ }
+
+ set_meta("_edit_layout_mode", (int)p_mode);
+
+ if (p_mode == LayoutMode::LAYOUT_MODE_POSITION) {
+ remove_meta("_edit_layout_mode");
+ remove_meta("_edit_use_custom_anchors");
+ set_anchors_and_offsets_preset(LayoutPreset::PRESET_TOP_LEFT, LayoutPresetMode::PRESET_MODE_KEEP_SIZE);
+ set_grow_direction_preset(LayoutPreset::PRESET_TOP_LEFT);
+ }
+ } else {
+ if (has_meta("_edit_layout_mode")) {
+ remove_meta("_edit_layout_mode");
+ list_changed = true;
+ }
+ }
+
+ if (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);
@@ -1414,6 +1585,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 (get_meta("_edit_layout_mode", LayoutMode::LAYOUT_MODE_ANCHORS).operator int() != LayoutMode::LAYOUT_MODE_ANCHORS) {
+ list_changed = true;
+ set_meta("_edit_layout_mode", LayoutMode::LAYOUT_MODE_ANCHORS);
+ }
+
+ if (p_preset == -1) {
+ if (!get_meta("_edit_use_custom_anchors", false)) {
+ set_meta("_edit_use_custom_anchors", true);
+ notify_property_list_changed();
+ }
+ return; // Keep settings as is.
+ }
+
+ if (get_meta("_edit_use_custom_anchors", true)) {
+ list_changed = true;
+ remove_meta("_edit_use_custom_anchors");
+ }
+
+ 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 ((bool)get_meta("_edit_use_custom_anchors", false)) {
+ 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);
@@ -1670,6 +1968,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);
@@ -1715,7 +2069,7 @@ Point2 Control::get_global_position() const {
Point2 Control::get_screen_position() const {
ERR_FAIL_COND_V(!is_inside_tree(), Point2());
- Point2 global_pos = get_viewport()->get_canvas_transform().xform(get_global_position());
+ Point2 global_pos = get_global_transform_with_canvas().get_origin();
Window *w = Object::cast_to<Window>(get_viewport());
if (w && !w->is_embedding_subwindows()) {
global_pos += w->get_position();
@@ -2179,8 +2533,7 @@ void Control::release_focus() {
return;
}
- get_viewport()->_gui_remove_focus();
- update();
+ get_viewport()->gui_release_focus();
}
bool Control::is_top_level_control() const {
@@ -2576,6 +2929,7 @@ int Control::get_v_size_flags() const {
void Control::set_mouse_filter(MouseFilter p_filter) {
ERR_FAIL_INDEX(p_filter, 3);
data.mouse_filter = p_filter;
+ notify_property_list_changed();
update_configuration_warnings();
}
@@ -2583,104 +2937,34 @@ 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::set_force_pass_scroll_events(bool p_force_pass_scroll_events) {
+ data.force_pass_scroll_events = p_force_pass_scroll_events;
+}
+
+bool Control::is_force_pass_scroll_events() const {
+ return data.force_pass_scroll_events;
}
-void Control::warp_mouse(const Point2 &p_to_pos) {
+void Control::warp_mouse(const Point2 &p_position) {
ERR_FAIL_COND(!is_inside_tree());
- get_viewport()->warp_mouse(get_global_transform().xform(p_to_pos));
+ get_viewport()->warp_mouse(get_global_transform_with_canvas().xform(p_position));
}
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 ret;
- switch (p_theme_type) {
- case STRUCTURED_TEXT_URI: {
- int prev = 0;
- for (int i = 0; i < p_text.length(); i++) {
- if ((p_text[i] == '\\') || (p_text[i] == '/') || (p_text[i] == '.') || (p_text[i] == ':') || (p_text[i] == '&') || (p_text[i] == '=') || (p_text[i] == '@') || (p_text[i] == '?') || (p_text[i] == '#')) {
- if (prev != i) {
- ret.push_back(Vector2i(prev, i));
- }
- ret.push_back(Vector2i(i, i + 1));
- prev = i + 1;
- }
- }
- if (prev != p_text.length()) {
- ret.push_back(Vector2i(prev, p_text.length()));
- }
- } break;
- case STRUCTURED_TEXT_FILE: {
- int prev = 0;
- for (int i = 0; i < p_text.length(); i++) {
- if ((p_text[i] == '\\') || (p_text[i] == '/') || (p_text[i] == ':')) {
- if (prev != i) {
- ret.push_back(Vector2i(prev, i));
- }
- ret.push_back(Vector2i(i, i + 1));
- prev = i + 1;
- }
- }
- if (prev != p_text.length()) {
- ret.push_back(Vector2i(prev, p_text.length()));
- }
- } break;
- case STRUCTURED_TEXT_EMAIL: {
- bool local = true;
- int prev = 0;
- for (int i = 0; i < p_text.length(); i++) {
- if ((p_text[i] == '@') && local) { // Add full "local" as single context.
- local = false;
- ret.push_back(Vector2i(prev, i));
- ret.push_back(Vector2i(i, i + 1));
- prev = i + 1;
- } else if (!local & (p_text[i] == '.')) { // Add each dot separated "domain" part as context.
- if (prev != i) {
- ret.push_back(Vector2i(prev, i));
- }
- ret.push_back(Vector2i(i, i + 1));
- prev = i + 1;
- }
- }
- if (prev != p_text.length()) {
- ret.push_back(Vector2i(prev, p_text.length()));
- }
- } break;
- case STRUCTURED_TEXT_LIST: {
- if (p_args.size() == 1 && p_args[0].get_type() == Variant::STRING) {
- Vector<String> tags = p_text.split(String(p_args[0]));
- int prev = 0;
- for (int i = 0; i < tags.size(); i++) {
- if (prev != i) {
- ret.push_back(Vector2i(prev, prev + tags[i].length()));
- }
- ret.push_back(Vector2i(prev + tags[i].length(), prev + tags[i].length() + 1));
- prev = prev + tags[i].length() + 1;
- }
- }
- } break;
- case STRUCTURED_TEXT_CUSTOM: {
- Array r;
- if (GDVIRTUAL_CALL(_structured_text_parser, p_args, p_text, r)) {
- for (int i = 0; i < r.size(); i++) {
- if (r[i].get_type() == Variant::VECTOR2I) {
- ret.push_back(Vector2i(r[i]));
- }
- }
- }
- } break;
- case STRUCTURED_TEXT_NONE:
- case STRUCTURED_TEXT_DEFAULT:
- default: {
- ret.push_back(Vector2i(0, p_text.length()));
+Array Control::structured_text_parser(TextServer::StructuredTextParser p_parser_type, const Array &p_args, const String &p_text) const {
+ if (p_parser_type == TextServer::STRUCTURED_TEXT_CUSTOM) {
+ Array ret;
+ if (GDVIRTUAL_CALL(_structured_text_parser, p_args, p_text, ret)) {
+ return ret;
+ } else {
+ return Array();
}
+ } else {
+ return TS->parse_structured_text(p_parser_type, p_args, p_text);
}
- return ret;
}
void Control::set_rotation(real_t p_radians) {
@@ -2792,7 +3076,7 @@ TypedArray<String> Control::get_configuration_warnings() const {
TypedArray<String> warnings = Node::get_configuration_warnings();
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\"."));
+ warnings.push_back(RTR("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\"."));
}
return warnings;
@@ -2836,14 +3120,22 @@ 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));
@@ -2857,7 +3149,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,6 +3159,7 @@ void Control::_bind_methods() {
ClassDB::bind_method(D_METHOD("get_custom_minimum_size"), &Control::get_custom_minimum_size);
ClassDB::bind_method(D_METHOD("get_parent_area_size"), &Control::get_parent_area_size);
ClassDB::bind_method(D_METHOD("get_global_position"), &Control::get_global_position);
+ ClassDB::bind_method(D_METHOD("get_screen_position"), &Control::get_screen_position);
ClassDB::bind_method(D_METHOD("get_rect"), &Control::get_rect);
ClassDB::bind_method(D_METHOD("get_global_rect"), &Control::get_global_rect);
ClassDB::bind_method(D_METHOD("set_focus_mode", "mode"), &Control::set_focus_mode);
@@ -2877,7 +3169,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);
@@ -2966,6 +3257,9 @@ void Control::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_mouse_filter", "filter"), &Control::set_mouse_filter);
ClassDB::bind_method(D_METHOD("get_mouse_filter"), &Control::get_mouse_filter);
+ ClassDB::bind_method(D_METHOD("set_force_pass_scroll_events", "force_pass_scroll_events"), &Control::set_force_pass_scroll_events);
+ ClassDB::bind_method(D_METHOD("is_force_pass_scroll_events"), &Control::is_force_pass_scroll_events);
+
ClassDB::bind_method(D_METHOD("set_clip_contents", "enable"), &Control::set_clip_contents);
ClassDB::bind_method(D_METHOD("is_clipping_contents"), &Control::is_clipping_contents);
@@ -2975,7 +3269,7 @@ void Control::_bind_methods() {
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("warp_mouse", "position"), &Control::warp_mouse);
ClassDB::bind_method(D_METHOD("update_minimum_size"), &Control::update_minimum_size);
@@ -2986,38 +3280,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_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("Anchor Offsets", "offset_", 1);
+ ADD_PROPERTYI(PropertyInfo(Variant::INT, "offset_left", PROPERTY_HINT_RANGE, "-4096,4096,suffix:px"), "set_offset", "get_offset", SIDE_LEFT);
+ ADD_PROPERTYI(PropertyInfo(Variant::INT, "offset_top", PROPERTY_HINT_RANGE, "-4096,4096,suffix:px"), "set_offset", "get_offset", SIDE_TOP);
+ ADD_PROPERTYI(PropertyInfo(Variant::INT, "offset_right", PROPERTY_HINT_RANGE, "-4096,4096,suffix:px"), "set_offset", "get_offset", SIDE_RIGHT);
+ ADD_PROPERTYI(PropertyInfo(Variant::INT, "offset_bottom", PROPERTY_HINT_RANGE, "-4096,4096,suffix:px"), "set_offset", "get_offset", SIDE_BOTTOM);
+
+ 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, "suffix:px", PROPERTY_USAGE_EDITOR), "_set_size", "get_size");
+ ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "position", PROPERTY_HINT_NONE, "suffix:px", PROPERTY_USAGE_EDITOR), "_set_position", "get_position");
+ ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "global_position", PROPERTY_HINT_NONE, "suffix:px", PROPERTY_USAGE_NONE), "_set_global_position", "get_global_position");
+ ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "rotation", PROPERTY_HINT_RANGE, "-360,360,0.1,or_lesser,or_greater,radians"), "set_rotation", "get_rotation");
+ ADD_PROPERTY(PropertyInfo(Variant::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");
@@ -3032,13 +3341,9 @@ void Control::_bind_methods() {
ADD_GROUP("Mouse", "mouse_");
ADD_PROPERTY(PropertyInfo(Variant::INT, "mouse_filter", PROPERTY_HINT_ENUM, "Stop,Pass,Ignore"), "set_mouse_filter", "get_mouse_filter");
+ ADD_PROPERTY(PropertyInfo(Variant::BOOL, "mouse_force_pass_scroll_events"), "set_force_pass_scroll_events", "is_force_pass_scroll_events");
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");
@@ -3097,6 +3402,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);
@@ -3124,14 +3430,6 @@ void Control::_bind_methods() {
BIND_ENUM_CONSTANT(TEXT_DIRECTION_LTR);
BIND_ENUM_CONSTANT(TEXT_DIRECTION_RTL);
- BIND_ENUM_CONSTANT(STRUCTURED_TEXT_DEFAULT);
- BIND_ENUM_CONSTANT(STRUCTURED_TEXT_URI);
- BIND_ENUM_CONSTANT(STRUCTURED_TEXT_FILE);
- BIND_ENUM_CONSTANT(STRUCTURED_TEXT_EMAIL);
- BIND_ENUM_CONSTANT(STRUCTURED_TEXT_LIST);
- BIND_ENUM_CONSTANT(STRUCTURED_TEXT_NONE);
- BIND_ENUM_CONSTANT(STRUCTURED_TEXT_CUSTOM);
-
ADD_SIGNAL(MethodInfo("resized"));
ADD_SIGNAL(MethodInfo("gui_input", PropertyInfo(Variant::OBJECT, "event", PROPERTY_HINT_RESOURCE_TYPE, "InputEvent")));
ADD_SIGNAL(MethodInfo("mouse_entered"));
diff --git a/scene/gui/control.h b/scene/gui/control.h
index ae9ca1489a..f18dd99bff 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"
@@ -46,7 +44,6 @@ class Panel;
class Control : public CanvasItem {
GDCLASS(Control, CanvasItem);
- OBJ_CATEGORY("GUI Nodes");
public:
enum Anchor {
@@ -67,12 +64,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 +126,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,
@@ -142,16 +147,6 @@ public:
TEXT_DIRECTION_INHERITED,
};
- enum StructuredTextParser {
- STRUCTURED_TEXT_DEFAULT,
- STRUCTURED_TEXT_URI,
- STRUCTURED_TEXT_FILE,
- STRUCTURED_TEXT_EMAIL,
- STRUCTURED_TEXT_LIST,
- STRUCTURED_TEXT_NONE,
- STRUCTURED_TEXT_CUSTOM
- };
-
private:
struct CComparator {
bool operator()(const Control *p_a, const Control *p_b) const {
@@ -195,6 +190,7 @@ private:
Point2 custom_minimum_size;
MouseFilter mouse_filter = MOUSE_FILTER_STOP;
+ bool force_pass_scroll_events = true;
bool clip_contents = false;
@@ -221,16 +217,16 @@ private:
NodePath focus_prev;
bool bulk_theme_override = false;
- HashMap<StringName, Ref<Texture2D>> icon_override;
- HashMap<StringName, Ref<StyleBox>> style_override;
- HashMap<StringName, Ref<Font>> font_override;
- HashMap<StringName, int> font_size_override;
- HashMap<StringName, Color> color_override;
- HashMap<StringName, int> constant_override;
+ Theme::ThemeIconMap icon_override;
+ Theme::ThemeStyleMap style_override;
+ Theme::ThemeFontMap font_override;
+ Theme::ThemeFontSizeMap font_size_override;
+ Theme::ThemeColorMap color_override;
+ Theme::ThemeConstantMap constant_override;
} 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 +237,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 +281,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(TextServer::StructuredTextParser p_parser_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
@@ -378,6 +381,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;
@@ -462,11 +466,12 @@ 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;
+ void set_force_pass_scroll_events(bool p_force_pass_scroll_events);
+ bool is_force_pass_scroll_events() const;
+
/* SKINNING */
void begin_bulk_theme_override();
@@ -536,7 +541,7 @@ public:
void grab_click_focus();
- void warp_mouse(const Point2 &p_to_pos);
+ void warp_mouse(const Point2 &p_position);
virtual bool is_text_field() const;
@@ -565,8 +570,8 @@ 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);
#endif
diff --git a/scene/gui/dialogs.cpp b/scene/gui/dialogs.cpp
index f1725e57ea..0bb96a18a5 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,24 +33,19 @@
#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 (close_on_escape && key.is_valid() && key->is_pressed() && key->get_keycode() == Key::ESCAPE) {
_cancel_pressed();
}
}
void AcceptDialog::_parent_focused() {
- if (!is_exclusive()) {
+ if (close_on_escape && !is_exclusive()) {
_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;
@@ -94,6 +93,9 @@ void AcceptDialog::_notification(int p_what) {
}
void AcceptDialog::_text_submitted(const String &p_text) {
+ if (get_ok_button() && get_ok_button()->is_disabled()) {
+ return; // Do not allow submission if OK button is disabled.
+ }
_ok_pressed();
}
@@ -143,6 +145,14 @@ bool AcceptDialog::get_hide_on_ok() const {
return hide_on_ok;
}
+void AcceptDialog::set_close_on_escape(bool p_hide) {
+ close_on_escape = p_hide;
+}
+
+bool AcceptDialog::get_close_on_escape() const {
+ return close_on_escape;
+}
+
void AcceptDialog::set_autowrap(bool p_autowrap) {
label->set_autowrap_mode(p_autowrap ? Label::AUTOWRAP_WORD : Label::AUTOWRAP_OFF);
}
@@ -286,6 +296,8 @@ void AcceptDialog::_bind_methods() {
ClassDB::bind_method(D_METHOD("get_label"), &AcceptDialog::get_label);
ClassDB::bind_method(D_METHOD("set_hide_on_ok", "enabled"), &AcceptDialog::set_hide_on_ok);
ClassDB::bind_method(D_METHOD("get_hide_on_ok"), &AcceptDialog::get_hide_on_ok);
+ ClassDB::bind_method(D_METHOD("set_close_on_escape", "enabled"), &AcceptDialog::set_close_on_escape);
+ ClassDB::bind_method(D_METHOD("get_close_on_escape"), &AcceptDialog::get_close_on_escape);
ClassDB::bind_method(D_METHOD("add_button", "text", "right", "action"), &AcceptDialog::add_button, DEFVAL(false), DEFVAL(""));
ClassDB::bind_method(D_METHOD("add_cancel_button", "name"), &AcceptDialog::add_cancel_button);
ClassDB::bind_method(D_METHOD("remove_button", "button"), &AcceptDialog::remove_button);
@@ -302,6 +314,7 @@ void AcceptDialog::_bind_methods() {
ADD_GROUP("Dialog", "dialog");
ADD_PROPERTY(PropertyInfo(Variant::STRING, "dialog_text", PROPERTY_HINT_MULTILINE_TEXT, "", PROPERTY_USAGE_DEFAULT_INTL), "set_text", "get_text");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "dialog_hide_on_ok"), "set_hide_on_ok", "get_hide_on_ok");
+ ADD_PROPERTY(PropertyInfo(Variant::BOOL, "dialog_close_on_escape"), "set_close_on_escape", "get_close_on_escape");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "dialog_autowrap"), "set_autowrap", "has_autowrap");
}
diff --git a/scene/gui/dialogs.h b/scene/gui/dialogs.h
index 8e803a2a7c..41fd9c0a10 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 */
@@ -45,11 +45,12 @@ class AcceptDialog : public Window {
GDCLASS(AcceptDialog, Window);
Window *parent_visible = nullptr;
- Panel *bg;
- HBoxContainer *hbc;
- Label *label;
- Button *ok;
+ Panel *bg = nullptr;
+ HBoxContainer *hbc = nullptr;
+ Label *label = nullptr;
+ Button *ok = nullptr;
bool hide_on_ok = true;
+ bool close_on_escape = true;
void _custom_action(const String &p_action);
void _update_child_rects();
@@ -87,6 +88,9 @@ public:
void set_hide_on_ok(bool p_hide);
bool get_hide_on_ok() const;
+ void set_close_on_escape(bool p_enable);
+ bool get_close_on_escape() const;
+
void set_text(String p_text);
String get_text() const;
@@ -99,7 +103,7 @@ public:
class ConfirmationDialog : public AcceptDialog {
GDCLASS(ConfirmationDialog, AcceptDialog);
- Button *cancel;
+ Button *cancel = nullptr;
protected:
static void _bind_methods();
diff --git a/scene/gui/file_dialog.cpp b/scene/gui/file_dialog.cpp
index 08d5df3db8..1725816c31 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,27 +92,34 @@ 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_shortcut_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;
}
}
-void FileDialog::unhandled_input(const Ref<InputEvent> &p_event) {
+void FileDialog::shortcut_input(const Ref<InputEvent> &p_event) {
ERR_FAIL_COND(p_event.is_null());
Ref<InputEventKey> k = p_event;
@@ -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.
@@ -192,7 +217,7 @@ void FileDialog::_post_popup() {
tree->grab_focus();
}
- set_process_unhandled_input(true);
+ set_process_shortcut_input(true);
// For open dir mode, deselect all items on file dialog open.
if (mode == FILE_MODE_OPEN_DIR) {
@@ -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);
@@ -627,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();
@@ -639,7 +665,7 @@ void FileDialog::update_filters() {
}
}
- filter->add_item(TTRC("All Files (*)"));
+ filter->add_item(RTR("All Files") + " (*)");
}
void FileDialog::clear_filters() {
@@ -649,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();
@@ -687,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) {
@@ -777,7 +798,6 @@ void FileDialog::set_access(Access p_access) {
if (access == p_access) {
return;
}
- memdelete(dir_access);
switch (p_access) {
case ACCESS_FILESYSTEM: {
dir_access = DirAccess::create(DirAccess::ACCESS_FILESYSTEM);
@@ -837,7 +857,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();
@@ -855,7 +875,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());
+ }
}
}
@@ -893,12 +915,12 @@ void FileDialog::_bind_methods() {
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "mode_overrides_title"), "set_mode_overrides_title", "is_mode_overriding_title");
ADD_PROPERTY(PropertyInfo(Variant::INT, "file_mode", PROPERTY_HINT_ENUM, "Open File,Open Files,Open Folder,Open Any,Save"), "set_file_mode", "get_file_mode");
- ADD_PROPERTY(PropertyInfo(Variant::INT, "access", PROPERTY_HINT_ENUM, "Resources,User data,File system"), "set_access", "get_access");
+ ADD_PROPERTY(PropertyInfo(Variant::INT, "access", PROPERTY_HINT_ENUM, "Resources,User Data,File System"), "set_access", "get_access");
ADD_PROPERTY(PropertyInfo(Variant::PACKED_STRING_ARRAY, "filters"), "set_filters", "get_filters");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "show_hidden_files"), "set_show_hidden_files", "is_showing_hidden_files");
- ADD_PROPERTY(PropertyInfo(Variant::STRING, "current_dir"), "set_current_dir", "get_current_dir");
- ADD_PROPERTY(PropertyInfo(Variant::STRING, "current_file"), "set_current_file", "get_current_file");
- ADD_PROPERTY(PropertyInfo(Variant::STRING, "current_path"), "set_current_path", "get_current_path");
+ ADD_PROPERTY(PropertyInfo(Variant::STRING, "current_dir", PROPERTY_HINT_DIR, "", PROPERTY_USAGE_NONE), "set_current_dir", "get_current_dir");
+ ADD_PROPERTY(PropertyInfo(Variant::STRING, "current_file", PROPERTY_HINT_FILE, "*", PROPERTY_USAGE_NONE), "set_current_file", "get_current_file");
+ ADD_PROPERTY(PropertyInfo(Variant::STRING, "current_path", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NONE), "set_current_path", "get_current_path");
ADD_SIGNAL(MethodInfo("file_selected", PropertyInfo(Variant::STRING, "path")));
ADD_SIGNAL(MethodInfo("files_selected", PropertyInfo(Variant::PACKED_STRING_ARRAY, "paths")));
@@ -966,7 +988,7 @@ FileDialog::FileDialog() {
hbc->add_child(drives);
dir = memnew(LineEdit);
- dir->set_structured_text_bidi_override(Control::STRUCTURED_TEXT_FILE);
+ dir->set_structured_text_bidi_override(TextServer::STRUCTURED_TEXT_FILE);
hbc->add_child(dir);
dir->set_h_size_flags(Control::SIZE_EXPAND_FILL);
@@ -1007,7 +1029,7 @@ FileDialog::FileDialog() {
file_box = memnew(HBoxContainer);
file_box->add_child(memnew(Label(TTRC("File:"))));
file = memnew(LineEdit);
- file->set_structured_text_bidi_override(Control::STRUCTURED_TEXT_FILE);
+ file->set_structured_text_bidi_override(TextServer::STRUCTURED_TEXT_FILE);
file->set_stretch_ratio(4);
file->set_h_size_flags(Control::SIZE_EXPAND_FILL);
file_box->add_child(file);
@@ -1041,7 +1063,7 @@ FileDialog::FileDialog() {
makedialog->add_child(makevb);
makedirname = memnew(LineEdit);
- makedirname->set_structured_text_bidi_override(Control::STRUCTURED_TEXT_FILE);
+ makedirname->set_structured_text_bidi_override(TextServer::STRUCTURED_TEXT_FILE);
makevb->add_margin_child(TTRC("Name:"), makedirname);
add_child(makedialog, false, INTERNAL_MODE_FRONT);
makedialog->register_text_enter(makedirname);
@@ -1068,5 +1090,4 @@ FileDialog::~FileDialog() {
if (unregister_func) {
unregister_func(this);
}
- memdelete(dir_access);
}
diff --git a/scene/gui/file_dialog.h b/scene/gui/file_dialog.h
index b5190bdab1..2e326d2949 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 */
@@ -65,35 +65,34 @@ public:
static RegisterFunc unregister_func;
private:
- ConfirmationDialog *makedialog;
- LineEdit *makedirname;
+ ConfirmationDialog *makedialog = nullptr;
+ LineEdit *makedirname = nullptr;
- Button *makedir;
+ Button *makedir = nullptr;
Access access = ACCESS_RESOURCES;
- //Button *action;
- VBoxContainer *vbox;
+ VBoxContainer *vbox = nullptr;
FileMode mode;
- LineEdit *dir;
- HBoxContainer *drives_container;
- HBoxContainer *shortcuts_container;
- OptionButton *drives;
- Tree *tree;
- HBoxContainer *file_box;
- LineEdit *file;
- OptionButton *filter;
- AcceptDialog *mkdirerr;
- AcceptDialog *exterr;
- DirAccess *dir_access;
- ConfirmationDialog *confirm_save;
-
- Label *message;
-
- Button *dir_prev;
- Button *dir_next;
- Button *dir_up;
-
- Button *refresh;
- Button *show_hidden;
+ LineEdit *dir = nullptr;
+ HBoxContainer *drives_container = nullptr;
+ HBoxContainer *shortcuts_container = nullptr;
+ OptionButton *drives = nullptr;
+ Tree *tree = nullptr;
+ HBoxContainer *file_box = nullptr;
+ LineEdit *file = nullptr;
+ OptionButton *filter = nullptr;
+ AcceptDialog *mkdirerr = nullptr;
+ AcceptDialog *exterr = nullptr;
+ Ref<DirAccess> dir_access;
+ ConfirmationDialog *confirm_save = nullptr;
+
+ Label *message = nullptr;
+
+ Button *dir_prev = nullptr;
+ Button *dir_next = nullptr;
+ Button *dir_up = nullptr;
+
+ Button *refresh = nullptr;
+ Button *show_hidden = nullptr;
Vector<String> filters;
@@ -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,9 +131,9 @@ 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;
+ virtual void shortcut_input(const Ref<InputEvent> &p_event) override;
bool _is_open_should_be_disabled();
diff --git a/scene/gui/flow_container.cpp b/scene/gui/flow_container.cpp
new file mode 100644
index 0000000000..30b694da76
--- /dev/null
+++ b/scene/gui/flow_container.cpp
@@ -0,0 +1,281 @@
+/*************************************************************************/
+/* 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() {
+ // Avoid resorting if invisible.
+ if (!is_visible_in_tree()) {
+ return;
+ }
+
+ int separation_horizontal = get_theme_constant(SNAME("h_separation"));
+ int separation_vertical = get_theme_constant(SNAME("v_separation"));
+
+ bool rtl = is_layout_rtl();
+
+ HashMap<Control *, Size2i> children_minsize_cache;
+
+ 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()) {
+ 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()) {
+ 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 1210be15ce..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 */
@@ -287,84 +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.
+ // Draw point markers.
+ for (int i = 0; i < points.size(); i++) {
+ Color col = points[i].color.inverted();
+ col.a = 0.9;
- 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_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);
+ }
- // 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);
}
-
- rect = rect.grow(-1);
- draw_rect(rect, col, false);
}
- }
- //Draw "button" for color selector
- draw_texture_rect(get_theme_icon(SNAME("GuiMiniCheckerboard"), SNAME("EditorIcons")), Rect2(total_w + draw_spacing, 0, h, h), true);
- if (grabbed != -1) {
- //Draw with selection color
- draw_rect(Rect2(total_w + draw_spacing, 0, h, h), points[grabbed].color);
- } else {
- //if no color selected draw grey color with 'X' on top.
- draw_rect(Rect2(total_w + draw_spacing, 0, h, h), Color(0.5, 0.5, 0.5, 1));
- draw_line(Vector2(total_w + draw_spacing, 0), Vector2(total_w + draw_spacing + h, h), Color(1, 1, 1, 0.6));
- draw_line(Vector2(total_w + draw_spacing, h), Vector2(total_w + draw_spacing + h, 0), Color(1, 1, 1, 0.6));
- }
+ // Draw "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));
- }
- }
+ // 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;
}
}
@@ -432,6 +433,10 @@ 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 66b60d87c7..4e3c6525f9 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,14 +33,13 @@
#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 {
GDCLASS(GradientEdit, Control);
- PopupPanel *popup;
- ColorPicker *picker;
+ PopupPanel *popup = nullptr;
+ ColorPicker *picker = nullptr;
bool grabbing = false;
int grabbed = -1;
@@ -75,6 +74,7 @@ public:
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;
diff --git a/scene/gui/graph_edit.cpp b/scene/gui/graph_edit.cpp
index 32c62b7226..8ad55fc6ef 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;
@@ -422,89 +423,94 @@ 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_hotzone_inner_extent = get_theme_constant("port_hotzone_inner_extent");
+ port_hotzone_outer_extent = get_theme_constant("port_hotzone_outer_extent");
+
+ zoom_minus->set_icon(get_theme_icon(SNAME("minus")));
+ zoom_reset->set_icon(get_theme_icon(SNAME("reset")));
+ 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;
- if (p_what == NOTIFICATION_RESIZED) {
- _update_scroll();
- top_layer->update();
- minimap->update();
+ 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;
+ comment_node_rect.size *= zoom;
+ Vector<GraphNode *> enclosed_nodes;
for (int i = 0; i < get_child_count(); i++) {
GraphNode *gn = Object::cast_to<GraphNode>(get_child(i));
if (!gn || gn->is_selected()) {
@@ -512,13 +518,15 @@ void GraphEdit::_update_comment_enclosed_nodes_list(GraphNode *p_node, HashMap<S
}
Rect2 node_rect = gn->get_rect();
+ node_rect.size *= zoom;
+
bool included = comment_node_rect.encloses(node_rect);
if (included) {
enclosed_nodes.push_back(gn);
}
}
- p_comment_enclosed_nodes.set(p_node->get_name(), enclosed_nodes);
+ p_comment_enclosed_nodes.insert(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) {
@@ -539,8 +547,7 @@ void GraphEdit::_set_position_of_comment_enclosed_nodes(GraphNode *p_node, HashM
}
bool GraphEdit::_filter_input(const Point2 &p_point) {
- Ref<Texture2D> port = get_theme_icon(SNAME("port"), SNAME("GraphNode"));
- Vector2i port_size = Vector2i(port->get_width(), port->get_height());
+ Ref<Texture2D> port_icon = get_theme_icon(SNAME("port"), SNAME("GraphNode"));
for (int i = get_child_count() - 1; i >= 0; i--) {
GraphNode *gn = Object::cast_to<GraphNode>(get_child(i));
@@ -548,16 +555,18 @@ bool GraphEdit::_filter_input(const Point2 &p_point) {
continue;
}
- 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)) {
+ for (int j = 0; j < gn->get_connection_input_count(); j++) {
+ Vector2i port_size = Vector2i(port_icon->get_width(), port_icon->get_height());
+ port_size.height = MAX(port_size.height, gn->get_connection_input_height(j));
+ if (is_in_input_hotzone(gn, j, p_point / zoom, port_size)) {
return true;
}
}
- for (int j = 0; j < gn->get_connection_input_count(); j++) {
- Vector2 pos = gn->get_connection_input_position(j) + gn->get_position();
- if (is_in_hot_zone(pos / zoom, p_point / zoom, port_size, true)) {
+ for (int j = 0; j < gn->get_connection_output_count(); j++) {
+ Vector2i port_size = Vector2i(port_icon->get_width(), port_icon->get_height());
+ port_size.height = MAX(port_size.height, gn->get_connection_output_height(j));
+ if (is_in_output_hotzone(gn, j, p_point / zoom, port_size)) {
return true;
}
}
@@ -569,8 +578,7 @@ bool GraphEdit::_filter_input(const Point2 &p_point) {
void GraphEdit::_top_layer_input(const Ref<InputEvent> &p_ev) {
Ref<InputEventMouseButton> mb = p_ev;
if (mb.is_valid() && mb->get_button_index() == MouseButton::LEFT && mb->is_pressed()) {
- Ref<Texture2D> port = get_theme_icon(SNAME("port"), SNAME("GraphNode"));
- Vector2i port_size = Vector2i(port->get_width(), port->get_height());
+ Ref<Texture2D> port_icon = get_theme_icon(SNAME("port"), SNAME("GraphNode"));
connecting_valid = false;
click_pos = mb->get_position() / zoom;
@@ -582,7 +590,10 @@ 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)) {
+ Vector2i port_size = Vector2i(port_icon->get_width(), port_icon->get_height());
+ port_size.height = MAX(port_size.height, gn->get_connection_output_height(j));
+
+ if (is_in_output_hotzone(gn, j, click_pos, port_size)) {
if (valid_left_disconnect_types.has(gn->get_connection_output_type(j))) {
//check disconnect
for (const Connection &E : connections) {
@@ -602,6 +613,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;
}
@@ -618,13 +630,18 @@ 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)) {
+
+ Vector2i port_size = Vector2i(port_icon->get_width(), port_icon->get_height());
+ port_size.height = MAX(port_size.height, gn->get_connection_input_height(j));
+
+ if (is_in_input_hotzone(gn, j, click_pos, port_size)) {
if (right_disconnects || valid_right_disconnect_types.has(gn->get_connection_input_type(j))) {
//check disconnect
for (const Connection &E : connections) {
@@ -644,6 +661,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;
}
@@ -660,7 +678,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;
}
}
@@ -676,11 +694,9 @@ void GraphEdit::_top_layer_input(const Ref<InputEvent> &p_ev) {
connecting_valid = just_disconnected || click_pos.distance_to(connecting_to / zoom) > 20.0;
if (connecting_valid) {
- Ref<Texture2D> port = get_theme_icon(SNAME("port"), SNAME("GraphNode"));
- Vector2i port_size = Vector2i(port->get_width(), port->get_height());
-
Vector2 mpos = mm->get_position() / zoom;
for (int i = get_child_count() - 1; i >= 0; i--) {
+ Ref<Texture2D> port_icon = get_theme_icon(SNAME("port"), SNAME("GraphNode"));
GraphNode *gn = Object::cast_to<GraphNode>(get_child(i));
if (!gn) {
continue;
@@ -689,8 +705,11 @@ void GraphEdit::_top_layer_input(const Ref<InputEvent> &p_ev) {
if (!connecting_out) {
for (int j = 0; j < gn->get_connection_output_count(); j++) {
Vector2 pos = gn->get_connection_output_position(j) + gn->get_position();
+ Vector2i port_size = Vector2i(port_icon->get_width(), port_icon->get_height());
+ port_size.height = MAX(port_size.height, gn->get_connection_output_height(j));
+
int type = gn->get_connection_output_type(j);
- if ((type == connecting_type || valid_connection_types.has(ConnType(type, connecting_type))) && is_in_hot_zone(pos / zoom, mpos, port_size, false)) {
+ if ((type == connecting_type || valid_connection_types.has(ConnType(connecting_type, type))) && is_in_output_hotzone(gn, j, mpos, port_size)) {
connecting_target = true;
connecting_to = pos;
connecting_target_to = gn->get_name();
@@ -701,8 +720,11 @@ void GraphEdit::_top_layer_input(const Ref<InputEvent> &p_ev) {
} else {
for (int j = 0; j < gn->get_connection_input_count(); j++) {
Vector2 pos = gn->get_connection_input_position(j) + gn->get_position();
+ Vector2i port_size = Vector2i(port_icon->get_width(), port_icon->get_height());
+ port_size.height = MAX(port_size.height, gn->get_connection_input_height(j));
+
int type = gn->get_connection_input_type(j);
- if ((type == connecting_type || valid_connection_types.has(ConnType(type, connecting_type))) && is_in_hot_zone(pos / zoom, mpos, port_size, true)) {
+ if ((type == connecting_type || valid_connection_types.has(ConnType(connecting_type, type))) && is_in_input_hotzone(gn, j, mpos, port_size)) {
connecting_target = true;
connecting_to = pos;
connecting_target_to = gn->get_name();
@@ -742,27 +764,30 @@ 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();
+ }
}
}
-bool GraphEdit::_check_clickable_control(Control *p_control, const Vector2 &pos) {
- if (p_control->is_set_as_top_level() || !p_control->is_visible()) {
+bool GraphEdit::_check_clickable_control(Control *p_control, const Vector2 &mpos, const Vector2 &p_offset) {
+ if (p_control->is_set_as_top_level() || !p_control->is_visible() || !p_control->is_inside_tree()) {
return false;
}
- if (!p_control->has_point(pos) || p_control->get_mouse_filter() == MOUSE_FILTER_IGNORE) {
- //test children
+ Rect2 control_rect = p_control->get_rect();
+ control_rect.size *= zoom;
+ control_rect.position *= zoom;
+ control_rect.position += p_offset;
+
+ if (!control_rect.has_point(mpos) || p_control->get_mouse_filter() == MOUSE_FILTER_IGNORE) {
+ // Test children.
for (int i = 0; i < p_control->get_child_count(); i++) {
- Control *subchild = Object::cast_to<Control>(p_control->get_child(i));
- if (!subchild) {
+ Control *child_rect = Object::cast_to<Control>(p_control->get_child(i));
+ if (!child_rect) {
continue;
}
- if (_check_clickable_control(subchild, pos - subchild->get_position())) {
+ if (_check_clickable_control(child_rect, mpos, control_rect.position)) {
return true;
}
}
@@ -773,25 +798,35 @@ 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) {
+ Rect2 hotzone = Rect2(
+ pos.x - (p_left ? port_hotzone_outer_extent : port_hotzone_inner_extent),
+ pos.y - p_port_size.height / 2.0,
+ port_hotzone_inner_extent + port_hotzone_outer_extent,
+ p_port_size.height);
+
+ if (!hotzone.has_point(p_mouse_pos)) {
+ return false;
}
for (int i = 0; i < get_child_count(); i++) {
@@ -799,23 +834,17 @@ bool GraphEdit::is_in_hot_zone(const Vector2 &pos, const Vector2 &p_mouse_pos, c
if (!child) {
continue;
}
- Rect2 rect = child->get_rect();
-
- // To prevent intersections with other nodes.
- rect.position *= zoom;
- rect.size *= zoom;
-
- if (rect.has_point(p_mouse_pos)) {
- //check sub-controls
- Vector2 subpos = p_mouse_pos - rect.position;
+ Rect2 child_rect = child->get_rect();
+ child_rect.size *= zoom;
+ if (child_rect.has_point(p_mouse_pos * zoom)) {
for (int j = 0; j < child->get_child_count(); j++) {
Control *subchild = Object::cast_to<Control>(child->get_child(j));
if (!subchild) {
continue;
}
- if (_check_clickable_control(subchild, subpos - subchild->get_position())) {
+ if (_check_clickable_control(subchild, p_mouse_pos * zoom, child_rect.position)) {
return false;
}
}
@@ -831,13 +860,23 @@ PackedVector2Array GraphEdit::get_connection_line(const Vector2 &p_from, const V
return ret;
}
+ float x_diff = (p_to.x - p_from.x);
+ float cp_offset = x_diff * lines_curvature;
+ if (x_diff < 0) {
+ cp_offset *= -1;
+ }
+
Curve2D curve;
- Vector<Color> colors;
curve.add_point(p_from);
- curve.set_point_out(0, Vector2(60, 0));
+ curve.set_point_out(0, Vector2(cp_offset, 0));
curve.add_point(p_to);
- curve.set_point_in(1, Vector2(-60, 0));
- return curve.tessellate();
+ curve.set_point_in(1, Vector2(-cp_offset, 0));
+
+ if (lines_curvature > 0) {
+ return curve.tessellate(5, 2.0);
+ } else {
+ return curve.tessellate(1);
+ }
}
void GraphEdit::_draw_connection_line(CanvasItem *p_where, const Vector2 &p_from, const Vector2 &p_to, const Color &p_color, const Color &p_to_color, float p_width, float p_zoom) {
@@ -973,7 +1012,7 @@ void GraphEdit::_minimap_draw() {
Ref<StyleBoxFlat> sb_minimap = minimap->get_theme_stylebox(SNAME("node"))->duplicate();
// Override default values with colors provided by the GraphNode's stylebox, if possible.
- Ref<StyleBoxFlat> sbf = gn->get_theme_stylebox(gn->is_selected() ? "commentfocus" : "comment");
+ Ref<StyleBoxFlat> sbf = gn->get_theme_stylebox(gn->is_selected() ? "comment_focus" : "comment");
if (sbf.is_valid()) {
Color node_color = sbf->get_bg_color();
sb_minimap->set_bg_color(node_color);
@@ -996,7 +1035,7 @@ void GraphEdit::_minimap_draw() {
Ref<StyleBoxFlat> sb_minimap = minimap->get_theme_stylebox(SNAME("node"))->duplicate();
// Override default values with colors provided by the GraphNode's stylebox, if possible.
- Ref<StyleBoxFlat> sbf = gn->get_theme_stylebox(gn->is_selected() ? "selectedframe" : "frame");
+ Ref<StyleBoxFlat> sbf = gn->get_theme_stylebox(gn->is_selected() ? "selected_frame" : "frame");
if (sbf.is_valid()) {
Color node_color = sbf->get_border_color();
sb_minimap->set_bg_color(node_color);
@@ -1066,13 +1105,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() & MouseButton::MASK_MIDDLE) != MouseButton::NONE || ((mm->get_button_mask() & MouseButton::MASK_LEFT) != MouseButton::NONE && 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) {
@@ -1162,11 +1199,9 @@ 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"), get_screen_position() + b->get_position());
+ emit_signal(SNAME("popup_request"), b->get_position());
}
}
}
@@ -1274,7 +1309,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;
}
@@ -1326,22 +1361,6 @@ void GraphEdit::gui_input(const Ref<InputEvent> &p_ev) {
top_layer->update();
minimap->update();
}
-
- int scroll_direction = (b->get_button_index() == MouseButton::WHEEL_DOWN) - (b->get_button_index() == MouseButton::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()) {
@@ -1355,7 +1374,19 @@ void GraphEdit::gui_input(const Ref<InputEvent> &p_ev) {
emit_signal(SNAME("paste_nodes_request"));
accept_event();
} else if (p_ev->is_action("ui_graph_delete")) {
- emit_signal(SNAME("delete_nodes_request"));
+ TypedArray<StringName> nodes;
+
+ for (int i = 0; i < get_child_count(); i++) {
+ GraphNode *gn = Object::cast_to<GraphNode>(get_child(i));
+ if (!gn) {
+ continue;
+ }
+ if (gn->is_selected() && gn->is_close_button_visible()) {
+ nodes.push_back(gn->get_name());
+ }
+ }
+
+ emit_signal(SNAME("delete_nodes_request"), nodes);
accept_event();
}
}
@@ -1372,6 +1403,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) {
@@ -1394,6 +1442,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);
}
@@ -1543,26 +1611,17 @@ void GraphEdit::_update_zoom_label() {
}
void GraphEdit::add_valid_connection_type(int p_type, int p_with_type) {
- ConnType ct;
- ct.type_a = p_type;
- ct.type_b = p_with_type;
-
+ ConnType ct(p_type, p_with_type);
valid_connection_types.insert(ct);
}
void GraphEdit::remove_valid_connection_type(int p_type, int p_with_type) {
- ConnType ct;
- ct.type_a = p_type;
- ct.type_b = p_with_type;
-
+ ConnType ct(p_type, p_with_type);
valid_connection_types.erase(ct);
}
bool GraphEdit::is_valid_connection_type(int p_type, int p_with_type) const {
- ConnType ct;
- ct.type_a = p_type;
- ct.type_b = p_with_type;
-
+ ConnType ct(p_type, p_with_type);
return valid_connection_types.has(ct);
}
@@ -1621,6 +1680,7 @@ float GraphEdit::get_minimap_opacity() const {
void GraphEdit::set_minimap_enabled(bool p_enable) {
minimap_button->set_pressed(p_enable);
+ _minimap_toggled();
minimap->update();
}
@@ -1637,6 +1697,15 @@ void GraphEdit::_minimap_toggled() {
}
}
+void GraphEdit::set_connection_lines_curvature(float p_curvature) {
+ lines_curvature = p_curvature;
+ update();
+}
+
+float GraphEdit::get_connection_lines_curvature() const {
+ return lines_curvature;
+}
+
void GraphEdit::set_connection_lines_thickness(float p_thickness) {
lines_thickness = p_thickness;
update();
@@ -1659,12 +1728,21 @@ HBoxContainer *GraphEdit::get_zoom_hbox() {
return zoom_hb;
}
-int GraphEdit::_set_operations(SET_OPERATIONS p_operation, Set<StringName> &r_u, const Set<StringName> &r_v) {
+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, HashSet<StringName> &r_u, const HashSet<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()))
+ for (const StringName &E : r_u) {
+ if (!r_v.has(E)) {
return 0;
+ }
}
return r_u.size() == r_v.size();
} break;
@@ -1672,27 +1750,31 @@ int GraphEdit::_set_operations(SET_OPERATIONS p_operation, Set<StringName> &r_u,
if (r_u.size() == r_v.size() && !r_u.size()) {
return 1;
}
- for (Set<StringName>::Element *E = r_u.front(); E; E = E->next()) {
- if (!r_v.has(E->get()))
+ for (const StringName &E : r_u) {
+ if (!r_v.has(E)) {
return 0;
+ }
}
return 1;
} break;
case GraphEdit::DIFFERENCE: {
- for (Set<StringName>::Element *E = r_u.front(); E; E = E->next()) {
- if (r_v.has(E->get())) {
- r_u.erase(E->get());
+ for (HashSet<StringName>::Iterator E = r_u.begin(); E;) {
+ HashSet<StringName>::Iterator N = E;
+ ++N;
+ if (r_v.has(*E)) {
+ r_u.remove(E);
}
+ E = N;
}
return r_u.size();
} break;
case GraphEdit::UNION: {
- for (Set<StringName>::Element *E = r_v.front(); E; E = E->next()) {
- if (!r_u.has(E->get())) {
- r_u.insert(E->get());
+ for (const StringName &E : r_v) {
+ if (!r_u.has(E)) {
+ r_u.insert(E);
}
}
- return r_v.size();
+ return r_u.size();
} break;
default:
break;
@@ -1700,28 +1782,28 @@ int GraphEdit::_set_operations(SET_OPERATIONS p_operation, Set<StringName> &r_u,
return -1;
}
-HashMap<int, Vector<StringName>> GraphEdit::_layering(const Set<StringName> &r_selected_nodes, const HashMap<StringName, Set<StringName>> &r_upper_neighbours) {
+HashMap<int, Vector<StringName>> GraphEdit::_layering(const HashSet<StringName> &r_selected_nodes, const HashMap<StringName, HashSet<StringName>> &r_upper_neighbours) {
HashMap<int, Vector<StringName>> l;
- Set<StringName> p = r_selected_nodes, q = r_selected_nodes, u, z;
+ HashSet<StringName> p = r_selected_nodes, q = r_selected_nodes, u, z;
int current_layer = 0;
bool selected = false;
while (!_set_operations(GraphEdit::IS_EQUAL, q, u)) {
_set_operations(GraphEdit::DIFFERENCE, p, u);
- for (const Set<StringName>::Element *E = p.front(); E; E = E->next()) {
- Set<StringName> n = r_upper_neighbours[E->get()];
+ for (const StringName &E : p) {
+ HashSet<StringName> n = r_upper_neighbours[E];
if (_set_operations(GraphEdit::IS_SUBSET, n, z)) {
Vector<StringName> t;
- t.push_back(E->get());
+ t.push_back(E);
if (!l.has(current_layer)) {
- l.set(current_layer, Vector<StringName>{});
+ l.insert(current_layer, Vector<StringName>{});
}
selected = true;
t.append_array(l[current_layer]);
- l.set(current_layer, t);
- Set<StringName> V;
- V.insert(E->get());
+ l.insert(current_layer, t);
+ HashSet<StringName> V;
+ V.insert(E);
_set_operations(GraphEdit::UNION, u, V);
}
}
@@ -1762,10 +1844,10 @@ Vector<StringName> GraphEdit::_split(const Vector<StringName> &r_layer, const Ha
return left;
}
-void GraphEdit::_horizontal_alignment(Dictionary &r_root, Dictionary &r_align, const HashMap<int, Vector<StringName>> &r_layers, const HashMap<StringName, Set<StringName>> &r_upper_neighbours, const Set<StringName> &r_selected_nodes) {
- for (const Set<StringName>::Element *E = r_selected_nodes.front(); E; E = E->next()) {
- r_root[E->get()] = E->get();
- r_align[E->get()] = E->get();
+void GraphEdit::_horizontal_alignment(Dictionary &r_root, Dictionary &r_align, const HashMap<int, Vector<StringName>> &r_layers, const HashMap<StringName, HashSet<StringName>> &r_upper_neighbours, const HashSet<StringName> &r_selected_nodes) {
+ for (const StringName &E : r_selected_nodes) {
+ r_root[E] = E;
+ r_align[E] = E;
}
if (r_layers.size() == 1) {
@@ -1787,8 +1869,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) {
@@ -1802,7 +1884,7 @@ void GraphEdit::_horizontal_alignment(Dictionary &r_root, Dictionary &r_align, c
}
}
-void GraphEdit::_crossing_minimisation(HashMap<int, Vector<StringName>> &r_layers, const HashMap<StringName, Set<StringName>> &r_upper_neighbours) {
+void GraphEdit::_crossing_minimisation(HashMap<int, Vector<StringName>> &r_layers, const HashMap<StringName, HashSet<StringName>> &r_upper_neighbours) {
if (r_layers.size() == 1) {
return;
}
@@ -1833,17 +1915,17 @@ void GraphEdit::_crossing_minimisation(HashMap<int, Vector<StringName>> &r_layer
}
d[q] = crossings;
}
- c.set(p, d);
+ c.insert(p, d);
}
- r_layers.set(i, _split(lower_layer, c));
+ r_layers.insert(i, _split(lower_layer, c));
}
}
-void GraphEdit::_calculate_inner_shifts(Dictionary &r_inner_shifts, const Dictionary &r_root, const Dictionary &r_node_names, const Dictionary &r_align, const Set<StringName> &r_block_heads, const HashMap<StringName, Pair<int, int>> &r_port_info) {
- for (const Set<StringName>::Element *E = r_block_heads.front(); E; E = E->next()) {
+void GraphEdit::_calculate_inner_shifts(Dictionary &r_inner_shifts, const Dictionary &r_root, const Dictionary &r_node_names, const Dictionary &r_align, const HashSet<StringName> &r_block_heads, const HashMap<StringName, Pair<int, int>> &r_port_info) {
+ for (const StringName &E : r_block_heads) {
real_t left = 0;
- StringName u = E->get();
+ StringName u = E;
StringName v = r_align[u];
while (u != v && (StringName)r_root[u] != v) {
String _connection = String(u) + " " + String(v);
@@ -1864,11 +1946,11 @@ void GraphEdit::_calculate_inner_shifts(Dictionary &r_inner_shifts, const Dictio
v = (StringName)r_align[v];
}
- u = E->get();
+ u = E;
do {
r_inner_shifts[u] = (real_t)r_inner_shifts[u] - left;
u = (StringName)r_align[u];
- } while (u != E->get());
+ } while (u != E);
}
}
@@ -1999,7 +2081,7 @@ void GraphEdit::_place_block(StringName p_v, float p_delta, const HashMap<int, V
threshold = _calculate_threshold(p_v, w, r_node_name, r_layers, r_root, r_align, r_inner_shift, threshold, r_node_positions);
w = r_align[w];
} while (w != p_v);
- r_node_positions.set(p_v, pos);
+ r_node_positions.insert(p_v, pos);
}
#undef PRED
@@ -2013,7 +2095,7 @@ void GraphEdit::arrange_nodes() {
}
Dictionary node_names;
- Set<StringName> selected_nodes;
+ HashSet<StringName> selected_nodes;
for (int i = get_child_count() - 1; i >= 0; i--) {
GraphNode *gn = Object::cast_to<GraphNode>(get_child(i));
@@ -2024,7 +2106,7 @@ void GraphEdit::arrange_nodes() {
node_names[gn->get_name()] = gn;
}
- HashMap<StringName, Set<StringName>> upper_neighbours;
+ HashMap<StringName, HashSet<StringName>> upper_neighbours;
HashMap<StringName, Pair<int, int>> port_info;
Vector2 origin(FLT_MAX, FLT_MAX);
@@ -2039,7 +2121,7 @@ void GraphEdit::arrange_nodes() {
if (gn->is_selected()) {
selected_nodes.insert(gn->get_name());
- Set<StringName> s;
+ HashSet<StringName> s;
for (List<Connection>::Element *E = connections.front(); E; E = E->next()) {
GraphNode *p_from = Object::cast_to<GraphNode>(node_names[E->get().from]);
if (E->get().to == gn->get_name() && p_from->is_selected()) {
@@ -2055,10 +2137,10 @@ void GraphEdit::arrange_nodes() {
ports = p_ports;
}
}
- port_info.set(_connection, ports);
+ port_info.insert(_connection, ports);
}
}
- upper_neighbours.set(gn->get_name(), s);
+ upper_neighbours.insert(gn->get_name(), s);
}
}
@@ -2076,38 +2158,38 @@ void GraphEdit::arrange_nodes() {
HashMap<StringName, Vector2> new_positions;
Vector2 default_position(FLT_MAX, FLT_MAX);
Dictionary inner_shift;
- Set<StringName> block_heads;
+ HashSet<StringName> block_heads;
- for (const Set<StringName>::Element *E = selected_nodes.front(); E; E = E->next()) {
- inner_shift[E->get()] = 0.0f;
- sink[E->get()] = E->get();
- shift[E->get()] = FLT_MAX;
- new_positions.set(E->get(), default_position);
- if ((StringName)root[E->get()] == E->get()) {
- block_heads.insert(E->get());
+ for (const StringName &E : selected_nodes) {
+ inner_shift[E] = 0.0f;
+ sink[E] = E;
+ shift[E] = FLT_MAX;
+ new_positions.insert(E, default_position);
+ if ((StringName)root[E] == E) {
+ block_heads.insert(E);
}
}
_calculate_inner_shifts(inner_shift, root, node_names, align, block_heads, port_info);
- for (const Set<StringName>::Element *E = block_heads.front(); E; E = E->next()) {
- _place_block(E->get(), gap_v, layers, root, align, node_names, inner_shift, sink, shift, new_positions);
+ for (const StringName &E : block_heads) {
+ _place_block(E, gap_v, layers, root, align, node_names, inner_shift, sink, shift, new_positions);
}
origin.y = Object::cast_to<GraphNode>(node_names[layers[0][0]])->get_position_offset().y - (new_positions[layers[0][0]].y + (float)inner_shift[layers[0][0]]);
origin.x = Object::cast_to<GraphNode>(node_names[layers[0][0]])->get_position_offset().x;
- for (const Set<StringName>::Element *E = block_heads.front(); E; E = E->next()) {
- StringName u = E->get();
- float start_from = origin.y + new_positions[E->get()].y;
+ for (const StringName &E : block_heads) {
+ StringName u = E;
+ float start_from = origin.y + new_positions[E].y;
do {
Vector2 cal_pos;
cal_pos.y = start_from + (real_t)inner_shift[u];
- new_positions.set(u, cal_pos);
+ new_positions.insert(u, cal_pos);
u = align[u];
- } while (u != E->get());
+ } while (u != E);
}
- //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;
@@ -2134,7 +2216,7 @@ void GraphEdit::arrange_nodes() {
}
cal_pos.x = current_node_start_pos;
}
- new_positions.set(layer[j], cal_pos);
+ new_positions.insert(layer[j], cal_pos);
}
start_from += largest_node_size + gap_h;
@@ -2142,10 +2224,10 @@ void GraphEdit::arrange_nodes() {
}
emit_signal(SNAME("begin_node_move"));
- for (const Set<StringName>::Element *E = selected_nodes.front(); E; E = E->next()) {
- GraphNode *gn = Object::cast_to<GraphNode>(node_names[E->get()]);
+ for (const StringName &E : selected_nodes) {
+ GraphNode *gn = Object::cast_to<GraphNode>(node_names[E]);
gn->set_drag(true);
- Vector2 pos = (new_positions[E->get()]);
+ Vector2 pos = (new_positions[E]);
if (is_using_snap()) {
const int snap = get_snap();
@@ -2165,8 +2247,9 @@ 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);
+ ClassDB::bind_method(D_METHOD("set_scroll_ofs", "offset"), &GraphEdit::set_scroll_ofs);
ClassDB::bind_method(D_METHOD("add_valid_right_disconnect_type", "type"), &GraphEdit::add_valid_right_disconnect_type);
ClassDB::bind_method(D_METHOD("remove_valid_right_disconnect_type", "type"), &GraphEdit::remove_valid_right_disconnect_type);
@@ -2177,6 +2260,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);
@@ -2198,6 +2284,9 @@ void GraphEdit::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_use_snap", "enable"), &GraphEdit::set_use_snap);
ClassDB::bind_method(D_METHOD("is_using_snap"), &GraphEdit::is_using_snap);
+ ClassDB::bind_method(D_METHOD("set_connection_lines_curvature", "curvature"), &GraphEdit::set_connection_lines_curvature);
+ ClassDB::bind_method(D_METHOD("get_connection_lines_curvature"), &GraphEdit::get_connection_lines_curvature);
+
ClassDB::bind_method(D_METHOD("set_connection_lines_thickness", "pixels"), &GraphEdit::set_connection_lines_thickness);
ClassDB::bind_method(D_METHOD("get_connection_lines_thickness"), &GraphEdit::get_connection_lines_thickness);
@@ -2216,6 +2305,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);
@@ -2229,8 +2320,10 @@ 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_curvature"), "set_connection_lines_curvature", "get_connection_lines_curvature");
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "connection_lines_thickness"), "set_connection_lines_thickness", "get_connection_lines_thickness");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "connection_lines_antialiased"), "set_connection_lines_antialiased", "is_connection_lines_antialiased");
@@ -2256,10 +2349,15 @@ void GraphEdit::_bind_methods() {
ADD_SIGNAL(MethodInfo("node_deselected", PropertyInfo(Variant::OBJECT, "node", PROPERTY_HINT_RESOURCE_TYPE, "Node")));
ADD_SIGNAL(MethodInfo("connection_to_empty", PropertyInfo(Variant::STRING_NAME, "from"), PropertyInfo(Variant::INT, "from_slot"), PropertyInfo(Variant::VECTOR2, "release_position")));
ADD_SIGNAL(MethodInfo("connection_from_empty", PropertyInfo(Variant::STRING_NAME, "to"), PropertyInfo(Variant::INT, "to_slot"), PropertyInfo(Variant::VECTOR2, "release_position")));
- ADD_SIGNAL(MethodInfo("delete_nodes_request"));
+ ADD_SIGNAL(MethodInfo("delete_nodes_request", PropertyInfo(Variant::ARRAY, "nodes", PROPERTY_HINT_ARRAY_TYPE, "StringName")));
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("scroll_offset_changed", PropertyInfo(Variant::VECTOR2, "offset")));
+ ADD_SIGNAL(MethodInfo("connection_drag_started", PropertyInfo(Variant::STRING, "from"), PropertyInfo(Variant::STRING, "slot"), PropertyInfo(Variant::BOOL, "is_output")));
+ ADD_SIGNAL(MethodInfo("connection_drag_ended"));
+
+ BIND_ENUM_CONSTANT(SCROLL_ZOOMS);
+ BIND_ENUM_CONSTANT(SCROLL_PANS);
}
GraphEdit::GraphEdit() {
@@ -2272,12 +2370,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);
diff --git a/scene/gui/graph_edit.h b/scene/gui/graph_edit.h
index 6c11f9df6a..02e90e4717 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,18 +36,17 @@
#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);
friend class GraphEdit;
friend class GraphEditMinimap;
- GraphEdit *ge;
+ GraphEdit *ge = nullptr;
virtual bool has_point(const Point2 &p_point) const override;
public:
@@ -59,7 +58,7 @@ class GraphEditMinimap : public Control {
friend class GraphEdit;
friend class GraphEditFilter;
- GraphEdit *ge;
+ GraphEdit *ge = nullptr;
protected:
public:
@@ -103,24 +102,36 @@ 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;
- Button *zoom_reset;
- Button *zoom_plus;
+ Label *zoom_label = nullptr;
+ Button *zoom_minus = nullptr;
+ Button *zoom_reset = nullptr;
+ Button *zoom_plus = nullptr;
- Button *snap_button;
- SpinBox *snap_amount;
+ Button *snap_button = nullptr;
+ SpinBox *snap_amount = nullptr;
- Button *minimap_button;
+ Button *minimap_button = nullptr;
- Button *layout_button;
+ Button *layout_button = nullptr;
- HScrollBar *h_scroll;
- VScrollBar *v_scroll;
+ HScrollBar *h_scroll = nullptr;
+ VScrollBar *v_scroll = nullptr;
- float port_grab_distance_horizontal = 0.0;
- float port_grab_distance_vertical;
+ float port_hotzone_inner_extent = 0.0;
+ float port_hotzone_outer_extent = 0.0;
+
+ 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;
@@ -131,11 +142,12 @@ private:
bool connecting_target = false;
Vector2 connecting_to;
String connecting_target_to;
- int connecting_target_index;
+ int connecting_target_index = 0;
bool just_disconnected = false;
bool connecting_valid = false;
Vector2 click_pos;
+ PanningScheme panning_scheme = SCROLL_ZOOMS;
bool dragging = false;
bool just_selected = false;
bool moving_selection = false;
@@ -143,8 +155,9 @@ private:
float zoom = 1.0;
float zoom_step = 1.2;
- float zoom_min;
- float zoom_max;
+ // Proper values set in constructor.
+ float zoom_min = 0.0;
+ float zoom_max = 0.0;
void _zoom_minus();
void _zoom_reset();
@@ -165,6 +178,7 @@ private:
List<Connection> connections;
float lines_thickness = 2.0f;
+ float lines_curvature = 0.5f;
bool lines_antialiased = true;
PackedVector2Array get_connection_line(const Vector2 &p_from, const Vector2 &p_to);
@@ -178,12 +192,14 @@ private:
void _scroll_moved(double);
virtual void gui_input(const Ref<InputEvent> &p_ev) override;
- Control *connections_layer;
- GraphEditFilter *top_layer;
- GraphEditMinimap *minimap;
+ Control *connections_layer = nullptr;
+ GraphEditFilter *top_layer = nullptr;
+ GraphEditMinimap *minimap = nullptr;
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();
@@ -192,7 +208,7 @@ private:
Array _get_connection_list() const;
- bool lines_on_bg;
+ bool lines_on_bg = false;
struct ConnType {
union {
@@ -203,8 +219,11 @@ private:
uint64_t key = 0;
};
- bool operator<(const ConnType &p_type) const {
- return key < p_type.key;
+ static uint32_t hash(const ConnType &p_conn) {
+ return hash_one_uint64(p_conn.key);
+ }
+ bool operator==(const ConnType &p_type) const {
+ return key == p_type.key;
}
ConnType(uint32_t a = 0, uint32_t b = 0) {
@@ -213,16 +232,16 @@ private:
}
};
- Set<ConnType> valid_connection_types;
- Set<int> valid_left_disconnect_types;
- Set<int> valid_right_disconnect_types;
+ HashSet<ConnType, ConnType> valid_connection_types;
+ HashSet<int> valid_left_disconnect_types;
+ HashSet<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;
+ HBoxContainer *zoom_hb = nullptr;
friend class GraphEditFilter;
bool _filter_input(const Point2 &p_point);
@@ -232,7 +251,7 @@ private:
friend class GraphEditMinimap;
void _minimap_toggled();
- bool _check_clickable_control(Control *p_control, const Vector2 &pos);
+ bool _check_clickable_control(Control *p_control, const Vector2 &r_mouse_pos, const Vector2 &p_offset);
bool arranging_graph = false;
@@ -243,12 +262,12 @@ private:
UNION,
};
- int _set_operations(SET_OPERATIONS p_operation, Set<StringName> &r_u, const Set<StringName> &r_v);
- HashMap<int, Vector<StringName>> _layering(const Set<StringName> &r_selected_nodes, const HashMap<StringName, Set<StringName>> &r_upper_neighbours);
+ int _set_operations(SET_OPERATIONS p_operation, HashSet<StringName> &r_u, const HashSet<StringName> &r_v);
+ HashMap<int, Vector<StringName>> _layering(const HashSet<StringName> &r_selected_nodes, const HashMap<StringName, HashSet<StringName>> &r_upper_neighbours);
Vector<StringName> _split(const Vector<StringName> &r_layer, const HashMap<StringName, Dictionary> &r_crossings);
- void _horizontal_alignment(Dictionary &r_root, Dictionary &r_align, const HashMap<int, Vector<StringName>> &r_layers, const HashMap<StringName, Set<StringName>> &r_upper_neighbours, const Set<StringName> &r_selected_nodes);
- void _crossing_minimisation(HashMap<int, Vector<StringName>> &r_layers, const HashMap<StringName, Set<StringName>> &r_upper_neighbours);
- void _calculate_inner_shifts(Dictionary &r_inner_shifts, const Dictionary &r_root, const Dictionary &r_node_names, const Dictionary &r_align, const Set<StringName> &r_block_heads, const HashMap<StringName, Pair<int, int>> &r_port_info);
+ void _horizontal_alignment(Dictionary &r_root, Dictionary &r_align, const HashMap<int, Vector<StringName>> &r_layers, const HashMap<StringName, HashSet<StringName>> &r_upper_neighbours, const HashSet<StringName> &r_selected_nodes);
+ void _crossing_minimisation(HashMap<int, Vector<StringName>> &r_layers, const HashMap<StringName, HashSet<StringName>> &r_upper_neighbours);
+ void _calculate_inner_shifts(Dictionary &r_inner_shifts, const Dictionary &r_root, const Dictionary &r_node_names, const Dictionary &r_align, const HashSet<StringName> &r_block_heads, const HashMap<StringName, Pair<int, int>> &r_port_info);
float _calculate_threshold(StringName p_v, StringName p_w, const Dictionary &r_node_names, const HashMap<int, Vector<StringName>> &r_layers, const Dictionary &r_root, const Dictionary &r_align, const Dictionary &r_inner_shift, real_t p_current_threshold, const HashMap<StringName, Vector2> &r_node_positions);
void _place_block(StringName p_v, float p_delta, const HashMap<int, Vector<StringName>> &r_layers, const Dictionary &r_root, const Dictionary &r_align, const Dictionary &r_node_name, const Dictionary &r_inner_shift, Dictionary &r_sink, Dictionary &r_shift, HashMap<StringName, Vector2> &r_node_positions);
@@ -259,12 +278,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);
@@ -272,6 +294,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;
@@ -320,6 +345,9 @@ public:
int get_snap() const;
void set_snap(int p_snap);
+ void set_connection_lines_curvature(float p_curvature);
+ float get_connection_lines_curvature() const;
+
void set_connection_lines_thickness(float p_thickness);
float get_connection_lines_thickness() const;
@@ -327,10 +355,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 7a7b35585e..5cb28a30e8 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();
@@ -92,11 +93,13 @@ bool GraphNode::_set(const StringName &p_name, const Variant &p_value) {
si.color_right = p_value;
} else if (what == "right_icon") {
si.custom_slot_right = p_value;
+ } else if (what == "draw_stylebox") {
+ si.draw_stylebox = p_value;
} else {
return false;
}
- set_slot(idx, si.enable_left, si.type_left, si.color_left, si.enable_right, si.type_right, si.color_right, si.custom_slot_left, si.custom_slot_right);
+ set_slot(idx, si.enable_left, si.type_left, si.color_left, si.enable_right, si.type_right, si.color_right, si.custom_slot_left, si.custom_slot_right, si.draw_stylebox);
update();
return true;
}
@@ -143,6 +146,8 @@ bool GraphNode::_get(const StringName &p_name, Variant &r_ret) const {
r_ret = si.color_right;
} else if (what == "right_icon") {
r_ret = si.custom_slot_right;
+ } else if (what == "draw_stylebox") {
+ r_ret = si.draw_stylebox;
} else {
return false;
}
@@ -153,7 +158,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));
@@ -174,7 +179,7 @@ void GraphNode::_get_property_list(List<PropertyInfo> *p_list) const {
p_list->push_back(PropertyInfo(Variant::INT, base + "right_type"));
p_list->push_back(PropertyInfo(Variant::COLOR, base + "right_color"));
p_list->push_back(PropertyInfo(Variant::OBJECT, base + "right_icon", PROPERTY_HINT_RESOURCE_TYPE, "Texture2D", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_STORE_IF_NULL));
-
+ p_list->push_back(PropertyInfo(Variant::BOOL, base + "draw_stylebox"));
idx++;
}
}
@@ -184,6 +189,7 @@ void GraphNode::_resort() {
Size2i new_size = get_size();
Ref<StyleBox> sb = get_theme_stylebox(SNAME("frame"));
+ Ref<StyleBox> sb_slot = get_theme_stylebox(SNAME("slot"));
int sep = get_theme_constant(SNAME("separation"));
@@ -192,7 +198,7 @@ void GraphNode::_resort() {
int stretch_min = 0;
int stretch_avail = 0;
float stretch_ratio_total = 0;
- Map<Control *, _MinSizeCache> min_size_cache;
+ HashMap<Control *, _MinSizeCache> min_size_cache;
for (int i = 0; i < get_child_count(); i++) {
Control *c = Object::cast_to<Control>(get_child(i));
@@ -203,7 +209,7 @@ void GraphNode::_resort() {
continue;
}
- Size2i size = c->get_combined_minimum_size();
+ Size2i size = c->get_combined_minimum_size() + (slot_info[i].draw_stylebox ? sb_slot->get_minimum_size() : Size2());
_MinSizeCache msc;
stretch_min += size.height;
@@ -311,7 +317,9 @@ void GraphNode::_resort() {
int size = to - from;
- Rect2 rect(sb->get_margin(SIDE_LEFT), from, w, size);
+ float margin = sb->get_margin(SIDE_LEFT) + (slot_info[i].draw_stylebox ? sb_slot->get_margin(SIDE_LEFT) : 0);
+ float width = w - (slot_info[i].draw_stylebox ? sb_slot->get_minimum_size().x : 0);
+ Rect2 rect(margin, from, width, size);
fit_child_in_rect(c, rect);
cache_y.push_back(from - sb->get_margin(SIDE_TOP) + size * 0.5);
@@ -350,14 +358,14 @@ void GraphNode::_notification(int p_what) {
Ref<StyleBox> sb;
if (comment) {
- sb = get_theme_stylebox(selected ? "commentfocus" : "comment");
+ sb = get_theme_stylebox(selected ? SNAME("comment_focus") : SNAME("comment"));
} else {
- sb = get_theme_stylebox(selected ? "selectedframe" : "frame");
+ sb = get_theme_stylebox(selected ? SNAME("selected_frame") : SNAME("frame"));
}
- //sb=sb->duplicate();
- //sb->call("set_modulate",modulate);
+ Ref<StyleBox> sb_slot = get_theme_stylebox(SNAME("slot"));
+
Ref<Texture2D> port = get_theme_icon(SNAME("port"));
Ref<Texture2D> close = get_theme_icon(SNAME("close"));
Ref<Texture2D> resizer = get_theme_icon(SNAME("resizer"));
@@ -388,14 +396,9 @@ void GraphNode::_notification(int p_what) {
int w = get_size().width - sb->get_minimum_size().x;
- if (show_close) {
- w -= close->get_width();
- }
-
- title_buf->set_width(w);
title_buf->draw(get_canvas_item(), Point2(sb->get_margin(SIDE_LEFT) + title_h_offset, -title_buf->get_size().y + title_offset), title_color);
if (show_close) {
- Vector2 cpos = Point2(w + sb->get_margin(SIDE_LEFT) + close_h_offset, -close->get_height() + close_offset);
+ Vector2 cpos = Point2(w + sb->get_margin(SIDE_LEFT) + close_h_offset - close->get_width(), -close->get_height() + close_offset);
draw_texture(close, cpos, close_color);
close_rect.position = cpos;
close_rect.size = close->get_size();
@@ -411,7 +414,7 @@ void GraphNode::_notification(int p_what) {
continue;
}
const Slot &s = slot_info[E.key];
- //left
+ // Left port.
if (s.enable_left) {
Ref<Texture2D> p = port;
if (s.custom_slot_left.is_valid()) {
@@ -419,6 +422,7 @@ void GraphNode::_notification(int p_what) {
}
p->draw(get_canvas_item(), icofs + Point2(edgeofs, cache_y[E.key]), s.color_left);
}
+ // Right port.
if (s.enable_right) {
Ref<Texture2D> p = port;
if (s.custom_slot_right.is_valid()) {
@@ -426,6 +430,15 @@ void GraphNode::_notification(int p_what) {
}
p->draw(get_canvas_item(), icofs + Point2(get_size().x - edgeofs, cache_y[E.key]), s.color_right);
}
+
+ // Draw slot stylebox.
+ if (s.draw_stylebox) {
+ Control *c = Object::cast_to<Control>(get_child(E.key));
+ Rect2 c_rect = c->get_rect();
+ c_rect.position.x = sb->get_margin(SIDE_LEFT);
+ c_rect.size.width = w;
+ draw_style_box(sb_slot, c_rect);
+ }
}
if (resizable) {
@@ -482,7 +495,7 @@ void GraphNode::_validate_property(PropertyInfo &property) const {
}
#endif
-void GraphNode::set_slot(int p_idx, bool p_enable_left, int p_type_left, const Color &p_color_left, bool p_enable_right, int p_type_right, const Color &p_color_right, const Ref<Texture2D> &p_custom_left, const Ref<Texture2D> &p_custom_right) {
+void GraphNode::set_slot(int p_idx, bool p_enable_left, int p_type_left, const Color &p_color_left, bool p_enable_right, int p_type_right, const Color &p_color_right, const Ref<Texture2D> &p_custom_left, const Ref<Texture2D> &p_custom_right, bool p_draw_stylebox) {
ERR_FAIL_COND_MSG(p_idx < 0, vformat("Cannot set slot with p_idx (%d) lesser than zero.", p_idx));
if (!p_enable_left && p_type_left == 0 && p_color_left == Color(1, 1, 1, 1) &&
@@ -501,6 +514,7 @@ void GraphNode::set_slot(int p_idx, bool p_enable_left, int p_type_left, const C
s.color_right = p_color_right;
s.custom_slot_left = p_custom_left;
s.custom_slot_right = p_custom_right;
+ s.draw_stylebox = p_draw_stylebox;
slot_info[p_idx] = s;
update();
connpos_dirty = true;
@@ -622,16 +636,39 @@ Color GraphNode::get_slot_color_right(int p_idx) const {
return slot_info[p_idx].color_right;
}
+bool GraphNode::is_slot_draw_stylebox(int p_idx) const {
+ if (!slot_info.has(p_idx)) {
+ return false;
+ }
+ return slot_info[p_idx].draw_stylebox;
+}
+
+void GraphNode::set_slot_draw_stylebox(int p_idx, bool p_enable) {
+ ERR_FAIL_COND_MSG(p_idx < 0, vformat("Cannot set draw_stylebox for the slot with p_idx (%d) lesser than zero.", p_idx));
+
+ slot_info[p_idx].draw_stylebox = p_enable;
+ update();
+ connpos_dirty = true;
+
+ emit_signal(SNAME("slot_updated"), p_idx);
+}
+
Size2 GraphNode::get_minimum_size() const {
- int sep = get_theme_constant(SNAME("separation"));
Ref<StyleBox> sb = get_theme_stylebox(SNAME("frame"));
+ Ref<StyleBox> sb_slot = get_theme_stylebox(SNAME("slot"));
+
+ int sep = get_theme_constant(SNAME("separation"));
+ int title_h_offset = get_theme_constant(SNAME("title_h_offset"));
+
bool first = true;
Size2 minsize;
- minsize.x = title_buf->get_size().x;
+ minsize.x = title_buf->get_size().x + title_h_offset;
if (show_close) {
+ int close_h_offset = get_theme_constant(SNAME("close_h_offset"));
Ref<Texture2D> close = get_theme_icon(SNAME("close"));
- minsize.x += sep + close->get_width();
+ //TODO: Remove this magic number after GraphNode rework.
+ minsize.x += 12 + close->get_width() + close_h_offset;
}
for (int i = 0; i < get_child_count(); i++) {
@@ -644,6 +681,9 @@ Size2 GraphNode::get_minimum_size() const {
}
Size2i size = c->get_combined_minimum_size();
+ if (slot_info.has(i)) {
+ size += slot_info[i].draw_stylebox ? sb_slot->get_minimum_size() : Size2();
+ }
minsize.y += size.y;
minsize.x = MAX(minsize.x, size.x);
@@ -792,6 +832,7 @@ void GraphNode::_connpos_update() {
cc.pos = Point2i(edgeofs, y + h / 2);
cc.type = slot_info[idx].type_left;
cc.color = slot_info[idx].color_left;
+ cc.height = size.height;
conn_input_cache.push_back(cc);
}
if (slot_info[idx].enable_right) {
@@ -799,6 +840,7 @@ void GraphNode::_connpos_update() {
cc.pos = Point2i(get_size().width - edgeofs, y + h / 2);
cc.type = slot_info[idx].type_right;
cc.color = slot_info[idx].color_right;
+ cc.height = size.height;
conn_output_cache.push_back(cc);
}
}
@@ -819,12 +861,13 @@ int GraphNode::get_connection_input_count() {
return conn_input_cache.size();
}
-int GraphNode::get_connection_output_count() {
+int GraphNode::get_connection_input_height(int p_idx) {
if (connpos_dirty) {
_connpos_update();
}
- return conn_output_cache.size();
+ ERR_FAIL_INDEX_V(p_idx, conn_input_cache.size(), 0);
+ return conn_input_cache[p_idx].height;
}
Vector2 GraphNode::get_connection_input_position(int p_idx) {
@@ -857,6 +900,23 @@ Color GraphNode::get_connection_input_color(int p_idx) {
return conn_input_cache[p_idx].color;
}
+int GraphNode::get_connection_output_count() {
+ if (connpos_dirty) {
+ _connpos_update();
+ }
+
+ return conn_output_cache.size();
+}
+
+int GraphNode::get_connection_output_height(int p_idx) {
+ if (connpos_dirty) {
+ _connpos_update();
+ }
+
+ ERR_FAIL_INDEX_V(p_idx, conn_output_cache.size(), 0);
+ return conn_output_cache[p_idx].height;
+}
+
Vector2 GraphNode::get_connection_output_position(int p_idx) {
if (connpos_dirty) {
_connpos_update();
@@ -959,6 +1019,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);
@@ -970,7 +1049,7 @@ void GraphNode::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_language", "language"), &GraphNode::set_language);
ClassDB::bind_method(D_METHOD("get_language"), &GraphNode::get_language);
- ClassDB::bind_method(D_METHOD("set_slot", "idx", "enable_left", "type_left", "color_left", "enable_right", "type_right", "color_right", "custom_left", "custom_right"), &GraphNode::set_slot, DEFVAL(Ref<Texture2D>()), DEFVAL(Ref<Texture2D>()));
+ ClassDB::bind_method(D_METHOD("set_slot", "idx", "enable_left", "type_left", "color_left", "enable_right", "type_right", "color_right", "custom_left", "custom_right", "enable"), &GraphNode::set_slot, DEFVAL(Ref<Texture2D>()), DEFVAL(Ref<Texture2D>()), DEFVAL(true));
ClassDB::bind_method(D_METHOD("clear_slot", "idx"), &GraphNode::clear_slot);
ClassDB::bind_method(D_METHOD("clear_all_slots"), &GraphNode::clear_all_slots);
@@ -992,6 +1071,9 @@ void GraphNode::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_slot_color_right", "idx", "color_right"), &GraphNode::set_slot_color_right);
ClassDB::bind_method(D_METHOD("get_slot_color_right", "idx"), &GraphNode::get_slot_color_right);
+ ClassDB::bind_method(D_METHOD("is_slot_draw_stylebox", "idx"), &GraphNode::is_slot_draw_stylebox);
+ ClassDB::bind_method(D_METHOD("set_slot_draw_stylebox", "idx", "draw_stylebox"), &GraphNode::set_slot_draw_stylebox);
+
ClassDB::bind_method(D_METHOD("set_position_offset", "offset"), &GraphNode::set_position_offset);
ClassDB::bind_method(D_METHOD("get_position_offset"), &GraphNode::get_position_offset);
@@ -1004,15 +1086,17 @@ void GraphNode::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_selected", "selected"), &GraphNode::set_selected);
ClassDB::bind_method(D_METHOD("is_selected"), &GraphNode::is_selected);
- ClassDB::bind_method(D_METHOD("get_connection_output_count"), &GraphNode::get_connection_output_count);
ClassDB::bind_method(D_METHOD("get_connection_input_count"), &GraphNode::get_connection_input_count);
+ ClassDB::bind_method(D_METHOD("get_connection_input_height", "idx"), &GraphNode::get_connection_input_height);
+ ClassDB::bind_method(D_METHOD("get_connection_input_position", "idx"), &GraphNode::get_connection_input_position);
+ ClassDB::bind_method(D_METHOD("get_connection_input_type", "idx"), &GraphNode::get_connection_input_type);
+ ClassDB::bind_method(D_METHOD("get_connection_input_color", "idx"), &GraphNode::get_connection_input_color);
+ ClassDB::bind_method(D_METHOD("get_connection_output_count"), &GraphNode::get_connection_output_count);
+ ClassDB::bind_method(D_METHOD("get_connection_output_height", "idx"), &GraphNode::get_connection_output_height);
ClassDB::bind_method(D_METHOD("get_connection_output_position", "idx"), &GraphNode::get_connection_output_position);
ClassDB::bind_method(D_METHOD("get_connection_output_type", "idx"), &GraphNode::get_connection_output_type);
ClassDB::bind_method(D_METHOD("get_connection_output_color", "idx"), &GraphNode::get_connection_output_color);
- ClassDB::bind_method(D_METHOD("get_connection_input_position", "idx"), &GraphNode::get_connection_input_position);
- ClassDB::bind_method(D_METHOD("get_connection_input_type", "idx"), &GraphNode::get_connection_input_type);
- ClassDB::bind_method(D_METHOD("get_connection_input_color", "idx"), &GraphNode::get_connection_input_color);
ClassDB::bind_method(D_METHOD("set_show_close_button", "show"), &GraphNode::set_show_close_button);
ClassDB::bind_method(D_METHOD("is_close_button_visible"), &GraphNode::is_close_button_visible);
@@ -1022,7 +1106,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..f6c943dc89 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 */
@@ -54,6 +54,7 @@ private:
Color color_right = Color(1, 1, 1, 1);
Ref<Texture2D> custom_slot_left;
Ref<Texture2D> custom_slot_right;
+ bool draw_stylebox = true;
};
String title;
@@ -80,12 +81,13 @@ private:
Vector2 pos;
int type = 0;
Color color;
+ int height;
};
Vector<ConnCache> conn_input_cache;
Vector<ConnCache> conn_output_cache;
- Map<int, Slot> slot_info;
+ HashMap<int, Slot> slot_info;
bool connpos_dirty = true;
@@ -115,7 +117,7 @@ protected:
public:
bool has_point(const Point2 &p_point) const override;
- void set_slot(int p_idx, bool p_enable_left, int p_type_left, const Color &p_color_left, bool p_enable_right, int p_type_right, const Color &p_color_right, const Ref<Texture2D> &p_custom_left = Ref<Texture2D>(), const Ref<Texture2D> &p_custom_right = Ref<Texture2D>());
+ void set_slot(int p_idx, bool p_enable_left, int p_type_left, const Color &p_color_left, bool p_enable_right, int p_type_right, const Color &p_color_right, const Ref<Texture2D> &p_custom_left = Ref<Texture2D>(), const Ref<Texture2D> &p_custom_right = Ref<Texture2D>(), bool p_draw_stylebox = true);
void clear_slot(int p_idx);
void clear_all_slots();
@@ -137,6 +139,9 @@ public:
void set_slot_color_right(int p_idx, const Color &p_color_right);
Color get_slot_color_right(int p_idx) const;
+ bool is_slot_draw_stylebox(int p_idx) const;
+ void set_slot_draw_stylebox(int p_idx, bool p_enable);
+
void set_title(const String &p_title);
String get_title() const;
@@ -163,10 +168,13 @@ public:
bool is_close_button_visible() const;
int get_connection_input_count();
- int get_connection_output_count();
+ int get_connection_input_height(int p_idx);
Vector2 get_connection_input_position(int p_idx);
int get_connection_input_type(int p_idx);
Color get_connection_input_color(int p_idx);
+
+ int get_connection_output_count();
+ int get_connection_output_height(int p_idx);
Vector2 get_connection_output_position(int p_idx);
int get_connection_output_type(int p_idx);
Color get_connection_output_color(int p_idx);
@@ -182,7 +190,12 @@ public:
virtual Size2 get_minimum_size() const override;
- bool is_resizing() const { return resizing; }
+ 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 624330cdf6..6f8518a7b0 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 */
@@ -29,17 +29,18 @@
/*************************************************************************/
#include "grid_container.h"
+#include "core/templates/rb_set.h"
void GridContainer::_notification(int p_what) {
switch (p_what) {
case NOTIFICATION_SORT_CHILDREN: {
- Map<int, int> col_minw; // Max of min_width of all controls in each col (indexed by col).
- Map<int, int> row_minh; // Max of min_height of all controls in each row (indexed by row).
- Set<int> col_expanded; // Columns which have the SIZE_EXPAND flag set.
- Set<int> row_expanded; // Rows which have the SIZE_EXPAND flag set.
+ RBMap<int, int> col_minw; // Max of min_width of all controls in each col (indexed by col).
+ RBMap<int, int> row_minh; // Max of min_height of all controls in each row (indexed by row).
+ RBSet<int> col_expanded; // Columns which have the SIZE_EXPAND flag set.
+ RBSet<int> row_expanded; // Rows which have the SIZE_EXPAND flag set.
- int hsep = get_theme_constant(SNAME("hseparation"));
- int vsep = get_theme_constant(SNAME("vseparation"));
+ int hsep = get_theme_constant(SNAME("h_separation"));
+ int vsep = get_theme_constant(SNAME("v_separation"));
int max_col = MIN(get_child_count(), columns);
int max_row = ceil((float)get_child_count() / (float)columns);
@@ -50,6 +51,9 @@ void GridContainer::_notification(int p_what) {
if (!c || !c->is_visible_in_tree()) {
continue;
}
+ if (c->is_set_as_top_level()) {
+ continue;
+ }
int row = valid_controls_index / columns;
int col = valid_controls_index % columns;
@@ -101,11 +105,11 @@ void GridContainer::_notification(int p_what) {
// Check if all minwidth constraints are OK if we use the remaining space.
can_fit = true;
int max_index = col_expanded.front()->get();
- for (Set<int>::Element *E = col_expanded.front(); E; E = E->next()) {
- if (col_minw[E->get()] > col_minw[max_index]) {
- max_index = E->get();
+ for (const int &E : col_expanded) {
+ if (col_minw[E] > col_minw[max_index]) {
+ max_index = E;
}
- if (can_fit && (remaining_space.width / col_expanded.size()) < col_minw[E->get()]) {
+ if (can_fit && (remaining_space.width / col_expanded.size()) < col_minw[E]) {
can_fit = false;
}
}
@@ -122,11 +126,11 @@ void GridContainer::_notification(int p_what) {
// Check if all minheight constraints are OK if we use the remaining space.
can_fit = true;
int max_index = row_expanded.front()->get();
- for (Set<int>::Element *E = row_expanded.front(); E; E = E->next()) {
- if (row_minh[E->get()] > row_minh[max_index]) {
- max_index = E->get();
+ for (const int &E : row_expanded) {
+ if (row_minh[E] > row_minh[max_index]) {
+ max_index = E;
}
- if (can_fit && (remaining_space.height / row_expanded.size()) < row_minh[E->get()]) {
+ if (can_fit && (remaining_space.height / row_expanded.size()) < row_minh[E]) {
can_fit = false;
}
}
@@ -139,13 +143,47 @@ void GridContainer::_notification(int p_what) {
}
// Finally, fit the nodes.
- int col_expand = col_expanded.size() > 0 ? remaining_space.width / col_expanded.size() : 0;
- int row_expand = row_expanded.size() > 0 ? remaining_space.height / row_expanded.size() : 0;
+ int col_remaining_pixel = 0;
+ int col_expand = 0;
+ if (col_expanded.size() > 0) {
+ col_expand = remaining_space.width / col_expanded.size();
+ col_remaining_pixel = remaining_space.width - col_expanded.size() * col_expand;
+ }
+
+ int row_remaining_pixel = 0;
+ int row_expand = 0;
+ if (row_expanded.size() > 0) {
+ row_expand = remaining_space.height / row_expanded.size();
+ row_remaining_pixel = remaining_space.height - row_expanded.size() * row_expand;
+ }
+
bool rtl = is_layout_rtl();
int col_ofs = 0;
int row_ofs = 0;
+ // Calculate the index of rows and columns that receive the remaining pixel.
+ int col_remaining_pixel_index = 0;
+ for (int i = 0; i < max_col; i++) {
+ if (col_remaining_pixel == 0) {
+ break;
+ }
+ if (col_expanded.has(i)) {
+ col_remaining_pixel_index = i + 1;
+ col_remaining_pixel--;
+ }
+ }
+ int row_remaining_pixel_index = 0;
+ for (int i = 0; i < max_row; i++) {
+ if (row_remaining_pixel == 0) {
+ break;
+ }
+ if (row_expanded.has(i)) {
+ row_remaining_pixel_index = i + 1;
+ row_remaining_pixel--;
+ }
+ }
+
valid_controls_index = 0;
for (int i = 0; i < get_child_count(); i++) {
Control *c = Object::cast_to<Control>(get_child(i));
@@ -164,26 +202,40 @@ void GridContainer::_notification(int p_what) {
}
if (row > 0) {
row_ofs += (row_expanded.has(row - 1) ? row_expand : row_minh[row - 1]) + vsep;
+
+ if (row_expanded.has(row - 1) && row - 1 < row_remaining_pixel_index) {
+ // Apply the remaining pixel of the previous row.
+ row_ofs++;
+ }
}
}
+ Size2 s(col_expanded.has(col) ? col_expand : col_minw[col], row_expanded.has(row) ? row_expand : row_minh[row]);
+
+ // Add the remaining pixel to the expanding columns and rows, starting from left and top.
+ if (col_expanded.has(col) && col < col_remaining_pixel_index) {
+ s.x++;
+ }
+ if (row_expanded.has(row) && row < row_remaining_pixel_index) {
+ s.y++;
+ }
+
if (rtl) {
- Size2 s(col_expanded.has(col) ? col_expand : col_minw[col], row_expanded.has(row) ? row_expand : row_minh[row]);
Point2 p(col_ofs - s.width, row_ofs);
fit_child_in_rect(c, Rect2(p, s));
col_ofs -= s.width + hsep;
} else {
Point2 p(col_ofs, row_ofs);
- Size2 s(col_expanded.has(col) ? col_expand : col_minw[col], row_expanded.has(row) ? row_expand : row_minh[row]);
fit_child_in_rect(c, Rect2(p, s));
col_ofs += s.width + hsep;
}
}
-
} break;
+
case NOTIFICATION_THEME_CHANGED: {
update_minimum_size();
} break;
+
case NOTIFICATION_TRANSLATION_CHANGED:
case NOTIFICATION_LAYOUT_DIRECTION_CHANGED: {
queue_sort();
@@ -210,11 +262,11 @@ void GridContainer::_bind_methods() {
}
Size2 GridContainer::get_minimum_size() const {
- Map<int, int> col_minw;
- Map<int, int> row_minh;
+ RBMap<int, int> col_minw;
+ RBMap<int, int> row_minh;
- int hsep = get_theme_constant(SNAME("hseparation"));
- int vsep = get_theme_constant(SNAME("vseparation"));
+ int hsep = get_theme_constant(SNAME("h_separation"));
+ int vsep = get_theme_constant(SNAME("v_separation"));
int max_row = 0;
int max_col = 0;
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 6deb39b9e2..8b22f3722a 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 */
@@ -83,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;
@@ -97,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) {
@@ -119,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) {
@@ -138,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;
@@ -152,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;
}
@@ -162,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;
@@ -175,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;
@@ -189,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;
@@ -203,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;
@@ -217,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;
@@ -230,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;
@@ -243,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;
@@ -256,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;
@@ -270,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;
@@ -281,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;
@@ -293,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;
@@ -537,6 +585,9 @@ Size2 ItemList::Item::get_icon_size() const {
void ItemList::gui_input(const Ref<InputEvent> &p_event) {
ERR_FAIL_COND(p_event.is_null());
+#define CAN_SELECT(i) (items[i].selectable && !items[i].disabled)
+#define IS_SAME_ROW(i, row) (i / current_columns == row)
+
double prev_scroll = scroll_bar->get_value();
Ref<InputEventMouseMotion> mm = p_event;
@@ -555,7 +606,7 @@ void ItemList::gui_input(const Ref<InputEvent> &p_event) {
return;
}
- if (mb.is_valid() && (mb->get_button_index() == MouseButton::LEFT || (allow_rmb_select && mb->get_button_index() == MouseButton::RIGHT)) && mb->is_pressed()) {
+ if (mb.is_valid() && mb->is_pressed()) {
search_string = ""; //any mousepress cancels
Vector2 pos = mb->get_position();
Ref<StyleBox> bg = get_theme_stylebox(SNAME("bg"));
@@ -580,7 +631,7 @@ void ItemList::gui_input(const Ref<InputEvent> &p_event) {
}
}
- if (closest != -1) {
+ if (closest != -1 && (mb->get_button_index() == MouseButton::LEFT || (allow_rmb_select && mb->get_button_index() == MouseButton::RIGHT))) {
int i = closest;
if (select_mode == SELECT_MULTI && items[i].selected && mb->is_command_pressed()) {
@@ -594,55 +645,47 @@ void ItemList::gui_input(const Ref<InputEvent> &p_event) {
SWAP(from, to);
}
for (int j = from; j <= to; j++) {
+ if (!CAN_SELECT(j)) {
+ continue;
+ }
bool selected = !items[j].selected;
select(j, false);
if (selected) {
emit_signal(SNAME("multi_selected"), j, true);
}
}
+ emit_signal(SNAME("item_clicked"), i, get_local_mouse_position(), mb->get_button_index());
- 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() == MouseButton::LEFT) {
defer_select_single = i;
return;
}
- 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;
-
+ if (!items[i].selected || allow_reselect) {
select(i, select_mode == SELECT_SINGLE || !mb->is_command_pressed());
- if (!selected || allow_reselect) {
- if (select_mode == SELECT_SINGLE) {
- emit_signal(SNAME("item_selected"), i);
- } else {
- emit_signal(SNAME("multi_selected"), i, true);
- }
+ if (select_mode == SELECT_SINGLE) {
+ emit_signal(SNAME("item_selected"), i);
+ } else {
+ emit_signal(SNAME("multi_selected"), i, true);
}
+ }
- 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);
- }
+ emit_signal(SNAME("item_clicked"), i, get_local_mouse_position(), mb->get_button_index());
+
+ if (mb->get_button_index() == MouseButton::LEFT && mb->is_double_click()) {
+ emit_signal(SNAME("item_activated"), i);
}
}
return;
+ } else if (closest != -1) {
+ emit_signal(SNAME("item_clicked"), closest, get_local_mouse_position(), mb->get_button_index());
+ } else {
+ // 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("empty_clicked"), get_local_mouse_position(), mb->get_button_index());
}
- if (mb->get_button_index() == MouseButton::RIGHT) {
- emit_signal(SNAME("rmb_clicked"), mb->get_position());
-
- return;
- }
-
- // 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() == MouseButton::WHEEL_UP && mb->is_pressed()) {
scroll_bar->set_value(scroll_bar->get_value() - scroll_bar->get_page() * mb->get_factor() / 8);
@@ -659,7 +702,7 @@ void ItemList::gui_input(const Ref<InputEvent> &p_event) {
if (diff < uint64_t(ProjectSettings::get_singleton()->get("gui/timers/incremental_search_max_interval_msec")) * 2) {
for (int i = current - 1; i >= 0; i--) {
- if (items[i].text.begins_with(search_string)) {
+ if (CAN_SELECT(i) && items[i].text.begins_with(search_string)) {
set_current(i);
ensure_current_is_visible();
if (select_mode == SELECT_SINGLE) {
@@ -675,7 +718,15 @@ void ItemList::gui_input(const Ref<InputEvent> &p_event) {
}
if (current >= current_columns) {
- set_current(current - current_columns);
+ int next = current - current_columns;
+ while (next >= 0 && !CAN_SELECT(next)) {
+ next = next - current_columns;
+ }
+ if (next < 0) {
+ accept_event();
+ return;
+ }
+ set_current(next);
ensure_current_is_visible();
if (select_mode == SELECT_SINGLE) {
emit_signal(SNAME("item_selected"), current);
@@ -689,7 +740,7 @@ void ItemList::gui_input(const Ref<InputEvent> &p_event) {
if (diff < uint64_t(ProjectSettings::get_singleton()->get("gui/timers/incremental_search_max_interval_msec")) * 2) {
for (int i = current + 1; i < items.size(); i++) {
- if (items[i].text.begins_with(search_string)) {
+ if (CAN_SELECT(i) && items[i].text.begins_with(search_string)) {
set_current(i);
ensure_current_is_visible();
if (select_mode == SELECT_SINGLE) {
@@ -704,7 +755,15 @@ void ItemList::gui_input(const Ref<InputEvent> &p_event) {
}
if (current < items.size() - current_columns) {
- set_current(current + current_columns);
+ int next = current + current_columns;
+ while (next < items.size() && !CAN_SELECT(next)) {
+ next = next + current_columns;
+ }
+ if (next >= items.size()) {
+ accept_event();
+ return;
+ }
+ set_current(next);
ensure_current_is_visible();
if (select_mode == SELECT_SINGLE) {
emit_signal(SNAME("item_selected"), current);
@@ -715,7 +774,7 @@ void ItemList::gui_input(const Ref<InputEvent> &p_event) {
search_string = ""; //any mousepress cancels
for (int i = 4; i > 0; i--) {
- if (current - current_columns * i >= 0) {
+ if (current - current_columns * i >= 0 && CAN_SELECT(current - current_columns * i)) {
set_current(current - current_columns * i);
ensure_current_is_visible();
if (select_mode == SELECT_SINGLE) {
@@ -729,7 +788,7 @@ void ItemList::gui_input(const Ref<InputEvent> &p_event) {
search_string = ""; //any mousepress cancels
for (int i = 4; i > 0; i--) {
- if (current + current_columns * i < items.size()) {
+ if (current + current_columns * i < items.size() && CAN_SELECT(current + current_columns * i)) {
set_current(current + current_columns * i);
ensure_current_is_visible();
if (select_mode == SELECT_SINGLE) {
@@ -744,7 +803,16 @@ void ItemList::gui_input(const Ref<InputEvent> &p_event) {
search_string = ""; //any mousepress cancels
if (current % current_columns != 0) {
- set_current(current - 1);
+ int current_row = current / current_columns;
+ int next = current - 1;
+ while (!CAN_SELECT(next)) {
+ next = next - 1;
+ }
+ if (next < 0 || !IS_SAME_ROW(next, current_row)) {
+ accept_event();
+ return;
+ }
+ set_current(next);
ensure_current_is_visible();
if (select_mode == SELECT_SINGLE) {
emit_signal(SNAME("item_selected"), current);
@@ -755,7 +823,16 @@ void ItemList::gui_input(const Ref<InputEvent> &p_event) {
search_string = ""; //any mousepress cancels
if (current % current_columns != (current_columns - 1) && current + 1 < items.size()) {
- set_current(current + 1);
+ int current_row = current / current_columns;
+ int next = current + 1;
+ while (!CAN_SELECT(next)) {
+ next = next + 1;
+ }
+ if (items.size() <= next || !IS_SAME_ROW(next, current_row)) {
+ accept_event();
+ return;
+ }
+ set_current(next);
ensure_current_is_visible();
if (select_mode == SELECT_SINGLE) {
emit_signal(SNAME("item_selected"), current);
@@ -831,6 +908,9 @@ void ItemList::gui_input(const Ref<InputEvent> &p_event) {
if (scroll_bar->get_value() != prev_scroll) {
accept_event(); //accept event if scroll changed
}
+
+#undef CAN_SELECT
+#undef IS_SAME_ROW
}
void ItemList::ensure_current_is_visible() {
@@ -855,445 +935,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("h_separation"));
+ int vseparation = get_theme_constant(SNAME("v_separation"));
+ int icon_margin = get_theme_constant(SNAME("icon_margin"));
+ int line_separation = get_theme_constant(SNAME("line_separation"));
+ Color font_outline_color = get_theme_color(SNAME("font_outline_color"));
+ int outline_size = get_theme_constant(SNAME("outline_size"));
- 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.is_empty()) {
- if (icon_mode == ICON_MODE_TOP) {
- minsize.y += icon_margin;
- } else {
- minsize.x += icon_margin;
+ 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.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 (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));
}
}
- update_minimum_size();
- 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;
+ }
- if (!items[i].text.is_empty()) {
- int max_len = -1;
+ Color modulate = items[i].icon_modulate;
+ if (items[i].disabled) {
+ modulate.a *= 0.5;
+ }
- 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;
- }
+ // 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;
+ }
- 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;
- }
+ 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 && max_text_lines > 0) {
- text_ofs += base_ofs;
- text_ofs += items[i].rect_cache.position;
+ 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 (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_alignment(HORIZONTAL_ALIGNMENT_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_alignment(HORIZONTAL_ALIGNMENT_RIGHT);
+ items[i].text_buf->draw(get_canvas_item(), text_ofs, modulate);
} else {
- items.write[i].text_buf->set_alignment(HORIZONTAL_ALIGNMENT_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 (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 (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;
}
- 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;
}
}
@@ -1492,6 +1578,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
@@ -1528,6 +1617,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;
@@ -1541,6 +1633,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);
@@ -1652,7 +1748,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);
@@ -1680,11 +1776,10 @@ void ItemList::_bind_methods() {
BIND_ENUM_CONSTANT(SELECT_MULTI);
ADD_SIGNAL(MethodInfo("item_selected", PropertyInfo(Variant::INT, "index")));
- ADD_SIGNAL(MethodInfo("item_rmb_selected", PropertyInfo(Variant::INT, "index"), PropertyInfo(Variant::VECTOR2, "at_position")));
+ ADD_SIGNAL(MethodInfo("empty_clicked", PropertyInfo(Variant::VECTOR2, "at_position"), PropertyInfo(Variant::INT, "mouse_button_index")));
+ ADD_SIGNAL(MethodInfo("item_clicked", PropertyInfo(Variant::INT, "index"), PropertyInfo(Variant::VECTOR2, "at_position"), PropertyInfo(Variant::INT, "mouse_button_index")));
ADD_SIGNAL(MethodInfo("multi_selected", PropertyInfo(Variant::INT, "index"), PropertyInfo(Variant::BOOL, "selected")));
ADD_SIGNAL(MethodInfo("item_activated", PropertyInfo(Variant::INT, "index")));
- ADD_SIGNAL(MethodInfo("rmb_clicked", PropertyInfo(Variant::VECTOR2, "at_position")));
- ADD_SIGNAL(MethodInfo("nothing_selected"));
GLOBAL_DEF("gui/timers/incremental_search_max_interval_msec", 2000);
ProjectSettings::get_singleton()->set_custom_property_info("gui/timers/incremental_search_max_interval_msec", PropertyInfo(Variant::INT, "gui/timers/incremental_search_max_interval_msec", PROPERTY_HINT_RANGE, "0,10000,1,or_greater")); // No negative numbers
diff --git a/scene/gui/item_list.h b/scene/gui/item_list.h
index e780179e7b..ffbe7d055a 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;
@@ -98,8 +98,8 @@ private:
SelectMode select_mode = SELECT_SINGLE;
IconMode icon_mode = ICON_MODE_LEFT;
- VScrollBar *scroll_bar;
- TextParagraph::OverrunBehavior text_overrun_behavior = TextParagraph::OVERRUN_NO_TRIMMING;
+ VScrollBar *scroll_bar = nullptr;
+ 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 0a09d87509..eda3d40f63 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 */
@@ -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,19 +95,27 @@ 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.is_empty()) ? 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;
}
if (lines_dirty) {
for (int i = 0; i < lines_rid.size(); i++) {
- TS->free(lines_rid[i]);
+ TS->free_rid(lines_rid[i]);
}
lines_rid.clear();
@@ -183,11 +194,9 @@ 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++) {
@@ -254,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 (vertical_alignment) {
- case VERTICAL_ALIGNMENT_TOP: {
- // Nothing.
- } break;
- case VERTICAL_ALIGNMENT_CENTER: {
- vbegin = (size.y - (total_h - line_spacing)) / 2;
- vsep = 0;
-
- } break;
- case VERTICAL_ALIGNMENT_BOTTOM: {
- vbegin = size.y - (total_h - line_spacing);
- vsep = 0;
-
- } break;
- case VERTICAL_ALIGNMENT_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 (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;
+ } 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 ((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++) {
- //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();
}
@@ -524,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();
}
@@ -599,12 +643,12 @@ 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();
}
}
-void Label::set_structured_text_bidi_override(Control::StructuredTextParser p_parser) {
+void Label::set_structured_text_bidi_override(TextServer::StructuredTextParser p_parser) {
if (st_parser != p_parser) {
st_parser = p_parser;
dirty = true;
@@ -612,7 +656,7 @@ void Label::set_structured_text_bidi_override(Control::StructuredTextParser p_pa
}
}
-Control::StructuredTextParser Label::get_structured_text_bidi_override() const {
+TextServer::StructuredTextParser Label::get_structured_text_bidi_override() const {
return st_parser;
}
@@ -632,7 +676,7 @@ Control::TextDirection Label::get_text_direction() const {
void Label::clear_opentype_features() {
opentype_features.clear();
- dirty = true;
+ font_dirty = true;
update();
}
@@ -640,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();
}
}
@@ -702,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();
}
}
@@ -720,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();
}
}
@@ -729,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;
@@ -751,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();
}
@@ -763,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();
}
}
@@ -803,7 +863,7 @@ 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));
}
@@ -836,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);
@@ -858,20 +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_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::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");
@@ -887,8 +960,8 @@ Label::Label(const String &p_text) {
Label::~Label() {
for (int i = 0; i < lines_rid.size(); i++) {
- TS->free(lines_rid[i]);
+ TS->free_rid(lines_rid[i]);
}
lines_rid.clear();
- TS->free(text_rid);
+ TS->free_rid(text_rid);
}
diff --git a/scene/gui/label.h b/scene/gui/label.h
index 2ad8ae8818..f7b725928f 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 */
@@ -52,6 +52,14 @@ 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:
HorizontalAlignment horizontal_alignment = HORIZONTAL_ALIGNMENT_LEFT;
VerticalAlignment vertical_alignment = VERTICAL_ALIGNMENT_TOP;
@@ -65,17 +73,19 @@ private:
bool lines_dirty = true;
bool dirty = true;
+ bool font_dirty = true;
RID text_rid;
Vector<RID> lines_rid;
Dictionary opentype_features;
String language;
TextDirection text_direction = TEXT_DIRECTION_AUTO;
- Control::StructuredTextParser st_parser = STRUCTURED_TEXT_DEFAULT;
+ TextServer::StructuredTextParser st_parser = TextServer::STRUCTURED_TEXT_DEFAULT;
Array st_args;
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;
@@ -114,8 +124,8 @@ public:
void set_language(const String &p_language);
String get_language() const;
- void set_structured_text_bidi_override(Control::StructuredTextParser p_parser);
- Control::StructuredTextParser get_structured_text_bidi_override() const;
+ void set_structured_text_bidi_override(TextServer::StructuredTextParser p_parser);
+ TextServer::StructuredTextParser get_structured_text_bidi_override() const;
void set_structured_text_bidi_override_options(Array p_args);
Array get_structured_text_bidi_override_options() const;
@@ -126,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;
@@ -155,5 +168,6 @@ public:
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 810781995e..fa600d24ef 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 */
@@ -215,6 +215,27 @@ void LineEdit::_delete(bool p_word, bool p_all_to_right) {
}
}
+void LineEdit::unhandled_key_input(const Ref<InputEvent> &p_event) {
+ Ref<InputEventKey> k = p_event;
+
+ if (k.is_valid()) {
+ if (!k->is_pressed()) {
+ return;
+ }
+ // Handle Unicode (with modifiers active, process after shortcuts).
+ if (has_focus() && editable && (k->get_unicode() >= 32)) {
+ selection_delete();
+ char32_t ucodestr[2] = { (char32_t)k->get_unicode(), 0 };
+ int prev_len = text.length();
+ insert_text_at_caret(ucodestr);
+ if (text.length() != prev_len) {
+ _text_changed();
+ }
+ accept_event();
+ }
+ }
+}
+
void LineEdit::gui_input(const Ref<InputEvent> &p_event) {
ERR_FAIL_COND(p_event.is_null());
@@ -385,9 +406,45 @@ void LineEdit::gui_input(const Ref<InputEvent> &p_event) {
if (k.is_valid()) {
if (!k->is_pressed()) {
+ if (alt_start && k->get_keycode() == Key::ALT) {
+ alt_start = false;
+ if ((alt_code > 0x31 && alt_code < 0xd800) || (alt_code > 0xdfff && alt_code <= 0x10ffff)) {
+ char32_t ucodestr[2] = { (char32_t)alt_code, 0 };
+ insert_text_at_caret(ucodestr);
+ }
+ accept_event();
+ return;
+ }
return;
}
+ // Alt+ Unicode input:
+ if (k->is_alt_pressed()) {
+ if (!alt_start) {
+ if (k->get_keycode() == Key::KP_ADD) {
+ alt_start = true;
+ alt_code = 0;
+ accept_event();
+ return;
+ }
+ } else {
+ if (k->get_keycode() >= Key::KEY_0 && k->get_keycode() <= Key::KEY_9) {
+ alt_code = alt_code << 4;
+ alt_code += (uint32_t)(k->get_keycode() - Key::KEY_0);
+ }
+ if (k->get_keycode() >= Key::KP_0 && k->get_keycode() <= Key::KP_9) {
+ alt_code = alt_code << 4;
+ alt_code += (uint32_t)(k->get_keycode() - Key::KP_0);
+ }
+ if (k->get_keycode() >= Key::A && k->get_keycode() <= Key::F) {
+ alt_code = alt_code << 4;
+ alt_code += (uint32_t)(k->get_keycode() - Key::A) + 10;
+ }
+ accept_event();
+ return;
+ }
+ }
+
if (context_menu_enabled) {
if (k->is_action("ui_menu", true)) {
_ensure_menu();
@@ -638,7 +695,7 @@ bool LineEdit::_is_over_clear_button(const Point2 &p_pos) const {
return false;
}
Ref<Texture2D> icon = Control::get_theme_icon(SNAME("clear"));
- int x_ofs = get_theme_stylebox(SNAME("normal"))->get_offset().x;
+ int x_ofs = get_theme_stylebox(SNAME("normal"))->get_margin(SIDE_RIGHT);
return p_pos.x > get_size().width - icon->get_width() - x_ofs;
}
@@ -647,8 +704,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));
@@ -656,31 +713,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;
@@ -752,7 +815,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;
@@ -780,8 +843,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) {
@@ -843,6 +904,9 @@ void LineEdit::_notification(int p_what) {
// Draw carets.
ofs.x = x_ofs + scroll_offset;
if (draw_caret || drag_caret_force_displayed) {
+ // Prevent carets from disappearing at theme scales below 1.0 (if the caret width is 1).
+ const int caret_width = get_theme_constant(SNAME("caret_width")) * MAX(1, get_theme_default_base_scale());
+
if (ime_text.length() == 0) {
// Normal caret.
CaretInfo caret = TS->shaped_text_get_carets(text_rid, caret_column);
@@ -923,6 +987,7 @@ void LineEdit::_notification(int p_what) {
}
}
} break;
+
case NOTIFICATION_FOCUS_ENTER: {
if (!caret_force_displayed) {
if (caret_blink_enabled) {
@@ -942,6 +1007,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();
@@ -964,6 +1030,7 @@ void LineEdit::_notification(int p_what) {
deselect();
}
} break;
+
case MainLoop::NOTIFICATION_OS_IME_UPDATE: {
if (has_focus()) {
ime_text = DisplayServer::get_singleton()->ime_get_text();
@@ -974,10 +1041,12 @@ void LineEdit::_notification(int p_what) {
update();
}
} break;
- case Control::NOTIFICATION_DRAG_BEGIN: {
+
+ case NOTIFICATION_DRAG_BEGIN: {
drag_action = true;
} break;
- case Control::NOTIFICATION_DRAG_END: {
+
+ case NOTIFICATION_DRAG_END: {
if (is_drag_successful()) {
if (selection.drag_attempt) {
selection.drag_attempt = false;
@@ -1419,7 +1488,7 @@ bool LineEdit::get_draw_control_chars() const {
return draw_control_chars;
}
-void LineEdit::set_structured_text_bidi_override(Control::StructuredTextParser p_parser) {
+void LineEdit::set_structured_text_bidi_override(TextServer::StructuredTextParser p_parser) {
if (st_parser != p_parser) {
st_parser = p_parser;
_shape();
@@ -1427,7 +1496,7 @@ void LineEdit::set_structured_text_bidi_override(Control::StructuredTextParser p
}
}
-Control::StructuredTextParser LineEdit::get_structured_text_bidi_override() const {
+TextServer::StructuredTextParser LineEdit::get_structured_text_bidi_override() const {
return st_parser;
}
@@ -1476,15 +1545,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();
@@ -1615,7 +1675,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) {
@@ -1626,13 +1686,17 @@ Size2 LineEdit::get_minimum_size() const {
min_size.height = MAX(TS->shaped_text_get_size(text_rid).y + font->get_spacing(TextServer::SPACING_TOP) + font->get_spacing(TextServer::SPACING_BOTTOM), font->get_height(font_size));
// Take icons into account.
- bool using_placeholder = text.is_empty() && ime_text.is_empty();
- bool display_clear_icon = !using_placeholder && is_editable() && clear_button_enabled;
- if (right_icon.is_valid() || display_clear_icon) {
- Ref<Texture2D> r_icon = display_clear_icon ? Control::get_theme_icon(SNAME("clear")) : right_icon;
- min_size.width += r_icon->get_width();
- min_size.height = MAX(min_size.height, r_icon->get_height());
+ int icon_max_width = 0;
+ if (right_icon.is_valid()) {
+ min_size.height = MAX(min_size.height, right_icon->get_height());
+ icon_max_width = right_icon->get_width();
+ }
+ if (clear_button_enabled) {
+ Ref<Texture2D> clear_icon = Control::get_theme_icon(SNAME("clear"));
+ min_size.height = MAX(min_size.height, clear_icon->get_height());
+ icon_max_width = MAX(icon_max_width, clear_icon->get_width());
}
+ min_size.width += icon_max_width;
return style->get_minimum_size() + min_size;
}
@@ -1941,8 +2005,8 @@ 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
}
@@ -2164,7 +2228,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);
@@ -2172,7 +2236,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();
@@ -2204,7 +2268,7 @@ 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));
}
@@ -2245,8 +2309,6 @@ void LineEdit::_bind_methods() {
ClassDB::bind_method(D_METHOD("get_structured_text_bidi_override_options"), &LineEdit::get_structured_text_bidi_override_options);
ClassDB::bind_method(D_METHOD("set_placeholder", "text"), &LineEdit::set_placeholder);
ClassDB::bind_method(D_METHOD("get_placeholder"), &LineEdit::get_placeholder);
- ClassDB::bind_method(D_METHOD("set_placeholder_alpha", "alpha"), &LineEdit::set_placeholder_alpha);
- ClassDB::bind_method(D_METHOD("get_placeholder_alpha"), &LineEdit::get_placeholder_alpha);
ClassDB::bind_method(D_METHOD("set_caret_column", "position"), &LineEdit::set_caret_column);
ClassDB::bind_method(D_METHOD("get_caret_column"), &LineEdit::get_caret_column);
ClassDB::bind_method(D_METHOD("get_scroll_offset"), &LineEdit::get_scroll_offset);
@@ -2328,6 +2390,7 @@ void LineEdit::_bind_methods() {
BIND_ENUM_CONSTANT(MENU_MAX);
ADD_PROPERTY(PropertyInfo(Variant::STRING, "text"), "set_text", "get_text");
+ ADD_PROPERTY(PropertyInfo(Variant::STRING, "placeholder_text"), "set_placeholder", "get_placeholder");
ADD_PROPERTY(PropertyInfo(Variant::INT, "alignment", PROPERTY_HINT_ENUM, "Left,Center,Right,Fill"), "set_horizontal_alignment", "get_horizontal_alignment");
ADD_PROPERTY(PropertyInfo(Variant::INT, "max_length", PROPERTY_HINT_RANGE, "0,1000,1,or_greater"), "set_max_length", "get_max_length");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "editable"), "set_editable", "is_editable");
@@ -2344,14 +2407,11 @@ void LineEdit::_bind_methods() {
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");
@@ -2438,7 +2498,7 @@ void LineEdit::_ensure_menu() {
}
}
-LineEdit::LineEdit() {
+LineEdit::LineEdit(const String &p_placeholder) {
text_rid = TS->create_shaped_text();
_create_undo_state();
@@ -2446,6 +2506,7 @@ LineEdit::LineEdit() {
set_focus_mode(FOCUS_ALL);
set_default_cursor_shape(CURSOR_IBEAM);
set_mouse_filter(MOUSE_FILTER_STOP);
+ set_process_unhandled_key_input(true);
caret_blink_timer = memnew(Timer);
add_child(caret_blink_timer, false, INTERNAL_MODE_FRONT);
@@ -2453,9 +2514,11 @@ 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.
}
LineEdit::~LineEdit() {
- TS->free(text_rid);
+ TS->free_rid(text_rid);
}
diff --git a/scene/gui/line_edit.h b/scene/gui/line_edit.h
index 854e54e8f1..0fb178fca4 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 */
@@ -77,12 +77,14 @@ private:
bool pass = false;
bool text_changed_dirty = false;
+ bool alt_start = false;
+ uint32_t alt_code = 0;
+
String undo_text;
String text;
String placeholder;
String placeholder_translated;
String secret_character = "*";
- float placeholder_alpha = 0.6;
String ime_text;
Point2 ime_selection;
@@ -97,7 +99,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;
@@ -107,7 +109,7 @@ private:
String language;
TextDirection text_direction = TEXT_DIRECTION_AUTO;
TextDirection input_direction = TEXT_DIRECTION_LTR;
- Control::StructuredTextParser st_parser = STRUCTURED_TEXT_DEFAULT;
+ TextServer::StructuredTextParser st_parser = TextServer::STRUCTURED_TEXT_DEFAULT;
Array st_args;
bool draw_control_chars = false;
@@ -203,6 +205,7 @@ private:
protected:
void _notification(int p_what);
static void _bind_methods();
+ virtual void unhandled_key_input(const Ref<InputEvent> &p_event) override;
virtual void gui_input(const Ref<InputEvent> &p_event) override;
bool _set(const StringName &p_name, const Variant &p_value);
@@ -253,8 +256,8 @@ public:
void set_draw_control_chars(bool p_draw_control_chars);
bool get_draw_control_chars() const;
- void set_structured_text_bidi_override(Control::StructuredTextParser p_parser);
- Control::StructuredTextParser get_structured_text_bidi_override() const;
+ void set_structured_text_bidi_override(TextServer::StructuredTextParser p_parser);
+ TextServer::StructuredTextParser get_structured_text_bidi_override() const;
void set_structured_text_bidi_override_options(Array p_args);
Array get_structured_text_bidi_override_options() const;
@@ -262,9 +265,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;
@@ -336,7 +336,7 @@ public:
void show_virtual_keyboard();
- LineEdit();
+ LineEdit(const String &p_placeholder = String());
~LineEdit();
};
diff --git a/scene/gui/link_button.cpp b/scene/gui/link_button.cpp
index b59f4fb7d4..dca6437519 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() {
@@ -60,7 +61,7 @@ String LinkButton::get_text() const {
return text;
}
-void LinkButton::set_structured_text_bidi_override(Control::StructuredTextParser p_parser) {
+void LinkButton::set_structured_text_bidi_override(TextServer::StructuredTextParser p_parser) {
if (st_parser != p_parser) {
st_parser = p_parser;
_shape();
@@ -68,7 +69,7 @@ void LinkButton::set_structured_text_bidi_override(Control::StructuredTextParser
}
}
-Control::StructuredTextParser LinkButton::get_structured_text_bidi_override() const {
+TextServer::StructuredTextParser LinkButton::get_structured_text_bidi_override() const {
return st_parser;
}
@@ -148,18 +149,20 @@ void LinkButton::_notification(int p_what) {
case NOTIFICATION_TRANSLATION_CHANGED: {
xl_text = atr(text);
_shape();
-
update_minimum_size();
update();
} break;
+
case NOTIFICATION_LAYOUT_DIRECTION_CHANGED: {
update();
} break;
+
case NOTIFICATION_THEME_CHANGED: {
_shape();
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..6d2dcbde84 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 {
@@ -54,7 +53,7 @@ private:
Dictionary opentype_features;
String language;
TextDirection text_direction = TEXT_DIRECTION_AUTO;
- Control::StructuredTextParser st_parser = STRUCTURED_TEXT_DEFAULT;
+ TextServer::StructuredTextParser st_parser = TextServer::STRUCTURED_TEXT_DEFAULT;
Array st_args;
void _shape();
@@ -72,8 +71,8 @@ public:
void set_text(const String &p_text);
String get_text() const;
- void set_structured_text_bidi_override(Control::StructuredTextParser p_parser);
- Control::StructuredTextParser get_structured_text_bidi_override() const;
+ void set_structured_text_bidi_override(TextServer::StructuredTextParser p_parser);
+ TextServer::StructuredTextParser get_structured_text_bidi_override() const;
void set_structured_text_bidi_override_options(Array p_args);
Array get_structured_text_bidi_override_options() const;
@@ -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 af239d67ae..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,6 +107,7 @@ void MarginContainer::_notification(int p_what) {
fit_child_in_rect(c, Rect2(margin_left, margin_top, w, h));
}
} break;
+
case NOTIFICATION_THEME_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 32501b65a0..316fee53fe 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 */
@@ -33,10 +33,10 @@
#include "core/os/keyboard.h"
#include "scene/main/window.h"
-void MenuButton::unhandled_key_input(const Ref<InputEvent> &p_event) {
+void MenuButton::shortcut_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();
}
@@ -130,11 +136,16 @@ int MenuButton::get_item_count() const {
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));
@@ -176,7 +187,7 @@ void MenuButton::_get_property_list(List<PropertyInfo> *p_list) const {
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 = 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);
@@ -184,7 +195,7 @@ void MenuButton::_get_property_list(List<PropertyInfo> *p_list) const {
pi.usage &= ~(!popup->is_item_checked(i) ? PROPERTY_USAGE_STORAGE : 0);
p_list->push_back(pi);
- pi = PropertyInfo(Variant::INT, vformat("popup/item_%d/id", i), PROPERTY_HINT_RANGE, "1,10,1,or_greater");
+ pi = PropertyInfo(Variant::INT, vformat("popup/item_%d/id", i), PROPERTY_HINT_RANGE, "0,10,1,or_greater");
p_list->push_back(pi);
pi = PropertyInfo(Variant::BOOL, vformat("popup/item_%d/disabled", i));
@@ -216,11 +227,12 @@ 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);
- set_process_unhandled_key_input(true);
+ set_process_shortcut_input(true);
set_focus_mode(FOCUS_NONE);
set_action_mode(ACTION_MODE_BUTTON_PRESS);
diff --git a/scene/gui/menu_button.h b/scene/gui/menu_button.h
index 455b7dc870..0a6b46c796 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 */
@@ -40,7 +40,7 @@ class MenuButton : public Button {
bool clicked = false;
bool switch_on_hover = false;
bool disable_shortcuts = false;
- PopupMenu *popup;
+ PopupMenu *popup = nullptr;
Vector2i mouse_pos_adjusted;
@@ -54,7 +54,7 @@ protected:
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;
+ virtual void shortcut_input(const Ref<InputEvent> &p_event) override;
public:
virtual void pressed() override;
@@ -67,7 +67,7 @@ public:
void set_item_count(int p_count);
int get_item_count() const;
- MenuButton();
+ MenuButton(const String &p_text = String());
~MenuButton();
};
diff --git a/scene/gui/nine_patch_rect.cpp b/scene/gui/nine_patch_rect.cpp
index ea5c82306d..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,10 +95,6 @@ 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
- */
update_minimum_size();
emit_signal(SceneStringNames::get_singleton()->texture_changed);
}
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 cbbda09261..a86f2bdbc1 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();
@@ -40,7 +42,7 @@ Size2 OptionButton::get_minimum_size() const {
const Size2 arrow_size = Control::get_theme_icon(SNAME("arrow"))->get_size();
Size2 content_size = minsize - padding;
- content_size.width += arrow_size.width + get_theme_constant(SNAME("hseparation"));
+ content_size.width += arrow_size.width + get_theme_constant(SNAME("h_separation"));
content_size.height = MAX(content_size.height, arrow_size.height);
minsize = content_size + padding;
@@ -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,20 +188,33 @@ 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();
}
void OptionButton::add_icon_item(const Ref<Texture2D> &p_icon, const String &p_label, int p_id) {
+ bool first_selectable = !has_selectable_items();
popup->add_icon_radio_check_item(p_icon, p_label, p_id);
- if (popup->get_item_count() == 1) {
- select(0);
+ if (first_selectable) {
+ select(get_item_count() - 1);
}
}
void OptionButton::add_item(const String &p_label, int p_id) {
+ bool first_selectable = !has_selectable_items();
popup->add_radio_check_item(p_label, p_id);
- if (popup->get_item_count() == 1) {
- select(0);
+ if (first_selectable) {
+ select(get_item_count() - 1);
}
}
@@ -163,6 +242,10 @@ void OptionButton::set_item_metadata(int p_idx, const Variant &p_metadata) {
popup->set_item_metadata(p_idx, p_metadata);
}
+void OptionButton::set_item_tooltip(int p_idx, const String &p_tooltip) {
+ popup->set_item_tooltip(p_idx, p_tooltip);
+}
+
void OptionButton::set_item_disabled(int p_idx, bool p_disabled) {
popup->set_item_disabled(p_idx, p_disabled);
}
@@ -176,6 +259,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);
}
@@ -187,41 +274,99 @@ Variant OptionButton::get_item_metadata(int p_idx) const {
return popup->get_item_metadata(p_idx);
}
+String OptionButton::get_item_tooltip(int p_idx) const {
+ return popup->get_item_tooltip(p_idx);
+}
+
bool OptionButton::is_item_disabled(int p_idx) const {
return popup->is_item_disabled(p_idx);
}
+bool OptionButton::is_item_separator(int p_idx) const {
+ return popup->is_item_separator(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();
+}
+
+bool OptionButton::has_selectable_items() const {
+ for (int i = 0; i < get_item_count(); i++) {
+ if (!is_item_disabled(i) && !is_item_separator(i)) {
+ return true;
+ }
+ }
+ return false;
+}
+int OptionButton::get_selectable_item(bool p_from_last) const {
+ if (!p_from_last) {
+ for (int i = 0; i < get_item_count(); i++) {
+ if (!is_item_disabled(i) && !is_item_separator(i)) {
+ return i;
+ }
+ }
+ } else {
+ for (int i = get_item_count() - 1; i >= 0; i--) {
+ if (!is_item_disabled(i) && !is_item_separator(i)) {
+ return i;
+ }
+ }
+ }
+ return -1;
+}
+
int OptionButton::get_item_count() const {
return popup->get_item_count();
}
-void OptionButton::add_separator() {
- popup->add_separator();
+void OptionButton::add_separator(const String &p_text) {
+ popup->add_separator(p_text);
}
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());
- current = p_which;
- set_text(popup->get_item_text(current));
- set_icon(popup->get_item_icon(current));
+ for (int i = 0; i < popup->get_item_count(); i++) {
+ popup->set_item_checked(i, i == p_which);
+ }
+
+ current = p_which;
+ set_text(popup->get_item_text(current));
+ set_icon(popup->get_item_icon(current));
+ }
if (is_inside_tree() && p_emit) {
emit_signal(SNAME("item_selected"), current);
@@ -229,7 +374,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 +389,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,48 +402,25 @@ 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::get_translatable_strings(List<String> *p_strings) const {
+ popup->get_translatable_strings(p_strings);
}
-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::_validate_property(PropertyInfo &property) const {
+ if (property.name == "text" || property.name == "icon") {
+ property.usage = PROPERTY_USAGE_NONE;
}
}
-void OptionButton::get_translatable_strings(List<String> *p_strings) const {
- popup->get_translatable_strings(p_strings);
-}
-
void OptionButton::_bind_methods() {
ClassDB::bind_method(D_METHOD("add_item", "label", "id"), &OptionButton::add_item, DEFVAL(-1));
ClassDB::bind_method(D_METHOD("add_icon_item", "texture", "label", "id"), &OptionButton::add_icon_item, DEFVAL(-1));
@@ -311,14 +429,16 @@ void OptionButton::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_item_disabled", "idx", "disabled"), &OptionButton::set_item_disabled);
ClassDB::bind_method(D_METHOD("set_item_id", "idx", "id"), &OptionButton::set_item_id);
ClassDB::bind_method(D_METHOD("set_item_metadata", "idx", "metadata"), &OptionButton::set_item_metadata);
+ ClassDB::bind_method(D_METHOD("set_item_tooltip", "idx", "tooltip"), &OptionButton::set_item_tooltip);
ClassDB::bind_method(D_METHOD("get_item_text", "idx"), &OptionButton::get_item_text);
ClassDB::bind_method(D_METHOD("get_item_icon", "idx"), &OptionButton::get_item_icon);
ClassDB::bind_method(D_METHOD("get_item_id", "idx"), &OptionButton::get_item_id);
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("get_item_tooltip", "idx"), &OptionButton::get_item_tooltip);
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("is_item_separator", "idx"), &OptionButton::is_item_separator);
+ ClassDB::bind_method(D_METHOD("add_separator", "text"), &OptionButton::add_separator, DEFVAL(String()));
ClassDB::bind_method(D_METHOD("clear"), &OptionButton::clear);
ClassDB::bind_method(D_METHOD("select", "idx"), &OptionButton::select);
ClassDB::bind_method(D_METHOD("get_selected"), &OptionButton::get_selected);
@@ -329,17 +449,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);
+ ClassDB::bind_method(D_METHOD("has_selectable_items"), &OptionButton::has_selectable_items);
+ ClassDB::bind_method(D_METHOD("get_selectable_item", "from_last"), &OptionButton::get_selectable_item, DEFVAL(false));
- ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "items", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR | 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_alignment(HORIZONTAL_ALIGNMENT_LEFT);
if (is_layout_rtl()) {
diff --git a/scene/gui/option_button.h b/scene/gui/option_button.h
index 953337ecce..7896132626 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 */
@@ -37,7 +37,7 @@
class OptionButton : public Button {
GDCLASS(OptionButton, Button);
- PopupMenu *popup;
+ PopupMenu *popup = nullptr;
int current = -1;
void _focused(int p_which);
@@ -45,14 +45,15 @@ 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;
+ virtual void _validate_property(PropertyInfo &property) const override;
static void _bind_methods();
public:
@@ -68,6 +69,7 @@ public:
void set_item_id(int p_idx, int p_id);
void set_item_metadata(int p_idx, const Variant &p_metadata);
void set_item_disabled(int p_idx, bool p_disabled);
+ void set_item_tooltip(int p_idx, const String &p_tooltip);
String get_item_text(int p_idx) const;
Ref<Texture2D> get_item_icon(int p_idx) const;
@@ -75,10 +77,16 @@ public:
int get_item_index(int p_id) const;
Variant get_item_metadata(int p_idx) const;
bool is_item_disabled(int p_idx) const;
+ bool is_item_separator(int p_idx) const;
+ String get_item_tooltip(int p_idx) const;
+
+ bool has_selectable_items() const;
+ int get_selectable_item(bool p_from_last = false) const;
+ void set_item_count(int p_count);
int get_item_count() const;
- void add_separator();
+ void add_separator(const String &p_text = "");
void clear();
@@ -93,7 +101,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 a48ad0f770..6532fc5934 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 */
@@ -42,26 +42,30 @@ void Popup::_input_from_window(const Ref<InputEvent> &p_event) {
}
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();
}
}
@@ -104,27 +108,25 @@ void Popup::_close_pressed() {
_deinitialize_visible_parents();
- call_deferred(SNAME("hide"));
+ // Hide after returning to process events, but only if we don't
+ // get popped up in the interim.
+ call_deferred(SNAME("_popup_conditional_hide"));
}
-void Popup::set_as_minsize() {
- set_size(get_contents_minimum_size());
+void Popup::_post_popup() {
+ Window::_post_popup();
+ popped_up = true;
}
-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::_popup_conditional_hide() {
+ if (!popped_up) {
+ hide();
+ }
}
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"));
+ ClassDB::bind_method(D_METHOD("_popup_conditional_hide"), &Popup::_popup_conditional_hide);
}
Rect2i Popup::_popup_adjust_rect() const {
@@ -184,6 +186,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));
}
@@ -241,13 +244,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..27f46d4a97 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);
@@ -58,12 +57,11 @@ protected:
void _notification(int p_what);
static void _bind_methods();
-public:
- void set_as_minsize();
+ void _popup_conditional_hide();
- void set_close_on_parent_focus(bool p_close);
- bool get_close_on_parent_focus();
+ virtual void _post_popup() override;
+public:
Popup();
~Popup();
};
@@ -71,7 +69,7 @@ public:
class PopupPanel : public Popup {
GDCLASS(PopupPanel, Popup);
- Panel *panel;
+ Panel *panel = nullptr;
protected:
void _update_child_rects();
diff --git a/scene/gui/popup_menu.cpp b/scene/gui/popup_menu.cpp
index a61d77e95c..8303d6db57 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"
@@ -46,11 +47,11 @@ String PopupMenu::_get_accel_text(const Item &p_item) const {
}
Size2 PopupMenu::_get_contents_minimum_size() const {
- int vseparation = get_theme_constant(SNAME("vseparation"));
- int hseparation = get_theme_constant(SNAME("hseparation"));
+ int vseparation = get_theme_constant(SNAME("v_separation"));
+ int hseparation = get_theme_constant(SNAME("h_separation"));
Size2 minsize = get_theme_stylebox(SNAME("panel"))->get_minimum_size(); // Accounts for margin in the margin container
- 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,7 +68,7 @@ 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;
}
@@ -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()));
}
@@ -129,7 +129,7 @@ int PopupMenu::_get_item_height(int p_item) const {
}
int PopupMenu::_get_items_total_height() const {
- int vsep = get_theme_constant(SNAME("vseparation"));
+ int vsep = get_theme_constant(SNAME("v_separation"));
// Get total height of all items by taking max of icon height and font height
int items_total_height = 0;
@@ -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;
@@ -165,7 +148,7 @@ int PopupMenu::_get_mouse_over(const Point2 &p_over) const {
Ref<StyleBox> style = get_theme_stylebox(SNAME("panel")); // Accounts for margin in the margin container
- int vseparation = get_theme_constant(SNAME("vseparation"));
+ int vseparation = get_theme_constant(SNAME("v_separation"));
Point2 ofs = style->get_offset() + Point2(0, vseparation / 2);
@@ -186,32 +169,34 @@ int PopupMenu::_get_mouse_over(const Point2 &p_over) const {
return -1;
}
-void PopupMenu::_activate_submenu(int p_over) {
+void PopupMenu::_activate_submenu(int p_over, bool p_by_keyboard) {
Node *n = get_node(items[p_over].submenu);
ERR_FAIL_COND_MSG(!n, "Item subnode does not exist: " + items[p_over].submenu + ".");
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"));
- int vsep = get_theme_constant(SNAME("vseparation"));
+ int vsep = get_theme_constant(SNAME("v_separation"));
Point2 this_pos = get_position();
Rect2 this_rect(this_pos, get_size());
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;
}
@@ -220,27 +205,41 @@ void PopupMenu::_activate_submenu(int p_over) {
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;
+ }
+
+ submenu_pum->activated_by_keyboard = p_by_keyboard;
+
+ // If not triggered by the mouse, start the popup with its first item selected.
+ if (submenu_pum->get_item_count() > 0 && p_by_keyboard) {
+ submenu_pum->set_current_index(0);
+ }
+
+ 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 +265,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 +279,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 +297,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 +311,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,14 +325,14 @@ 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.is_empty() && submenu_over != mouse_over) {
- _activate_submenu(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, true);
set_input_as_handled();
}
} else if (p_event->is_action("ui_accept") && p_event->is_pressed()) {
if (mouse_over >= 0 && mouse_over < items.size() && !items[mouse_over].separator) {
if (!items[mouse_over].submenu.is_empty() && submenu_over != mouse_over) {
- _activate_submenu(mouse_over);
+ _activate_submenu(mouse_over, true);
} else {
activate_item(mouse_over);
}
@@ -343,11 +342,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;
}
}
@@ -399,6 +398,11 @@ void PopupMenu::gui_input(const Ref<InputEvent> &p_event) {
Ref<InputEventMouseMotion> m = p_event;
if (m.is_valid()) {
+ if (m->get_velocity().is_equal_approx(Vector2())) {
+ return;
+ }
+ activated_by_keyboard = false;
+
for (const Rect2 &E : autohide_areas) {
if (!Rect2(Point2(), get_size()).has_point(m->get_position()) && E.has_point(m->get_position())) {
_close_pressed();
@@ -462,7 +466,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 +490,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;
@@ -500,21 +504,25 @@ void PopupMenu::_draw_items() {
Ref<StyleBox> labeled_separator_left = get_theme_stylebox(SNAME("labeled_separator_left"));
Ref<StyleBox> labeled_separator_right = get_theme_stylebox(SNAME("labeled_separator_right"));
- int vseparation = get_theme_constant(SNAME("vseparation"));
- int hseparation = get_theme_constant(SNAME("hseparation"));
+ int vseparation = get_theme_constant(SNAME("v_separation"));
+ int hseparation = get_theme_constant(SNAME("h_separation"));
Color font_color = get_theme_color(SNAME("font_color"));
Color font_disabled_color = get_theme_color(SNAME("font_disabled_color"));
Color font_accelerator_color = get_theme_color(SNAME("font_accelerator_color"));
Color font_hover_color = get_theme_color(SNAME("font_hover_color"));
Color font_separator_color = get_theme_color(SNAME("font_separator_color"));
- float scroll_width = scroll_container->get_v_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) {
@@ -556,31 +564,39 @@ void PopupMenu::_draw_items() {
// Separator
item_ofs.x += items[i].h_ofs;
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.is_empty()) {
- 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 + hseparation * 2;
+ if (!items[i].icon.is_null()) {
+ content_size += icon_size.width + hseparation;
+ }
+
+ int content_center = display_width / 2;
+ int content_left = content_center - content_size / 2;
+ int content_right = content_center + content_size / 2;
+ if (content_left > item_ofs.x) {
+ int sep_h = labeled_separator_left->get_center_size().height + labeled_separator_left->get_minimum_size().height;
+ int sep_ofs = Math::floor((h - sep_h) / 2.0);
+ labeled_separator_left->draw(ci, Rect2(item_ofs + Point2(0, sep_ofs), Size2(MAX(0, content_left - item_ofs.x), sep_h)));
}
- 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)));
+ if (content_right < display_width) {
+ int sep_h = labeled_separator_right->get_center_size().height + labeled_separator_right->get_minimum_size().height;
+ int sep_ofs = Math::floor((h - sep_h) / 2.0);
+ labeled_separator_right->draw(ci, Rect2(Point2(content_right, item_ofs.y + sep_ofs), Size2(MAX(0, display_width - content_right), sep_h)));
}
} else {
+ int sep_h = separator->get_center_size().height + separator->get_minimum_size().height;
+ int sep_ofs = Math::floor((h - sep_h) / 2.0);
separator->draw(ci, Rect2(item_ofs + Point2(0, sep_ofs), Size2(display_width, sep_h)));
}
}
- 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,16 +605,28 @@ 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
+ // 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);
@@ -607,20 +635,28 @@ void PopupMenu::_draw_items() {
}
}
- // Text
Color font_outline_color = get_theme_color(SNAME("font_outline_color"));
int outline_size = get_theme_constant(SNAME("outline_size"));
+
+ // Text
if (items[i].separator) {
+ Color font_separator_outline_color = get_theme_color(SNAME("font_separator_outline_color"));
+ int separator_outline_size = get_theme_constant(SNAME("separator_outline_size"));
+
if (!text.is_empty()) {
- 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 (outline_size > 0 && font_outline_color.a > 0) {
- items[i].text_buf->draw_outline(ci, text_pos, outline_size, font_outline_color);
+ 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 (separator_outline_size > 0 && font_separator_outline_color.a > 0) {
+ items[i].text_buf->draw_outline(ci, text_pos, separator_outline_size, font_separator_outline_color);
}
items[i].text_buf->draw(ci, text_pos, font_separator_color);
}
} else {
item_ofs.x += icon_ofs + check_ofs;
+
if (rtl) {
Vector2 text_pos = Size2(control->get_size().width - items[i].text_buf->get_size().width - item_ofs.x, item_ofs.y) + Point2(0, Math::floor((h - items[i].text_buf->get_size().y) / 2.0));
if (outline_size > 0 && font_outline_color.a > 0) {
@@ -650,7 +686,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;
@@ -667,7 +703,7 @@ void PopupMenu::_draw_background() {
void PopupMenu::_minimum_lifetime_timeout() {
close_allowed = true;
// If the mouse still isn't in this popup after timer expires, close.
- if (!get_visible_rect().has_point(get_mouse_position())) {
+ if (!activated_by_keyboard && !get_visible_rect().has_point(get_mouse_position())) {
_close_pressed();
}
}
@@ -693,8 +729,8 @@ void PopupMenu::_shape_item(int p_item) {
if (items.write[p_item].dirty) {
items.write[p_item].text_buf->clear();
- Ref<Font> font = get_theme_font(SNAME("font"));
- int font_size = get_theme_font_size(SNAME("font_size"));
+ Ref<Font> font = get_theme_font(items[p_item].separator ? SNAME("font_separator") : SNAME("font"));
+ int font_size = get_theme_font_size(items[p_item].separator ? SNAME("font_separator_size") : SNAME("font_size"));
if (items[p_item].text_direction == Control::TEXT_DIRECTION_INHERITED) {
items.write[p_item].text_buf->set_direction(is_layout_rtl() ? TextServer::DIRECTION_RTL : TextServer::DIRECTION_LTR);
@@ -715,11 +751,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,24 +769,26 @@ 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.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
- if (!is_embedded() && autohide_areas.size()) {
+ // Only used when using operating system windows.
+ if (!activated_by_keyboard && !is_embedded() && autohide_areas.size()) {
Point2 mouse_pos = DisplayServer::get_singleton()->mouse_get_position();
mouse_pos -= get_position();
@@ -761,6 +800,7 @@ void PopupMenu::_notification(int p_what) {
}
}
} break;
+
case NOTIFICATION_VISIBILITY_CHANGED: {
if (!is_visible()) {
if (mouse_over >= 0) {
@@ -794,10 +834,10 @@ void PopupMenu::_notification(int p_what) {
// Set margin on the margin container
Ref<StyleBox> panel_style = get_theme_stylebox(SNAME("panel"));
- margin_container->add_theme_constant_override("margin_top", panel_style->get_margin(Side::SIDE_TOP));
- margin_container->add_theme_constant_override("margin_bottom", panel_style->get_margin(Side::SIDE_BOTTOM));
margin_container->add_theme_constant_override("margin_left", panel_style->get_margin(Side::SIDE_LEFT));
+ margin_container->add_theme_constant_override("margin_top", panel_style->get_margin(Side::SIDE_TOP));
margin_container->add_theme_constant_override("margin_right", panel_style->get_margin(Side::SIDE_RIGHT));
+ margin_container->add_theme_constant_override("margin_bottom", panel_style->get_margin(Side::SIDE_BOTTOM));
}
} break;
}
@@ -975,9 +1015,16 @@ 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());
+ if (items[p_idx].text == p_text) {
+ return;
+ }
items.write[p_idx].text = p_text;
items.write[p_idx].xl_text = atr(p_text);
+ items.write[p_idx].dirty = true;
_shape_item(p_idx);
control->update();
@@ -985,6 +1032,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) {
@@ -995,6 +1045,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;
@@ -1002,6 +1055,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) {
@@ -1012,6 +1068,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;
@@ -1021,6 +1080,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;
@@ -1029,6 +1091,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;
@@ -1038,6 +1103,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;
@@ -1046,6 +1114,9 @@ void PopupMenu::set_item_id(int p_idx, int p_id) {
}
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;
@@ -1055,6 +1126,9 @@ void PopupMenu::set_item_accelerator(int p_idx, Key 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();
@@ -1062,6 +1136,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();
@@ -1069,6 +1146,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();
@@ -1177,6 +1257,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();
@@ -1188,24 +1271,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);
@@ -1222,6 +1317,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();
@@ -1229,12 +1327,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();
@@ -1269,13 +1373,28 @@ 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();
@@ -1285,6 +1404,20 @@ 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;
Ref<InputEventKey> k = p_event;
@@ -1388,12 +1521,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) {
@@ -1558,7 +1691,7 @@ bool PopupMenu::_set(const StringName &p_name, const Variant &p_value) {
} else if (property == "id") {
set_item_id(item_index, p_value);
return true;
- } else if (components[1] == "disabled") {
+ } else if (property == "disabled") {
set_item_disabled(item_index, p_value);
return true;
} else if (property == "separator") {
@@ -1631,7 +1764,7 @@ bool PopupMenu::_get(const StringName &p_name, Variant &r_ret) const {
} else if (property == "id") {
r_ret = get_item_id(item_index);
return true;
- } else if (components[1] == "disabled") {
+ } else if (property == "disabled") {
r_ret = is_item_disabled(item_index);
return true;
} else if (property == "separator") {
@@ -1658,7 +1791,7 @@ void PopupMenu::_get_property_list(List<PropertyInfo> *p_list) const {
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, "1,10,1,or_greater");
+ 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));
@@ -1732,10 +1865,13 @@ void PopupMenu::_bind_methods() {
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("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));
diff --git a/scene/gui/popup_menu.h b/scene/gui/popup_menu.h
index 22912fb59c..12587b7e73 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 */
@@ -87,9 +87,10 @@ class PopupMenu : public Popup {
};
bool close_allowed = false;
+ bool activated_by_keyboard = false;
Timer *minimum_lifetime_timer = nullptr;
- Timer *submenu_timer;
+ Timer *submenu_timer = nullptr;
List<Rect2> autohide_areas;
Vector<Item> items;
MouseButton initial_button_mask = MouseButton::NONE;
@@ -103,12 +104,11 @@ 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);
virtual void gui_input(const Ref<InputEvent> &p_event);
- void _activate_submenu(int p_over);
+ void _activate_submenu(int p_over, bool p_by_keyboard = false);
void _submenu_timeout();
uint64_t popup_time_msec = 0;
@@ -117,7 +117,7 @@ class PopupMenu : public Popup {
bool hide_on_multistate_item_selection = false;
Vector2 moved;
- Map<Ref<Shortcut>, int> shortcut_refcount;
+ HashMap<Ref<Shortcut>, int> shortcut_refcount;
void _ref_shortcut(Ref<Shortcut> p_sc);
void _unref_shortcut(Ref<Shortcut> p_sc);
@@ -126,9 +126,9 @@ class PopupMenu : public Popup {
uint64_t search_time_msec = 0;
String search_string = "";
- MarginContainer *margin_container;
- ScrollContainer *scroll_container;
- Control *control;
+ MarginContainer *margin_container = nullptr;
+ ScrollContainer *scroll_container = nullptr;
+ Control *control = nullptr;
void _draw_items();
void _draw_background();
@@ -212,11 +212,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..f36682942f 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,39 +53,78 @@ 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"));
-
- 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)));
+ 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();
+
+ switch (mode) {
+ case FILL_BEGIN_TO_END:
+ case FILL_END_TO_BEGIN: {
+ int mp = fg->get_minimum_size().width;
+ int p = round(r * (get_size().width - mp));
+ // We want FILL_BEGIN_TO_END to map to right to left when UI layout is RTL,
+ // and left to right otherwise. And likewise for FILL_END_TO_BEGIN.
+ bool right_to_left = is_layout_rtl() ? (mode == FILL_BEGIN_TO_END) : (mode == FILL_END_TO_BEGIN);
+ if (p > 0) {
+ if (right_to_left) {
+ int p_remaining = round((1.0 - r) * (get_size().width - mp));
+ draw_style_box(fg, Rect2(Point2(p_remaining, 0), Size2(p + fg->get_minimum_size().width, get_size().height)));
+ } else {
+ draw_style_box(fg, Rect2(Point2(0, 0), Size2(p + fg->get_minimum_size().width, get_size().height)));
+ }
+ }
+ } break;
+ case FILL_TOP_TO_BOTTOM:
+ case FILL_BOTTOM_TO_TOP: {
+ int mp = fg->get_minimum_size().height;
+ int p = round(r * (get_size().height - mp));
+
+ if (p > 0) {
+ if (mode == FILL_TOP_TO_BOTTOM) {
+ draw_style_box(fg, Rect2(Point2(0, 0), Size2(get_size().width, p + fg->get_minimum_size().height)));
+ } else {
+ int p_remaining = round((1.0 - r) * (get_size().height - mp));
+ draw_style_box(fg, Rect2(Point2(0, p_remaining), Size2(get_size().width, p + fg->get_minimum_size().height)));
+ }
+ }
+ } break;
+ case FILL_MODE_MAX:
+ break;
}
- }
-
- 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;
}
}
+void ProgressBar::set_fill_mode(int p_fill) {
+ ERR_FAIL_INDEX(p_fill, FILL_MODE_MAX);
+ mode = (FillMode)p_fill;
+ update();
+}
+
+int ProgressBar::get_fill_mode() {
+ return mode;
+}
+
void ProgressBar::set_percent_visible(bool p_visible) {
percent_visible = p_visible;
update();
@@ -95,10 +135,18 @@ bool ProgressBar::is_percent_visible() const {
}
void ProgressBar::_bind_methods() {
+ ClassDB::bind_method(D_METHOD("set_fill_mode", "mode"), &ProgressBar::set_fill_mode);
+ ClassDB::bind_method(D_METHOD("get_fill_mode"), &ProgressBar::get_fill_mode);
ClassDB::bind_method(D_METHOD("set_percent_visible", "visible"), &ProgressBar::set_percent_visible);
ClassDB::bind_method(D_METHOD("is_percent_visible"), &ProgressBar::is_percent_visible);
- ADD_GROUP("Percent", "percent_");
+
+ ADD_PROPERTY(PropertyInfo(Variant::INT, "fill_mode", PROPERTY_HINT_ENUM, "Begin to End,End to Begin,Top to Bottom,Bottom to Top"), "set_fill_mode", "get_fill_mode");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "percent_visible"), "set_percent_visible", "is_percent_visible");
+
+ BIND_ENUM_CONSTANT(FILL_BEGIN_TO_END);
+ BIND_ENUM_CONSTANT(FILL_END_TO_BEGIN);
+ BIND_ENUM_CONSTANT(FILL_TOP_TO_BOTTOM);
+ BIND_ENUM_CONSTANT(FILL_BOTTOM_TO_TOP);
}
ProgressBar::ProgressBar() {
diff --git a/scene/gui/progress_bar.h b/scene/gui/progress_bar.h
index fb6060d932..5ba21ad7d5 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 */
@@ -43,11 +43,27 @@ protected:
static void _bind_methods();
public:
+ enum FillMode {
+ FILL_BEGIN_TO_END,
+ FILL_END_TO_BEGIN,
+ FILL_TOP_TO_BOTTOM,
+ FILL_BOTTOM_TO_TOP,
+ FILL_MODE_MAX
+ };
+
+ void set_fill_mode(int p_fill);
+ int get_fill_mode();
+
void set_percent_visible(bool p_visible);
bool is_percent_visible() const;
Size2 get_minimum_size() const override;
ProgressBar();
+
+private:
+ FillMode mode = FILL_BEGIN_TO_END;
};
+VARIANT_ENUM_CAST(ProgressBar::FillMode);
+
#endif // PROGRESS_BAR_H
diff --git a/scene/gui/range.cpp b/scene/gui/range.cpp
index c4f05a7975..fae6688452 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 */
@@ -34,12 +34,15 @@ TypedArray<String> Range::get_configuration_warnings() const {
TypedArray<String> warnings = Node::get_configuration_warnings();
if (shared->exp_ratio && shared->min <= 0) {
- warnings.push_back(TTR("If \"Exp Edit\" is enabled, \"Min Value\" must be greater than 0."));
+ warnings.push_back(RTR("If \"Exp Edit\" is enabled, \"Min Value\" must be greater than 0."));
}
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);
@@ -47,8 +50,8 @@ void Range::_value_changed_notify() {
}
void Range::Shared::emit_value_changed() {
- for (Set<Range *>::Element *E = owners.front(); E; E = E->next()) {
- Range *r = E->get();
+ for (Range *E : owners) {
+ Range *r = E;
if (!r->is_inside_tree()) {
continue;
}
@@ -67,8 +70,8 @@ void Range::_validate_values() {
}
void Range::Shared::emit_changed(const char *p_what) {
- for (Set<Range *>::Element *E = owners.front(); E; E = E->next()) {
- Range *r = E->get();
+ for (Range *E : owners) {
+ Range *r = E;
if (!r->is_inside_tree()) {
continue;
}
@@ -279,6 +282,8 @@ void Range::_bind_methods() {
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, "new_value");
+
ADD_LINKED_PROPERTY("min_value", "value");
ADD_LINKED_PROPERTY("min_value", "max_value");
ADD_LINKED_PROPERTY("min_value", "page");
diff --git a/scene/gui/range.h b/scene/gui/range.h
index 0dc702b19c..1274821bd1 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 */
@@ -45,12 +45,12 @@ class Range : public Control {
bool exp_ratio = false;
bool allow_greater = false;
bool allow_lesser = false;
- Set<Range *> owners;
+ HashSet<Range *> owners;
void emit_value_changed();
void emit_changed(const char *p_what = "");
};
- Shared *shared;
+ Shared *shared = nullptr;
void _ref_shared(Shared *p_shared);
void _unref_shared();
@@ -62,12 +62,14 @@ class Range : public Control {
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 7ed8056826..cf7bf930a5 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 */
@@ -30,9 +30,11 @@
#include "rich_text_label.h"
+#include "core/input/input_map.h"
#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"
@@ -141,7 +143,7 @@ RichTextLabel::Item *RichTextLabel::_get_item_at_pos(RichTextLabel::Item *p_item
for (Item *it = p_item_from; it && it != p_item_to; it = _get_next_item(it)) {
switch (it->type) {
case ITEM_TEXT: {
- ItemText *t = (ItemText *)it;
+ ItemText *t = static_cast<ItemText *>(it);
offset += t->text.length();
if (offset > p_position) {
return it;
@@ -165,16 +167,16 @@ String RichTextLabel::_roman(int p_num, bool p_capitalize) const {
};
String s;
if (p_capitalize) {
- String M[] = { "", "M", "MM", "MMM" };
- String C[] = { "", "C", "CC", "CCC", "CD", "D", "DC", "DCC", "DCCC", "CM" };
- String X[] = { "", "X", "XX", "XXX", "XL", "L", "LX", "LXX", "LXXX", "XC" };
- String I[] = { "", "I", "II", "III", "IV", "V", "VI", "VII", "VIII", "IX" };
+ const String M[] = { "", "M", "MM", "MMM" };
+ const String C[] = { "", "C", "CC", "CCC", "CD", "D", "DC", "DCC", "DCCC", "CM" };
+ const String X[] = { "", "X", "XX", "XXX", "XL", "L", "LX", "LXX", "LXXX", "XC" };
+ const String I[] = { "", "I", "II", "III", "IV", "V", "VI", "VII", "VIII", "IX" };
s = M[p_num / 1000] + C[(p_num % 1000) / 100] + X[(p_num % 100) / 10] + I[p_num % 10];
} else {
- String M[] = { "", "m", "mm", "mmm" };
- String C[] = { "", "c", "cc", "ccc", "cd", "d", "dc", "dcc", "dccc", "cm" };
- String X[] = { "", "x", "xx", "xxx", "xl", "l", "lx", "lxx", "lxxx", "xc" };
- String I[] = { "", "i", "ii", "iii", "iv", "v", "vi", "vii", "viii", "ix" };
+ const String M[] = { "", "m", "mm", "mmm" };
+ const String C[] = { "", "c", "cc", "ccc", "cd", "d", "dc", "dcc", "dccc", "cm" };
+ const String X[] = { "", "x", "xx", "xxx", "xl", "l", "lx", "lxx", "lxxx", "xc" };
+ const String I[] = { "", "i", "ii", "iii", "iv", "v", "vi", "vii", "viii", "ix" };
s = M[p_num / 1000] + C[(p_num % 1000) / 100] + X[(p_num % 100) / 10] + I[p_num % 10];
}
return s;
@@ -205,11 +207,56 @@ String RichTextLabel::_letters(int p_num, bool p_capitalize) const {
return s;
}
-void RichTextLabel::_resize_line(ItemFrame *p_frame, int p_line, const Ref<Font> &p_base_font, int p_base_font_size, int p_width) {
+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());
+ ERR_FAIL_COND(p_line < 0 || p_line >= (int)p_frame->lines.size());
- Line &l = p_frame->lines.write[p_line];
+ Line &l = p_frame->lines[p_line];
+ MutexLock lock(l.text_buf->get_mutex());
+
+ RID t = l.text_buf->get_rid();
+ int spans = TS->shaped_get_span_count(t);
+ for (int i = 0; i < spans; i++) {
+ ItemText *it = reinterpret_cast<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 < (int)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 < (int)frame->lines.size(); i++) {
+ _update_line_font(frame, i, p_base_font, p_base_font_size);
+ }
+ }
+ } break;
+ default:
+ break;
+ }
+ }
+}
+
+float RichTextLabel::_resize_line(ItemFrame *p_frame, int p_line, const Ref<Font> &p_base_font, int p_base_font_size, int p_width, float p_h) {
+ ERR_FAIL_COND_V(p_frame == nullptr, p_h);
+ ERR_FAIL_COND_V(p_line < 0 || p_line >= (int)p_frame->lines.size(), p_h);
+
+ Line &l = p_frame->lines[p_line];
+ MutexLock lock(l.text_buf->get_mutex());
l.offset.x = _find_margin(l.from, p_base_font, p_base_font_size);
l.text_buf->set_width(p_width - l.offset.x);
@@ -220,25 +267,28 @@ void RichTextLabel::_resize_line(ItemFrame *p_frame, int p_line, const Ref<Font>
l.text_buf->tab_align(tabs);
}
- Item *it_to = (p_line + 1 < p_frame->lines.size()) ? p_frame->lines[p_line + 1].from : nullptr;
+ Item *it_to = (p_line + 1 < (int)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);
- int hseparation = get_theme_constant(SNAME("table_hseparation"));
- int vseparation = get_theme_constant(SNAME("table_vseparation"));
+ int hseparation = get_theme_constant(SNAME("table_h_separation"));
+ int vseparation = get_theme_constant(SNAME("table_v_separation"));
int col_count = table->columns.size();
for (int i = 0; i < col_count; i++) {
- table->columns.write[i].width = 0;
+ table->columns[i].width = 0;
}
int idx = 0;
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++) {
- _resize_line(frame, i, p_base_font, p_base_font_size, 1);
+ float prev_h = 0;
+ for (int i = 0; i < (int)frame->lines.size(); i++) {
+ MutexLock sub_lock(frame->lines[i].text_buf->get_mutex());
+ int w = _find_margin(frame->lines[i].from, p_base_font, p_base_font_size) + 1;
+ prev_h = _resize_line(frame, i, p_base_font, p_base_font_size, w, prev_h);
}
idx++;
}
@@ -254,7 +304,7 @@ void RichTextLabel::_resize_line(ItemFrame *p_frame, int p_line, const Ref<Font>
for (int i = 0; i < col_count; i++) {
remaining_width -= table->columns[i].min_width;
if (table->columns[i].max_width > table->columns[i].min_width) {
- table->columns.write[i].expand = true;
+ table->columns[i].expand = true;
}
if (table->columns[i].expand) {
total_ratio += table->columns[i].expand_ratio;
@@ -263,9 +313,9 @@ 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) {
- table->columns.write[i].width += table->columns[i].expand_ratio * remaining_width / total_ratio;
+ table->columns[i].width = table->columns[i].min_width;
+ if (table->columns[i].expand && total_ratio > 0 && remaining_width > 0) {
+ table->columns[i].width += table->columns[i].expand_ratio * remaining_width / total_ratio;
}
table->total_width += table->columns[i].width + hseparation;
}
@@ -282,7 +332,7 @@ void RichTextLabel::_resize_line(ItemFrame *p_frame, int p_line, const Ref<Font>
int dif = table->columns[i].width - table->columns[i].max_width;
if (dif > 0) {
table_need_fit = true;
- table->columns.write[i].width = table->columns[i].max_width;
+ table->columns[i].width = table->columns[i].max_width;
table->total_width -= dif;
total_ratio -= table->columns[i].expand_ratio;
}
@@ -296,7 +346,7 @@ void RichTextLabel::_resize_line(ItemFrame *p_frame, int p_line, const Ref<Font>
if (dif > 0) {
int slice = table->columns[i].expand_ratio * remaining_width / total_ratio;
int incr = MIN(dif, slice);
- table->columns.write[i].width += incr;
+ table->columns[i].width += incr;
table->total_width += incr;
}
}
@@ -320,18 +370,19 @@ void RichTextLabel::_resize_line(ItemFrame *p_frame, int p_line, const Ref<Font>
offset.x += frame->padding.position.x;
float yofs = frame->padding.position.y;
- for (int i = 0; i < frame->lines.size(); i++) {
- frame->lines.write[i].text_buf->set_width(table->columns[column].width);
- table->columns.write[column].width = MAX(table->columns.write[column].width, ceil(frame->lines[i].text_buf->get_size().x));
+ float prev_h = 0;
+ for (int i = 0; i < (int)frame->lines.size(); i++) {
+ MutexLock sub_lock(frame->lines[i].text_buf->get_mutex());
+ frame->lines[i].text_buf->set_width(table->columns[column].width);
+ table->columns[column].width = MAX(table->columns[column].width, ceil(frame->lines[i].text_buf->get_size().x));
+
+ frame->lines[i].offset.y = prev_h;
+ frame->lines[i].offset += offset;
+ float h = frame->lines[i].text_buf->get_size().y + (frame->lines[i].text_buf->get_line_count() - 1) * get_theme_constant(SNAME("line_separation"));
if (i > 0) {
- frame->lines.write[i].offset.y = frame->lines[i - 1].offset.y + frame->lines[i - 1].text_buf->get_size().y;
- } else {
- frame->lines.write[i].offset.y = 0;
+ h += get_theme_constant(SNAME("line_separation"));
}
- frame->lines.write[i].offset += offset;
-
- float h = frame->lines[i].text_buf->get_size().y;
if (frame->min_size_over.y > 0) {
h = MAX(h, frame->min_size_over.y);
}
@@ -339,6 +390,7 @@ void RichTextLabel::_resize_line(ItemFrame *p_frame, int p_line, const Ref<Font>
h = MIN(h, frame->max_size_over.y);
}
yofs += h;
+ prev_h = frame->lines[i].offset.y + frame->lines[i].text_buf->get_size().y + frame->lines[i].text_buf->get_line_count() * get_theme_constant(SNAME("line_separation"));
}
yofs += frame->padding.size.y;
offset.x += table->columns[column].width + hseparation + frame->padding.size.x;
@@ -361,22 +413,35 @@ 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"));
- } else {
- l.offset.y = 0;
- }
+ l.offset.y = p_h;
+ return _calculate_line_vertical_offset(l);
}
-void RichTextLabel::_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) {
- ERR_FAIL_COND(p_frame == nullptr);
- ERR_FAIL_COND(p_line < 0 || p_line >= p_frame->lines.size());
+float RichTextLabel::_shape_line(ItemFrame *p_frame, int p_line, const Ref<Font> &p_base_font, int p_base_font_size, int p_width, float p_h, int *r_char_offset) {
+ ERR_FAIL_COND_V(p_frame == nullptr, p_h);
+ ERR_FAIL_COND_V(p_line < 0 || p_line >= (int)p_frame->lines.size(), p_h);
- Line &l = p_frame->lines.write[p_line];
+ Line &l = p_frame->lines[p_line];
+ MutexLock lock(l.text_buf->get_mutex());
+
+ 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;
@@ -394,24 +459,20 @@ void RichTextLabel::_shape_line(ItemFrame *p_frame, int p_line, const Ref<Font>
// Shape current paragraph.
String text;
- Item *it_to = (p_line + 1 < p_frame->lines.size()) ? p_frame->lines[p_line + 1].from : nullptr;
+ Item *it_to = (p_line + 1 < (int)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) {
case ITEM_DROPCAP: {
// Add dropcap.
- const ItemDropcap *dc = (ItemDropcap *)it;
- if (dc != nullptr) {
- l.text_buf->set_dropcap(dc->text, dc->font, dc->font_size, dc->dropcap_margins);
- l.dc_color = dc->color;
- l.dc_ol_size = dc->ol_size;
- l.dc_ol_color = dc->ol_color;
- } else {
- l.text_buf->clear_dropcap();
- }
+ const ItemDropcap *dc = static_cast<ItemDropcap *>(it);
+ l.text_buf->set_dropcap(dc->text, dc->font, dc->font_size, dc->dropcap_margins);
+ l.dc_color = dc->color;
+ l.dc_ol_size = dc->ol_size;
+ l.dc_ol_color = dc->ol_color;
} break;
case ITEM_NEWLINE: {
Ref<Font> font = _find_font(it);
@@ -428,7 +489,7 @@ void RichTextLabel::_shape_line(ItemFrame *p_frame, int p_line, const Ref<Font>
remaining_characters--;
} break;
case ITEM_TEXT: {
- ItemText *t = (ItemText *)it;
+ ItemText *t = static_cast<ItemText *>(it);
Ref<Font> font = _find_font(it);
if (font.is_null()) {
font = p_base_font;
@@ -440,33 +501,33 @@ 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);
+ ItemImage *img = static_cast<ItemImage *>(it);
+ l.text_buf->add_object((uint64_t)it, img->size, img->inline_align, 1);
text += String::chr(0xfffc);
l.char_count++;
remaining_characters--;
} break;
case ITEM_TABLE: {
ItemTable *table = static_cast<ItemTable *>(it);
- int hseparation = get_theme_constant(SNAME("table_hseparation"));
- int vseparation = get_theme_constant(SNAME("table_vseparation"));
+ int hseparation = get_theme_constant(SNAME("table_h_separation"));
+ int vseparation = get_theme_constant(SNAME("table_v_separation"));
int col_count = table->columns.size();
int t_char_count = 0;
// Set minimums to zero.
for (int i = 0; i < col_count; i++) {
- table->columns.write[i].min_width = 0;
- table->columns.write[i].max_width = 0;
- table->columns.write[i].width = 0;
+ table->columns[i].min_width = 0;
+ table->columns[i].max_width = 0;
+ table->columns[i].width = 0;
}
// Compute minimum width for each cell.
const int available_width = p_width - hseparation * (col_count - 1);
@@ -477,16 +538,20 @@ void RichTextLabel::_shape_line(ItemFrame *p_frame, int p_line, const Ref<Font>
ItemFrame *frame = static_cast<ItemFrame *>(E);
int column = idx % col_count;
- for (int i = 0; i < frame->lines.size(); i++) {
+ float prev_h = 0;
+ for (int i = 0; i < (int)frame->lines.size(); i++) {
+ MutexLock sub_lock(frame->lines[i].text_buf->get_mutex());
+
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;
+ prev_h = _shape_line(frame, i, p_base_font, p_base_font_size, w, prev_h, &char_offset);
int cell_ch = (char_offset - (l.char_offset + l.char_count));
l.char_count += cell_ch;
t_char_count += cell_ch;
remaining_characters -= cell_ch;
- table->columns.write[column].min_width = MAX(table->columns[column].min_width, ceil(frame->lines[i].text_buf->get_size().x));
- table->columns.write[column].max_width = MAX(table->columns[column].max_width, ceil(frame->lines[i].text_buf->get_non_wrapped_size().x));
+ table->columns[column].min_width = MAX(table->columns[column].min_width, ceil(frame->lines[i].text_buf->get_size().x));
+ table->columns[column].max_width = MAX(table->columns[column].max_width, ceil(frame->lines[i].text_buf->get_non_wrapped_size().x));
}
idx++;
}
@@ -499,7 +564,7 @@ void RichTextLabel::_shape_line(ItemFrame *p_frame, int p_line, const Ref<Font>
for (int i = 0; i < col_count; i++) {
remaining_width -= table->columns[i].min_width;
if (table->columns[i].max_width > table->columns[i].min_width) {
- table->columns.write[i].expand = true;
+ table->columns[i].expand = true;
}
if (table->columns[i].expand) {
total_ratio += table->columns[i].expand_ratio;
@@ -508,9 +573,9 @@ 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) {
- table->columns.write[i].width += table->columns[i].expand_ratio * remaining_width / total_ratio;
+ table->columns[i].width = table->columns[i].min_width;
+ if (table->columns[i].expand && total_ratio > 0 && remaining_width > 0) {
+ table->columns[i].width += table->columns[i].expand_ratio * remaining_width / total_ratio;
}
table->total_width += table->columns[i].width + hseparation;
}
@@ -527,7 +592,7 @@ void RichTextLabel::_shape_line(ItemFrame *p_frame, int p_line, const Ref<Font>
int dif = table->columns[i].width - table->columns[i].max_width;
if (dif > 0) {
table_need_fit = true;
- table->columns.write[i].width = table->columns[i].max_width;
+ table->columns[i].width = table->columns[i].max_width;
table->total_width -= dif;
total_ratio -= table->columns[i].expand_ratio;
}
@@ -541,7 +606,7 @@ void RichTextLabel::_shape_line(ItemFrame *p_frame, int p_line, const Ref<Font>
if (dif > 0) {
int slice = table->columns[i].expand_ratio * remaining_width / total_ratio;
int incr = MIN(dif, slice);
- table->columns.write[i].width += incr;
+ table->columns[i].width += incr;
table->total_width += incr;
}
}
@@ -565,18 +630,20 @@ void RichTextLabel::_shape_line(ItemFrame *p_frame, int p_line, const Ref<Font>
offset.x += frame->padding.position.x;
float yofs = frame->padding.position.y;
- for (int i = 0; i < frame->lines.size(); i++) {
- frame->lines.write[i].text_buf->set_width(table->columns[column].width);
- table->columns.write[column].width = MAX(table->columns.write[column].width, ceil(frame->lines[i].text_buf->get_size().x));
+ float prev_h = 0;
+ for (int i = 0; i < (int)frame->lines.size(); i++) {
+ MutexLock sub_lock(frame->lines[i].text_buf->get_mutex());
+
+ frame->lines[i].text_buf->set_width(table->columns[column].width);
+ table->columns[column].width = MAX(table->columns[column].width, ceil(frame->lines[i].text_buf->get_size().x));
+ frame->lines[i].offset.y = prev_h;
+ frame->lines[i].offset += offset;
+
+ float h = frame->lines[i].text_buf->get_size().y + (frame->lines[i].text_buf->get_line_count() - 1) * get_theme_constant(SNAME("line_separation"));
if (i > 0) {
- frame->lines.write[i].offset.y = frame->lines[i - 1].offset.y + frame->lines[i - 1].text_buf->get_size().y;
- } else {
- frame->lines.write[i].offset.y = 0;
+ h += get_theme_constant(SNAME("line_separation"));
}
- frame->lines.write[i].offset += offset;
-
- float h = frame->lines[i].text_buf->get_size().y;
if (frame->min_size_over.y > 0) {
h = MAX(h, frame->min_size_over.y);
}
@@ -584,6 +651,7 @@ void RichTextLabel::_shape_line(ItemFrame *p_frame, int p_line, const Ref<Font>
h = MIN(h, frame->max_size_over.y);
}
yofs += h;
+ prev_h = frame->lines[i].offset.y + frame->lines[i].text_buf->get_size().y + frame->lines[i].text_buf->get_line_count() * get_theme_constant(SNAME("line_separation"));
}
yofs += frame->padding.size.y;
offset.x += table->columns[column].width + hseparation + frame->padding.size.x;
@@ -614,24 +682,22 @@ 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"));
- } else {
- l.offset.y = 0;
- }
+ l.offset.y = p_h;
+ return _calculate_line_vertical_offset(l);
}
-int RichTextLabel::_draw_line(ItemFrame *p_frame, int p_line, const Vector2 &p_ofs, int p_width, const Color &p_base_color, int p_outline_size, const Color &p_outline_color, const Color &p_font_shadow_color, int p_shadow_outline_size, const Point2 &p_shadow_ofs) {
- 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);
+ ERR_FAIL_COND_V(p_line < 0 || p_line >= (int)p_frame->lines.size(), 0);
- Line &l = p_frame->lines.write[p_line];
+ Vector2 off;
+ int line_spacing = get_theme_constant(SNAME("line_separation"));
+
+ Line &l = p_frame->lines[p_line];
+ MutexLock lock(l.text_buf->get_mutex());
Item *it_from = l.from;
- Item *it_to = (p_line + 1 < p_frame->lines.size()) ? p_frame->lines[p_line + 1].from : nullptr;
- Variant meta;
+ Item *it_to = (p_line + 1 < (int)p_frame->lines.size()) ? p_frame->lines[p_line + 1].from : nullptr;
if (it_from == nullptr) {
return 0;
@@ -641,6 +707,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);
@@ -706,6 +778,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;
@@ -765,7 +841,7 @@ int RichTextLabel::_draw_line(ItemFrame *p_frame, int p_line, const Vector2 &p_o
// Draw inlined objects.
Array objects = TS->shaped_text_get_objects(rid);
for (int i = 0; i < objects.size(); i++) {
- Item *it = (Item *)(uint64_t)objects[i];
+ Item *it = reinterpret_cast<Item *>((uint64_t)objects[i]);
if (it != nullptr) {
Rect2 rect = TS->shaped_text_get_object_rect(rid, objects[i]);
//draw_rect(rect, Color(1,0,0), false, 2); //DEBUG_RECTS
@@ -779,7 +855,7 @@ int RichTextLabel::_draw_line(ItemFrame *p_frame, int p_line, const Vector2 &p_o
Color odd_row_bg = get_theme_color(SNAME("table_odd_row_bg"));
Color even_row_bg = get_theme_color(SNAME("table_even_row_bg"));
Color border = get_theme_color(SNAME("table_border"));
- int hseparation = get_theme_constant(SNAME("table_hseparation"));
+ int hseparation = get_theme_constant(SNAME("table_h_separation"));
int col_count = table->columns.size();
int row_count = table->rows.size();
@@ -803,8 +879,8 @@ int RichTextLabel::_draw_line(ItemFrame *p_frame, int p_line, const Vector2 &p_o
draw_rect(Rect2(p_ofs + rect.position + off + coff - frame->padding.position, Size2(table->columns[col].width + hseparation + frame->padding.position.x + frame->padding.size.x, table->rows[row])), (frame->border != Color(0, 0, 0, 0) ? frame->border : border), false);
}
- 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_outline_size, p_shadow_ofs);
+ for (int j = 0; j < (int)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_outline_size, p_shadow_ofs, r_processed_glyphs);
}
idx++;
}
@@ -820,12 +896,14 @@ 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);
+ Color font_color = _find_color(it, p_base_color);
+ Color font_outline_color = _find_outline_color(it, p_outline_color);
Color font_shadow_color = p_font_shadow_color;
- if ((size <= 0 || font_color.a == 0) && (font_shadow_color.a == 0)) {
+ if ((size <= 0 || font_outline_color.a == 0) && (font_shadow_color.a == 0)) {
gloff.x += glyphs[i].advance;
continue;
}
@@ -865,17 +943,17 @@ int RichTextLabel::_draw_line(ItemFrame *p_frame, int p_line, const Vector2 &p_o
}
//Apply fx.
- float faded_visibility = 1.0f;
if (fade) {
+ float faded_visibility = 1.0f;
if (glyphs[i].start >= fade->starting_index) {
faded_visibility -= (float)(glyphs[i].start - fade->starting_index) / (float)fade->length;
faded_visibility = faded_visibility < 0.0f ? 0.0f : faded_visibility;
}
- font_color.a = faded_visibility;
+ font_outline_color.a = faded_visibility;
font_shadow_color.a = faded_visibility;
}
- bool visible = (font_color.a != 0) || (font_shadow_color.a != 0);
+ bool visible = (font_outline_color.a != 0) || (font_shadow_color.a != 0);
for (int j = 0; j < fx_stack.size(); j++) {
ItemFX *item_fx = fx_stack[j];
@@ -945,19 +1023,23 @@ int RichTextLabel::_draw_line(ItemFrame *p_frame, int p_line, const Vector2 &p_o
}
// Draw glyph outlines.
+ const Color modulated_outline_color = font_outline_color * Color(1, 1, 1, font_color.a);
+ const Color modulated_shadow_color = font_shadow_color * Color(1, 1, 1, font_color.a);
for (int j = 0; j < glyphs[i].repeat; j++) {
if (visible) {
- if (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);
+ 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 (modulated_shadow_color.a > 0) {
+ TS->font_draw_glyph(frid, ci, glyphs[i].font_size, p_ofs + fx_offset + gloff + p_shadow_ofs, gl, modulated_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 (modulated_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, modulated_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);
+ if (modulated_outline_color.a != 0.0 && size > 0) {
+ TS->font_draw_glyph_outline(frid, ci, glyphs[i].font_size, size, p_ofs + fx_offset + gloff, gl, modulated_outline_color);
}
}
+ processed_glyphs_ol++;
}
gloff.x += glyphs[i].advance;
}
@@ -986,22 +1068,60 @@ int RichTextLabel::_draw_line(ItemFrame *p_frame, int p_line, const Vector2 &p_o
}
}
+ Vector2 ul_start;
+ bool ul_started = false;
+ Color ul_color;
+
+ Vector2 dot_ul_start;
+ bool dot_ul_started = false;
+ Color dot_ul_color;
+
+ Vector2 st_start;
+ bool st_started = false;
+ Color st_color;
+
for (int i = 0; i < gl_size; i++) {
bool selected = selection.active && (sel_start != -1) && (glyphs[i].start >= sel_start) && (glyphs[i].end <= sel_end);
Item *it = _get_item_at_pos(it_from, it_to, glyphs[i].start);
Color font_color = _find_color(it, p_base_color);
- if (_find_underline(it) || (_find_meta(it, &meta) && underline_meta)) {
- Color uc = font_color;
- uc.a *= 0.5;
+ if (_find_underline(it) || (_find_meta(it, nullptr) && underline_meta)) {
+ if (!ul_started) {
+ ul_started = true;
+ ul_start = p_ofs + Vector2(off.x, off.y);
+ ul_color = font_color;
+ ul_color.a *= 0.5;
+ }
+ } else if (ul_started) {
+ ul_started = false;
+ float y_off = TS->shaped_text_get_underline_position(rid);
+ float underline_width = TS->shaped_text_get_underline_thickness(rid) * get_theme_default_base_scale();
+ draw_line(ul_start + Vector2(0, y_off), p_ofs + Vector2(off.x, off.y + y_off), ul_color, underline_width);
+ }
+ if (_find_hint(it, nullptr) && underline_hint) {
+ if (!dot_ul_started) {
+ dot_ul_started = true;
+ dot_ul_start = p_ofs + Vector2(off.x, off.y);
+ dot_ul_color = font_color;
+ dot_ul_color.a *= 0.5;
+ }
+ } else if (dot_ul_started) {
+ dot_ul_started = false;
float y_off = TS->shaped_text_get_underline_position(rid);
float underline_width = TS->shaped_text_get_underline_thickness(rid) * get_theme_default_base_scale();
- 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)) {
- Color uc = font_color;
- uc.a *= 0.5;
+ draw_dashed_line(dot_ul_start + Vector2(0, y_off), p_ofs + Vector2(off.x, off.y + y_off), dot_ul_color, underline_width, underline_width * 2);
+ }
+ if (_find_strikethrough(it)) {
+ if (!st_started) {
+ st_started = true;
+ st_start = p_ofs + Vector2(off.x, off.y);
+ st_color = font_color;
+ st_color.a *= 0.5;
+ }
+ } else if (st_started) {
+ st_started = false;
float y_off = -TS->shaped_text_get_ascent(rid) + TS->shaped_text_get_size(rid).y / 2;
float underline_width = TS->shaped_text_get_underline_thickness(rid) * get_theme_default_base_scale();
- 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);
+ draw_line(st_start + Vector2(0, y_off), p_ofs + Vector2(off.x, off.y + y_off), st_color, underline_width);
}
// Get FX.
@@ -1039,8 +1159,8 @@ int RichTextLabel::_draw_line(ItemFrame *p_frame, int p_line, const Vector2 &p_o
}
//Apply fx.
- float faded_visibility = 1.0f;
if (fade) {
+ float faded_visibility = 1.0f;
if (glyphs[i].start >= fade->starting_index) {
faded_visibility -= (float)(glyphs[i].start - fade->starting_index) / (float)fade->length;
faded_visibility = faded_visibility < 0.0f ? 0.0f : faded_visibility;
@@ -1124,15 +1244,37 @@ 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;
}
}
+ if (ul_started) {
+ ul_started = false;
+ float y_off = TS->shaped_text_get_underline_position(rid);
+ float underline_width = TS->shaped_text_get_underline_thickness(rid) * get_theme_default_base_scale();
+ draw_line(ul_start + Vector2(0, y_off), p_ofs + Vector2(off.x, off.y + y_off), ul_color, underline_width);
+ }
+ if (dot_ul_started) {
+ dot_ul_started = false;
+ float y_off = TS->shaped_text_get_underline_position(rid);
+ float underline_width = TS->shaped_text_get_underline_thickness(rid) * get_theme_default_base_scale();
+ draw_dashed_line(dot_ul_start + Vector2(0, y_off), p_ofs + Vector2(off.x, off.y + y_off), dot_ul_color, underline_width, underline_width * 2);
+ }
+ if (st_started) {
+ st_started = false;
+ float y_off = -TS->shaped_text_get_ascent(rid) + TS->shaped_text_get_size(rid).y / 2;
+ float underline_width = TS->shaped_text_get_underline_thickness(rid) * get_theme_default_base_scale();
+ draw_line(st_start + Vector2(0, y_off), p_ofs + Vector2(off.x, off.y + y_off), st_color, underline_width);
+ }
// Draw foreground color box
_draw_fbg_boxes(ci, rid, fbg_line_off, it_from, it_to, chr_range.x, chr_range.y, 1);
@@ -1159,24 +1301,14 @@ void RichTextLabel::_find_click(ItemFrame *p_frame, const Point2i &p_click, Item
int vofs = vscroll->get_value();
// Search for the first line.
- int from_line = 0;
-
- //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) {
- break;
- }
- from_line++;
- }
-
- if (from_line >= main->lines.size()) {
- return;
- }
+ int to_line = main->first_invalid_line.load();
+ int from_line = _find_first_line(0, to_line, vofs);
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()) {
+ while (ofs.y < size.height && from_line < to_line) {
+ MutexLock lock(main->lines[from_line].text_buf->get_mutex());
_find_click_in_line(p_frame, from_line, ofs, text_rect.size.x, p_click, r_click_frame, r_click_line, r_click_item, r_click_char);
- ofs.y += main->lines[from_line].text_buf->get_size().y + 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;
@@ -1187,14 +1319,24 @@ void RichTextLabel::_find_click(ItemFrame *p_frame, const Point2i &p_click, Item
}
}
-float RichTextLabel::_find_click_in_line(ItemFrame *p_frame, int p_line, const Vector2 &p_ofs, int p_width, const Point2i &p_click, ItemFrame **r_click_frame, int *r_click_line, Item **r_click_item, int *r_click_char) {
+float RichTextLabel::_find_click_in_line(ItemFrame *p_frame, int p_line, const Vector2 &p_ofs, int p_width, const Point2i &p_click, ItemFrame **r_click_frame, int *r_click_line, Item **r_click_item, int *r_click_char, bool p_table) {
Vector2 off;
int char_pos = -1;
- Line &l = p_frame->lines.write[p_line];
+ Line &l = p_frame->lines[p_line];
+ MutexLock lock(l.text_buf->get_mutex());
+
bool rtl = (l.text_buf->get_direction() == TextServer::DIRECTION_RTL);
bool lrtl = is_layout_rtl();
+
+ // Table hit test results.
bool table_hit = false;
+ Vector2i table_range;
+ float table_offy = 0.f;
+ ItemFrame *table_click_frame = nullptr;
+ int table_click_line = -1;
+ Item *table_click_item = nullptr;
+ int table_click_char = -1;
for (int line = 0; line < l.text_buf->get_line_count(); line++) {
RID rid = l.text_buf->get_line_rid(line);
@@ -1235,19 +1377,18 @@ float RichTextLabel::_find_click_in_line(ItemFrame *p_frame, int p_line, const V
Array objects = TS->shaped_text_get_objects(rid);
for (int i = 0; i < objects.size(); i++) {
- Item *it = (Item *)(uint64_t)objects[i];
+ Item *it = reinterpret_cast<Item *>((uint64_t)objects[i]);
if (it != nullptr) {
Rect2 rect = TS->shaped_text_get_object_rect(rid, objects[i]);
- if (rect.has_point(p_click - p_ofs - off)) {
+ rect.position += p_ofs + off;
+ if (p_click.y >= rect.position.y && p_click.y <= rect.position.y + rect.size.y) {
switch (it->type) {
case ITEM_TABLE: {
- int hseparation = get_theme_constant(SNAME("table_hseparation"));
- int vseparation = get_theme_constant(SNAME("table_vseparation"));
+ int hseparation = get_theme_constant(SNAME("table_h_separation"));
+ int vseparation = get_theme_constant(SNAME("table_v_separation"));
ItemTable *table = static_cast<ItemTable *>(it);
- table_hit = true;
-
int idx = 0;
int col_count = table->columns.size();
int row_count = table->rows.size();
@@ -1263,7 +1404,7 @@ float RichTextLabel::_find_click_in_line(ItemFrame *p_frame, int p_line, const V
if (rtl) {
coff.x = rect.size.width - table->columns[col].width - coff.x;
}
- Rect2 crect = Rect2(p_ofs + off + rect.position + coff - frame->padding.position, Size2(table->columns[col].width + hseparation, table->rows[row] + vseparation) + frame->padding.position + frame->padding.size);
+ Rect2 crect = Rect2(rect.position + coff - frame->padding.position, Size2(table->columns[col].width + hseparation, table->rows[row] + vseparation) + frame->padding.position + frame->padding.size);
if (col == col_count - 1) {
if (rtl) {
crect.size.x = crect.position.x + crect.size.x;
@@ -1273,10 +1414,20 @@ float RichTextLabel::_find_click_in_line(ItemFrame *p_frame, int p_line, const V
}
}
if (crect.has_point(p_click)) {
- for (int j = 0; j < frame->lines.size(); j++) {
- _find_click_in_line(frame, j, p_ofs + off + rect.position + Vector2(0, frame->lines[j].offset.y), rect.size.x, p_click, r_click_frame, r_click_line, r_click_item, r_click_char);
- if (((r_click_item != nullptr) && ((*r_click_item) != nullptr)) || ((r_click_frame != nullptr) && ((*r_click_frame) != nullptr))) {
- return off.y;
+ for (int j = 0; j < (int)frame->lines.size(); j++) {
+ _find_click_in_line(frame, j, rect.position + Vector2(0, frame->lines[j].offset.y), rect.size.x, p_click, &table_click_frame, &table_click_line, &table_click_item, &table_click_char, true);
+ if (table_click_frame && table_click_item) {
+ // Save cell detected cell hit data.
+ table_range = Vector2i(INT32_MAX, 0);
+ for (Item *F : table->subitems) {
+ ItemFrame *sub_frame = static_cast<ItemFrame *>(F);
+ for (int k = 0; k < (int)sub_frame->lines.size(); k++) {
+ table_range.x = MIN(table_range.x, sub_frame->lines[k].char_offset);
+ table_range.y = MAX(table_range.y, sub_frame->lines[k].char_offset + sub_frame->lines[k].char_count);
+ }
+ }
+ table_offy = off.y;
+ table_hit = true;
}
}
}
@@ -1290,25 +1441,50 @@ float RichTextLabel::_find_click_in_line(ItemFrame *p_frame, int p_line, const V
}
}
}
- Rect2 rect = Rect2(p_ofs + off - Vector2(0, TS->shaped_text_get_ascent(rid)), Size2(get_size().x, TS->shaped_text_get_size(rid).y));
+ Rect2 rect = Rect2(p_ofs + off - Vector2(0, TS->shaped_text_get_ascent(rid)) - p_frame->padding.position, TS->shaped_text_get_size(rid) + p_frame->padding.position + p_frame->padding.size);
+ if (p_table) {
+ rect.size.y += get_theme_constant(SNAME("table_v_separation"));
+ }
- if (rect.has_point(p_click) && !table_hit) {
+ if (p_click.y >= rect.position.y && p_click.y <= rect.position.y + rect.size.y) {
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();
+
+ // If table hit was detected, and line hit is in the table bounds use table hit.
+ if (table_hit && (((char_pos + p_frame->lines[p_line].char_offset) >= table_range.x && (char_pos + p_frame->lines[p_line].char_offset) <= table_range.y) || char_pos == -1)) {
+ if (r_click_frame != nullptr) {
+ *r_click_frame = table_click_frame;
+ }
+
+ if (r_click_line != nullptr) {
+ *r_click_line = table_click_line;
+ }
+
+ if (r_click_item != nullptr) {
+ *r_click_item = table_click_item;
+ }
+
+ if (r_click_char != nullptr) {
+ *r_click_char = table_click_char;
+ }
+ return table_offy;
+ }
+
+ off.y += TS->shaped_text_get_descent(rid) + l.text_buf->get_spacing_bottom() + get_theme_constant(SNAME("line_separation"));
}
+ // Text line hit.
if (char_pos >= 0) {
// Find item.
if (r_click_item != nullptr) {
Item *it = p_frame->lines[p_line].from;
- Item *it_to = (p_line + 1 < p_frame->lines.size()) ? p_frame->lines[p_line + 1].from : nullptr;
+ Item *it_to = (p_line + 1 < (int)p_frame->lines.size()) ? p_frame->lines[p_line + 1].from : nullptr;
if (char_pos == p_frame->lines[p_line].char_count) {
// Selection after the end of line, select last item.
if (it_to != nullptr) {
*r_click_item = _get_prev_item(it_to);
} else {
- for (Item *i = it; i && i != it_to; i = _get_next_item(i)) {
+ for (Item *i = it; i; i = _get_next_item(i)) {
*r_click_item = i;
}
}
@@ -1350,28 +1526,6 @@ void RichTextLabel::_scroll_changed(double) {
update();
}
-void RichTextLabel::_update_scroll() {
- int total_height = get_content_height();
-
- bool exceeds = total_height > get_size().height && scroll_active;
-
- if (exceeds != scroll_visible) {
- if (exceeds) {
- scroll_visible = true;
- scroll_w = vscroll->get_combined_minimum_size().width;
- vscroll->show();
- vscroll->set_anchor_and_offset(SIDE_LEFT, ANCHOR_END, -scroll_w);
- } else {
- scroll_visible = false;
- scroll_w = 0;
- vscroll->hide();
- }
-
- main->first_resized_line = 0; //invalidate ALL
- _validate_line_caches(main);
- }
-}
-
void RichTextLabel::_update_fx(RichTextLabel::ItemFrame *p_frame, double p_delta_time) {
Item *it = p_frame;
while (it) {
@@ -1406,6 +1560,26 @@ void RichTextLabel::_update_fx(RichTextLabel::ItemFrame *p_frame, double p_delta
}
}
+int RichTextLabel::_find_first_line(int p_from, int p_to, int p_vofs) const {
+ int l = p_from;
+ int r = p_to;
+ while (l < r) {
+ int m = Math::floor(double(l + r) / 2.0);
+ MutexLock lock(main->lines[m].text_buf->get_mutex());
+ int ofs = _calculate_line_vertical_offset(main->lines[m]);
+ if (ofs < p_vofs) {
+ l = m + 1;
+ } else {
+ r = m;
+ }
+ }
+ return l;
+}
+
+_FORCE_INLINE_ float RichTextLabel::_calculate_line_vertical_offset(const RichTextLabel::Line &line) const {
+ return line.get_height(get_theme_constant(SNAME("line_separation")));
+}
+
void RichTextLabel::_notification(int p_what) {
switch (p_what) {
case NOTIFICATION_MOUSE_EXIT: {
@@ -1416,33 +1590,48 @@ void RichTextLabel::_notification(int p_what) {
update();
}
} break;
+
case NOTIFICATION_RESIZED: {
- main->first_resized_line = 0; //invalidate ALL
+ _stop_thread();
+ main->first_resized_line.store(0); //invalidate ALL
update();
+ } break;
+ case NOTIFICATION_THEME_CHANGED: {
+ _stop_thread();
+ main->first_invalid_font_line.store(0); //invalidate ALL
+ update();
} break;
- case NOTIFICATION_THEME_CHANGED:
+
case NOTIFICATION_ENTER_TREE: {
+ _stop_thread();
if (!text.is_empty()) {
set_text(text);
}
- main->first_invalid_line = 0; //invalidate ALL
+ main->first_invalid_line.store(0); //invalidate ALL
update();
} break;
+
+ case NOTIFICATION_PREDELETE:
+ case NOTIFICATION_EXIT_TREE: {
+ _stop_thread();
+ } break;
+
case NOTIFICATION_LAYOUT_DIRECTION_CHANGED:
case NOTIFICATION_TRANSLATION_CHANGED: {
- main->first_invalid_line = 0; //invalidate ALL
+ _stop_thread();
+ main->first_invalid_line.store(0); //invalidate ALL
update();
} break;
- case NOTIFICATION_DRAW: {
- _validate_line_caches(main);
- _update_scroll();
- RID ci = get_canvas_item();
+ case NOTIFICATION_INTERNAL_PHYSICS_PROCESS: {
+ update();
+ } break;
+ case NOTIFICATION_DRAW: {
+ RID ci = get_canvas_item();
Size2 size = get_size();
- Rect2 text_rect = _get_text_rect();
draw_style_box(get_theme_stylebox(SNAME("normal")), Rect2(Point2(), size));
@@ -1452,22 +1641,42 @@ void RichTextLabel::_notification(int p_what) {
RenderingServer::get_singleton()->canvas_item_add_clip_ignore(ci, false);
}
+ // Start text shaping.
+ if (_validate_line_caches()) {
+ set_physics_process_internal(false); // Disable auto refresh, if text is fully processed.
+ } else {
+ // Draw loading progress bar.
+ if ((progress_delay > 0) && (OS::get_singleton()->get_ticks_msec() - loading_started >= (uint64_t)progress_delay)) {
+ Ref<StyleBox> bg = get_theme_stylebox(SNAME("bg"), SNAME("ProgressBar"));
+ Ref<StyleBox> fg = get_theme_stylebox(SNAME("fg"), SNAME("ProgressBar"));
+ Ref<StyleBox> style = get_theme_stylebox(SNAME("normal"));
+
+ Vector2 p_size = Vector2(size.width - (style->get_offset().x + vscroll->get_combined_minimum_size().width) * 2, vscroll->get_combined_minimum_size().width);
+ Vector2 p_pos = Vector2(style->get_offset().x, size.height - style->get_offset().y - vscroll->get_combined_minimum_size().width);
+
+ draw_style_box(bg, Rect2(p_pos, p_size));
+
+ bool right_to_left = is_layout_rtl();
+ double r = loaded.load();
+ int mp = fg->get_minimum_size().width;
+ int p = round(r * (p_size.width - mp));
+ if (right_to_left) {
+ int p_remaining = round((1.0 - r) * (p_size.width - mp));
+ draw_style_box(fg, Rect2(p_pos + Point2(p_remaining, 0), Size2(p + fg->get_minimum_size().width, p_size.height)));
+ } else {
+ draw_style_box(fg, Rect2(p_pos, Size2(p + fg->get_minimum_size().width, p_size.height)));
+ }
+ }
+ }
+
+ // Draw main text.
+ Rect2 text_rect = _get_text_rect();
float vofs = vscroll->get_value();
// Search for the first line.
- int from_line = 0;
+ int to_line = main->first_invalid_line.load();
+ int from_line = _find_first_line(0, to_line, vofs);
- //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) {
- break;
- }
- from_line++;
- }
-
- if (from_line >= main->lines.size()) {
- break; //nothing to draw
- }
Ref<Font> base_font = get_theme_font(SNAME("normal_font"));
Color base_color = get_theme_color(SNAME("default_color"));
Color outline_color = get_theme_color(SNAME("font_outline_color"));
@@ -1481,26 +1690,37 @@ void RichTextLabel::_notification(int p_what) {
// New cache draw.
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()) {
+ int processed_glyphs = 0;
+ while (ofs.y < size.height && from_line < to_line) {
+ MutexLock lock(main->lines[from_line].text_buf->get_mutex());
+
visible_paragraph_count++;
- visible_line_count += _draw_line(main, from_line, ofs, text_rect.size.x, base_color, outline_size, outline_color, font_shadow_color, shadow_outline_size, shadow_ofs);
- 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()) {
+ if (!is_ready()) {
+ return;
+ }
double dt = get_process_delta_time();
_update_fx(main, dt);
update();
}
} break;
+
case NOTIFICATION_FOCUS_EXIT: {
if (deselect_on_focus_loss_enabled) {
- selection.active = false;
- update();
+ deselect();
}
} break;
+
+ case NOTIFICATION_DRAG_END: {
+ selection.drag_attempt = false;
+ } break;
}
}
@@ -1513,22 +1733,13 @@ Control::CursorShape RichTextLabel::get_cursor_shape(const Point2 &p_pos) const
return CURSOR_IBEAM;
}
- if (main->first_invalid_line < main->lines.size()) {
- return get_default_cursor_shape(); //invalid
- }
-
- if (main->first_resized_line < main->lines.size()) {
- return get_default_cursor_shape(); //invalid
- }
-
Item *item = nullptr;
bool outside = true;
- ((RichTextLabel *)(this))->_find_click(main, p_pos, nullptr, nullptr, &item, nullptr, &outside);
+ const_cast<RichTextLabel *>(this)->_find_click(main, p_pos, nullptr, nullptr, &item, nullptr, &outside);
- if (item && !outside && ((RichTextLabel *)(this))->_find_meta(item, nullptr)) {
+ if (item && !outside && const_cast<RichTextLabel *>(this)->_find_meta(item, nullptr)) {
return CURSOR_POINTING_HAND;
}
-
return get_default_cursor_shape();
}
@@ -1538,13 +1749,6 @@ void RichTextLabel::gui_input(const Ref<InputEvent> &p_event) {
Ref<InputEventMouseButton> b = p_event;
if (b.is_valid()) {
- if (main->first_invalid_line < main->lines.size()) {
- return;
- }
- if (main->first_resized_line < main->lines.size()) {
- return;
- }
-
if (b->get_button_index() == MouseButton::LEFT) {
if (b->is_pressed() && !b->is_double_click()) {
scroll_updated = false;
@@ -1554,6 +1758,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) {
@@ -1564,17 +1770,20 @@ 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;
+ deselect();
+ }
}
}
}
@@ -1587,10 +1796,13 @@ 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) {
const Line &l = c_frame->lines[c_line];
+ MutexLock lock(l.text_buf->get_mutex());
PackedInt32Array words = TS->shaped_text_get_word_breaks(l.text_buf->get_rid());
for (int i = 0; i < words.size(); i = i + 2) {
if (c_index >= words[i] && c_index < words[i + 1]) {
@@ -1618,8 +1830,21 @@ void RichTextLabel::gui_input(const Ref<InputEvent> &p_event) {
DisplayServer::get_singleton()->clipboard_set_primary(get_selected_text());
}
selection.click_item = nullptr;
-
- if (!b->is_double_click() && !scroll_updated) {
+ 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;
+ deselect();
+ }
+ }
+ if (!b->is_double_click() && !scroll_updated && !selection.active) {
Item *c_item = nullptr;
bool outside = true;
@@ -1646,6 +1871,13 @@ void RichTextLabel::gui_input(const Ref<InputEvent> &p_event) {
vscroll->set_value(vscroll->get_value() + vscroll->get_page() * b->get_factor() * 0.5 / 8);
}
}
+ if (b->get_button_index() == MouseButton::RIGHT && context_menu_enabled) {
+ _generate_context_menu();
+ menu->set_position(get_screen_position() + b->get_position());
+ menu->reset_size();
+ menu->popup();
+ grab_focus();
+ }
}
Ref<InputEventPanGesture> pan_gesture = p_event;
@@ -1687,8 +1919,24 @@ void RichTextLabel::gui_input(const Ref<InputEvent> &p_event) {
vscroll->set_value(vscroll->get_max());
handled = true;
}
- if (k->is_action("ui_copy")) {
- selection_copy();
+ if (is_shortcut_keys_enabled()) {
+ if (k->is_action("ui_text_select_all")) {
+ select_all();
+ handled = true;
+ }
+ if (k->is_action("ui_copy")) {
+ selection_copy();
+ handled = true;
+ }
+ }
+ if (k->is_action("ui_menu", true)) {
+ if (context_menu_enabled) {
+ _generate_context_menu();
+ menu->set_position(get_screen_position());
+ menu->reset_size();
+ menu->popup();
+ menu->grab_focus();
+ }
handled = true;
}
@@ -1699,15 +1947,7 @@ void RichTextLabel::gui_input(const Ref<InputEvent> &p_event) {
}
Ref<InputEventMouseMotion> m = p_event;
-
if (m.is_valid()) {
- if (main->first_invalid_line < main->lines.size()) {
- return;
- }
- if (main->first_resized_line < main->lines.size()) {
- return;
- }
-
ItemFrame *c_frame = nullptr;
int c_line = 0;
Item *c_item = nullptr;
@@ -1727,14 +1967,13 @@ void RichTextLabel::gui_input(const Ref<InputEvent> &p_event) {
selection.to_char = c_index;
bool swap = false;
- if (selection.from_item->index > selection.to_item->index) {
- swap = true;
- } else if (selection.from_item->index == selection.to_item->index) {
- if (selection.from_char > selection.to_char) {
+ if (selection.click_frame && c_frame) {
+ const Line &l1 = c_frame->lines[c_line];
+ const Line &l2 = selection.click_frame->lines[selection.click_line];
+ if (l1.char_offset + c_index < l2.char_offset + selection.click_char) {
swap = true;
- } else if (selection.from_char == selection.to_char) {
- selection.active = false;
- update();
+ } else if (l1.char_offset + c_index == l2.char_offset + selection.click_char) {
+ deselect();
return;
}
}
@@ -1769,6 +2008,20 @@ void RichTextLabel::gui_input(const Ref<InputEvent> &p_event) {
}
}
+String RichTextLabel::get_tooltip(const Point2 &p_pos) const {
+ Item *c_item = nullptr;
+ bool outside;
+
+ const_cast<RichTextLabel *>(this)->_find_click(main, p_pos, nullptr, nullptr, &c_item, nullptr, &outside);
+
+ String description;
+ if (c_item && !outside && const_cast<RichTextLabel *>(this)->_find_hint(c_item, &description)) {
+ return description;
+ } else {
+ return Control::get_tooltip(p_pos);
+ }
+}
+
void RichTextLabel::_find_frame(Item *p_item, ItemFrame **r_frame, int *r_line) {
if (r_frame != nullptr) {
*r_frame = nullptr;
@@ -1782,7 +2035,7 @@ void RichTextLabel::_find_frame(Item *p_item, ItemFrame **r_frame, int *r_line)
while (item) {
if (item->parent != nullptr && item->parent->type == ITEM_FRAME) {
if (r_frame != nullptr) {
- *r_frame = (ItemFrame *)item->parent;
+ *r_frame = static_cast<ItemFrame *>(item->parent);
}
if (r_line != nullptr) {
*r_line = item->line;
@@ -1986,7 +2239,7 @@ TextServer::Direction RichTextLabel::_find_direction(Item *p_item) {
}
}
-Control::StructuredTextParser RichTextLabel::_find_stt(Item *p_item) {
+TextServer::StructuredTextParser RichTextLabel::_find_stt(Item *p_item) {
Item *item = p_item;
while (item) {
@@ -2106,6 +2359,24 @@ bool RichTextLabel::_find_meta(Item *p_item, Variant *r_meta, ItemMeta **r_item)
return false;
}
+bool RichTextLabel::_find_hint(Item *p_item, String *r_description) {
+ Item *item = p_item;
+
+ while (item) {
+ if (item->type == ITEM_HINT) {
+ ItemHint *hint = static_cast<ItemHint *>(item);
+ if (r_description) {
+ *r_description = hint->description;
+ }
+ return true;
+ }
+
+ item = item->parent;
+ }
+
+ return false;
+}
+
Color RichTextLabel::_find_bgcolor(Item *p_item) {
Item *item = p_item;
@@ -2154,91 +2425,218 @@ bool RichTextLabel::_find_layout_subitem(Item *from, Item *to) {
return false;
}
-void RichTextLabel::_validate_line_caches(ItemFrame *p_frame) {
- if (p_frame->first_invalid_line == p_frame->lines.size()) {
- if (p_frame->first_resized_line == p_frame->lines.size()) {
- return;
- }
+void RichTextLabel::_thread_function(void *self) {
+ RichTextLabel *rtl = reinterpret_cast<RichTextLabel *>(self);
+ rtl->_process_line_caches();
+ rtl->updating.store(false);
+ rtl->call_deferred(SNAME("update"));
+}
- // Resize lines without reshaping.
- Size2 size = get_size();
- if (fixed_width != -1) {
- size.width = fixed_width;
- }
+void RichTextLabel::_stop_thread() {
+ if (threaded) {
+ stop_thread.store(true);
+ thread.wait_to_finish();
+ }
+}
+
+bool RichTextLabel::is_ready() const {
+ if (updating.load()) {
+ return false;
+ }
+ return (main->first_invalid_line.load() == (int)main->lines.size() && main->first_resized_line.load() == (int)main->lines.size() && main->first_invalid_font_line.load() == (int)main->lines.size());
+}
+
+void RichTextLabel::set_threaded(bool p_threaded) {
+ if (threaded != p_threaded) {
+ _stop_thread();
+ threaded = p_threaded;
+ update();
+ }
+}
+
+bool RichTextLabel::is_threaded() const {
+ return threaded;
+}
+
+void RichTextLabel::set_progress_bar_delay(int p_delay_ms) {
+ progress_delay = p_delay_ms;
+}
+
+int RichTextLabel::get_progress_bar_delay() const {
+ return progress_delay;
+}
+
+bool RichTextLabel::_validate_line_caches() {
+ if (updating.load()) {
+ return false;
+ }
+ if (main->first_invalid_line.load() == (int)main->lines.size()) {
+ MutexLock data_lock(data_mutex);
Rect2 text_rect = _get_text_rect();
Ref<Font> base_font = get_theme_font(SNAME("normal_font"));
int base_font_size = get_theme_font_size(SNAME("normal_font_size"));
+ int ctrl_height = get_size().height;
- 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);
+ // Update fonts.
+ if (main->first_invalid_font_line.load() != (int)main->lines.size()) {
+ for (int i = main->first_invalid_font_line.load(); i < (int)main->lines.size(); i++) {
+ _update_line_font(main, i, base_font, base_font_size);
+ }
+ main->first_resized_line.store(main->first_invalid_font_line.load());
+ main->first_invalid_font_line.store(main->lines.size());
}
- 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;
+ if (main->first_resized_line.load() == (int)main->lines.size()) {
+ return true;
}
- p_frame->first_resized_line = p_frame->lines.size();
+ // Resize lines without reshaping.
+ int fi = main->first_resized_line.load();
+
+ float total_height = (fi == 0) ? 0 : _calculate_line_vertical_offset(main->lines[fi - 1]);
+ for (int i = fi; i < (int)main->lines.size(); i++) {
+ total_height = _resize_line(main, i, base_font, base_font_size, text_rect.get_size().width - scroll_w, total_height);
+
+ updating_scroll = true;
+ bool exceeds = total_height > ctrl_height && scroll_active;
+ if (exceeds != scroll_visible) {
+ if (exceeds) {
+ scroll_visible = true;
+ scroll_w = vscroll->get_combined_minimum_size().width;
+ vscroll->show();
+ vscroll->set_anchor_and_offset(SIDE_LEFT, ANCHOR_END, -scroll_w);
+ } else {
+ scroll_visible = false;
+ scroll_w = 0;
+ vscroll->hide();
+ }
- 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);
+ main->first_resized_line.store(0);
+
+ total_height = 0;
+ for (int j = 0; j <= i; j++) {
+ total_height = _resize_line(main, j, base_font, base_font_size, text_rect.get_size().width - scroll_w, total_height);
+
+ main->first_resized_line.store(j);
+ }
+ }
+
+ vscroll->set_max(total_height);
+ vscroll->set_page(text_rect.size.height);
+ if (scroll_follow && scroll_following) {
+ vscroll->set_value(total_height);
+ }
+ updating_scroll = false;
+
+ main->first_resized_line.store(i);
}
- updating_scroll = false;
+
+ main->first_resized_line.store(main->lines.size());
if (fit_content_height) {
update_minimum_size();
}
- return;
+ return true;
+ }
+ stop_thread.store(false);
+ if (threaded) {
+ updating.store(true);
+ loaded.store(true);
+ thread.start(RichTextLabel::_thread_function, reinterpret_cast<void *>(this));
+ loading_started = OS::get_singleton()->get_ticks_msec();
+ set_physics_process_internal(true);
+ return false;
+ } else {
+ _process_line_caches();
+ update();
+ return true;
}
+}
+void RichTextLabel::_process_line_caches() {
// Shape invalid lines.
- Size2 size = get_size();
- if (fixed_width != -1) {
- size.width = fixed_width;
+ if (!is_inside_tree()) {
+ return;
}
+
+ MutexLock data_lock(data_mutex);
Rect2 text_rect = _get_text_rect();
Ref<Font> base_font = get_theme_font(SNAME("normal_font"));
int base_font_size = get_theme_font_size(SNAME("normal_font_size"));
+ int ctrl_height = get_size().height;
+ int fi = main->first_invalid_line.load();
+ int total_chars = (fi == 0) ? 0 : (main->lines[fi].char_offset + main->lines[fi].char_count);
- int total_chars = (p_frame->first_invalid_line == 0) ? 0 : (p_frame->lines[p_frame->first_invalid_line].char_offset + p_frame->lines[p_frame->first_invalid_line].char_count);
- for (int i = p_frame->first_invalid_line; i < p_frame->lines.size(); i++) {
- _shape_line(p_frame, i, base_font, base_font_size, text_rect.get_size().width - scroll_w, &total_chars);
- }
+ float total_height = (fi == 0) ? 0 : _calculate_line_vertical_offset(main->lines[fi - 1]);
+ for (int i = fi; i < (int)main->lines.size(); i++) {
+ total_height = _shape_line(main, i, base_font, base_font_size, text_rect.get_size().width - scroll_w, total_height, &total_chars);
+ updating_scroll = true;
+ bool exceeds = total_height > ctrl_height && scroll_active;
+ if (exceeds != scroll_visible) {
+ if (exceeds) {
+ scroll_visible = true;
+ scroll_w = vscroll->get_combined_minimum_size().width;
+ vscroll->show();
+ vscroll->set_anchor_and_offset(SIDE_LEFT, ANCHOR_END, -scroll_w);
+ } else {
+ scroll_visible = false;
+ scroll_w = 0;
+ vscroll->hide();
+ }
+ main->first_invalid_line.store(0);
+ main->first_resized_line.store(0);
+ main->first_invalid_font_line.store(0);
+
+ // since scroll was added or removed we need to resize all lines
+ total_height = 0;
+ for (int j = 0; j <= i; j++) {
+ total_height = _resize_line(main, j, base_font, base_font_size, text_rect.get_size().width - scroll_w, total_height);
+
+ main->first_invalid_line.store(j);
+ main->first_resized_line.store(j);
+ main->first_invalid_font_line.store(j);
+ }
+ }
- 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;
- }
+ vscroll->set_max(total_height);
+ vscroll->set_page(text_rect.size.height);
+ if (scroll_follow && scroll_following) {
+ vscroll->set_value(total_height);
+ }
+ updating_scroll = false;
- p_frame->first_invalid_line = p_frame->lines.size();
- p_frame->first_resized_line = p_frame->lines.size();
+ main->first_invalid_line.store(i);
+ main->first_resized_line.store(i);
+ main->first_invalid_font_line.store(i);
- 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);
+ if (stop_thread.load()) {
+ return;
+ }
+ loaded.store(double(i) / double(main->lines.size()));
}
- updating_scroll = false;
+
+ main->first_invalid_line.store(main->lines.size());
+ main->first_resized_line.store(main->lines.size());
+ main->first_invalid_font_line.store(main->lines.size());
if (fit_content_height) {
update_minimum_size();
}
+ emit_signal(SNAME("finished"));
}
void RichTextLabel::_invalidate_current_line(ItemFrame *p_frame) {
- if (p_frame->lines.size() - 1 <= p_frame->first_invalid_line) {
- p_frame->first_invalid_line = p_frame->lines.size() - 1;
- update();
+ if ((int)p_frame->lines.size() - 1 <= p_frame->first_invalid_line) {
+ p_frame->first_invalid_line = (int)p_frame->lines.size() - 1;
}
}
void RichTextLabel::add_text(const String &p_text) {
+ _stop_thread();
+ MutexLock data_lock(data_mutex);
+
if (current->type == ITEM_TABLE) {
return; //can't add anything here
}
@@ -2282,13 +2680,14 @@ void RichTextLabel::add_text(const String &p_text) {
_add_item(item, false);
current_frame->lines.resize(current_frame->lines.size() + 1);
if (item->type != ITEM_NEWLINE) {
- current_frame->lines.write[current_frame->lines.size() - 1].from = item;
+ current_frame->lines[current_frame->lines.size() - 1].from = item;
}
_invalidate_current_line(current_frame);
}
pos = end + 1;
}
+ update();
}
void RichTextLabel::_add_item(Item *p_item, bool p_enter, bool p_ensure_newline) {
@@ -2297,7 +2696,7 @@ void RichTextLabel::_add_item(Item *p_item, bool p_enter, bool p_ensure_newline)
p_item->index = current_idx++;
p_item->char_ofs = current_char_ofs;
if (p_item->type == ITEM_TEXT) {
- ItemText *t = (ItemText *)p_item;
+ ItemText *t = static_cast<ItemText *>(p_item);
current_char_ofs += t->text.length();
} else if (p_item->type == ITEM_IMAGE) {
current_char_ofs++;
@@ -2317,7 +2716,7 @@ void RichTextLabel::_add_item(Item *p_item, bool p_enter, bool p_ensure_newline)
}
if (current_frame->lines[current_frame->lines.size() - 1].from == nullptr) {
- current_frame->lines.write[current_frame->lines.size() - 1].from = p_item;
+ current_frame->lines[current_frame->lines.size() - 1].from = p_item;
}
p_item->line = current_frame->lines.size() - 1;
@@ -2326,6 +2725,7 @@ void RichTextLabel::_add_item(Item *p_item, bool p_enter, bool p_ensure_newline)
if (fixed_width != -1) {
update_minimum_size();
}
+ update();
}
void RichTextLabel::_remove_item(Item *p_item, const int p_line, const int p_subitem_line) {
@@ -2349,9 +2749,13 @@ 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, InlineAlignment p_alignment) {
+ _stop_thread();
+ MutexLock data_lock(data_mutex);
+
if (current->type == ITEM_TABLE) {
return;
}
@@ -2391,6 +2795,9 @@ void RichTextLabel::add_image(const Ref<Texture2D> &p_image, const int p_width,
}
void RichTextLabel::add_newline() {
+ _stop_thread();
+ MutexLock data_lock(data_mutex);
+
if (current->type == ITEM_TABLE) {
return;
}
@@ -2399,10 +2806,14 @@ void RichTextLabel::add_newline() {
_add_item(item, false);
current_frame->lines.resize(current_frame->lines.size() + 1);
_invalidate_current_line(current_frame);
+ update();
}
bool RichTextLabel::remove_line(const int p_line) {
- if (p_line >= current_frame->lines.size() || p_line < 0) {
+ _stop_thread();
+ MutexLock data_lock(data_mutex);
+
+ if (p_line >= (int)current_frame->lines.size() || p_line < 0) {
return false;
}
@@ -2430,16 +2841,19 @@ bool RichTextLabel::remove_line(const int p_line) {
}
if (p_line == 0 && current->subitems.size() > 0) {
- main->lines.write[0].from = main;
+ main->lines[0].from = main;
}
- main->first_invalid_line = 0; // p_line ???
+ main->first_invalid_line.store(0);
update();
return true;
}
void RichTextLabel::push_dropcap(const String &p_string, const Ref<Font> &p_font, int p_size, const Rect2 &p_dropcap_margins, const Color &p_color, int p_ol_size, const Color &p_ol_color) {
+ _stop_thread();
+ MutexLock data_lock(data_mutex);
+
ERR_FAIL_COND(current->type == ITEM_TABLE);
ERR_FAIL_COND(p_string.is_empty());
ERR_FAIL_COND(p_font.is_null());
@@ -2458,6 +2872,9 @@ void RichTextLabel::push_dropcap(const String &p_string, const Ref<Font> &p_font
}
void RichTextLabel::push_font(const Ref<Font> &p_font) {
+ _stop_thread();
+ MutexLock data_lock(data_mutex);
+
ERR_FAIL_COND(current->type == ITEM_TABLE);
ERR_FAIL_COND(p_font.is_null());
ItemFont *item = memnew(ItemFont);
@@ -2502,6 +2919,9 @@ void RichTextLabel::push_mono() {
}
void RichTextLabel::push_font_size(int p_font_size) {
+ _stop_thread();
+ MutexLock data_lock(data_mutex);
+
ERR_FAIL_COND(current->type == ITEM_TABLE);
ItemFontSize *item = memnew(ItemFontSize);
@@ -2510,6 +2930,9 @@ void RichTextLabel::push_font_size(int p_font_size) {
}
void RichTextLabel::push_font_features(const Dictionary &p_features) {
+ _stop_thread();
+ MutexLock data_lock(data_mutex);
+
ERR_FAIL_COND(current->type == ITEM_TABLE);
ItemFontFeatures *item = memnew(ItemFontFeatures);
@@ -2518,6 +2941,9 @@ void RichTextLabel::push_font_features(const Dictionary &p_features) {
}
void RichTextLabel::push_outline_size(int p_font_size) {
+ _stop_thread();
+ MutexLock data_lock(data_mutex);
+
ERR_FAIL_COND(current->type == ITEM_TABLE);
ItemOutlineSize *item = memnew(ItemOutlineSize);
@@ -2526,6 +2952,9 @@ void RichTextLabel::push_outline_size(int p_font_size) {
}
void RichTextLabel::push_color(const Color &p_color) {
+ _stop_thread();
+ MutexLock data_lock(data_mutex);
+
ERR_FAIL_COND(current->type == ITEM_TABLE);
ItemColor *item = memnew(ItemColor);
@@ -2534,6 +2963,9 @@ void RichTextLabel::push_color(const Color &p_color) {
}
void RichTextLabel::push_outline_color(const Color &p_color) {
+ _stop_thread();
+ MutexLock data_lock(data_mutex);
+
ERR_FAIL_COND(current->type == ITEM_TABLE);
ItemOutlineColor *item = memnew(ItemOutlineColor);
@@ -2542,6 +2974,9 @@ void RichTextLabel::push_outline_color(const Color &p_color) {
}
void RichTextLabel::push_underline() {
+ _stop_thread();
+ MutexLock data_lock(data_mutex);
+
ERR_FAIL_COND(current->type == ITEM_TABLE);
ItemUnderline *item = memnew(ItemUnderline);
@@ -2549,13 +2984,19 @@ void RichTextLabel::push_underline() {
}
void RichTextLabel::push_strikethrough() {
+ _stop_thread();
+ MutexLock data_lock(data_mutex);
+
ERR_FAIL_COND(current->type == ITEM_TABLE);
ItemStrikethrough *item = memnew(ItemStrikethrough);
_add_item(item, true);
}
-void RichTextLabel::push_paragraph(HorizontalAlignment p_alignment, 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, TextServer::StructuredTextParser p_st_parser) {
+ _stop_thread();
+ MutexLock data_lock(data_mutex);
+
ERR_FAIL_COND(current->type == ITEM_TABLE);
ItemParagraph *item = memnew(ItemParagraph);
@@ -2567,6 +3008,9 @@ void RichTextLabel::push_paragraph(HorizontalAlignment p_alignment, Control::Tex
}
void RichTextLabel::push_indent(int p_level) {
+ _stop_thread();
+ MutexLock data_lock(data_mutex);
+
ERR_FAIL_COND(current->type == ITEM_TABLE);
ERR_FAIL_COND(p_level < 0);
@@ -2576,6 +3020,9 @@ void RichTextLabel::push_indent(int p_level) {
}
void RichTextLabel::push_list(int p_level, ListType p_list, bool p_capitalize) {
+ _stop_thread();
+ MutexLock data_lock(data_mutex);
+
ERR_FAIL_COND(current->type == ITEM_TABLE);
ERR_FAIL_COND(p_level < 0);
@@ -2588,6 +3035,9 @@ void RichTextLabel::push_list(int p_level, ListType p_list, bool p_capitalize) {
}
void RichTextLabel::push_meta(const Variant &p_meta) {
+ _stop_thread();
+ MutexLock data_lock(data_mutex);
+
ERR_FAIL_COND(current->type == ITEM_TABLE);
ItemMeta *item = memnew(ItemMeta);
@@ -2595,21 +3045,38 @@ void RichTextLabel::push_meta(const Variant &p_meta) {
_add_item(item, true);
}
+void RichTextLabel::push_hint(const String &p_string) {
+ _stop_thread();
+ MutexLock data_lock(data_mutex);
+
+ ERR_FAIL_COND(current->type == ITEM_TABLE);
+ ItemHint *item = memnew(ItemHint);
+
+ item->description = p_string;
+ _add_item(item, true);
+}
+
void RichTextLabel::push_table(int p_columns, InlineAlignment p_alignment) {
+ _stop_thread();
+ MutexLock data_lock(data_mutex);
+
ERR_FAIL_COND(p_columns < 1);
ItemTable *item = memnew(ItemTable);
item->columns.resize(p_columns);
item->total_width = 0;
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;
+ for (int i = 0; i < (int)item->columns.size(); i++) {
+ item->columns[i].expand = false;
+ item->columns[i].expand_ratio = 1;
}
_add_item(item, true, false);
}
void RichTextLabel::push_fade(int p_start_index, int p_length) {
+ _stop_thread();
+ MutexLock data_lock(data_mutex);
+
ItemFade *item = memnew(ItemFade);
item->starting_index = p_start_index;
item->length = p_length;
@@ -2617,6 +3084,9 @@ void RichTextLabel::push_fade(int p_start_index, int p_length) {
}
void RichTextLabel::push_shake(int p_strength = 10, float p_rate = 24.0f) {
+ _stop_thread();
+ MutexLock data_lock(data_mutex);
+
ItemShake *item = memnew(ItemShake);
item->strength = p_strength;
item->rate = p_rate;
@@ -2624,6 +3094,9 @@ void RichTextLabel::push_shake(int p_strength = 10, float p_rate = 24.0f) {
}
void RichTextLabel::push_wave(float p_frequency = 1.0f, float p_amplitude = 10.0f) {
+ _stop_thread();
+ MutexLock data_lock(data_mutex);
+
ItemWave *item = memnew(ItemWave);
item->frequency = p_frequency;
item->amplitude = p_amplitude;
@@ -2631,6 +3104,9 @@ void RichTextLabel::push_wave(float p_frequency = 1.0f, float p_amplitude = 10.0
}
void RichTextLabel::push_tornado(float p_frequency = 1.0f, float p_radius = 10.0f) {
+ _stop_thread();
+ MutexLock data_lock(data_mutex);
+
ItemTornado *item = memnew(ItemTornado);
item->frequency = p_frequency;
item->radius = p_radius;
@@ -2638,6 +3114,9 @@ void RichTextLabel::push_tornado(float p_frequency = 1.0f, float p_radius = 10.0
}
void RichTextLabel::push_rainbow(float p_saturation, float p_value, float p_frequency) {
+ _stop_thread();
+ MutexLock data_lock(data_mutex);
+
ItemRainbow *item = memnew(ItemRainbow);
item->frequency = p_frequency;
item->saturation = p_saturation;
@@ -2646,6 +3125,9 @@ void RichTextLabel::push_rainbow(float p_saturation, float p_value, float p_freq
}
void RichTextLabel::push_bgcolor(const Color &p_color) {
+ _stop_thread();
+ MutexLock data_lock(data_mutex);
+
ERR_FAIL_COND(current->type == ITEM_TABLE);
ItemBGColor *item = memnew(ItemBGColor);
@@ -2654,6 +3136,9 @@ void RichTextLabel::push_bgcolor(const Color &p_color) {
}
void RichTextLabel::push_fgcolor(const Color &p_color) {
+ _stop_thread();
+ MutexLock data_lock(data_mutex);
+
ERR_FAIL_COND(current->type == ITEM_TABLE);
ItemFGColor *item = memnew(ItemFGColor);
@@ -2662,6 +3147,9 @@ void RichTextLabel::push_fgcolor(const Color &p_color) {
}
void RichTextLabel::push_customfx(Ref<RichTextEffect> p_custom_effect, Dictionary p_environment) {
+ _stop_thread();
+ MutexLock data_lock(data_mutex);
+
ItemCustomFX *item = memnew(ItemCustomFX);
item->custom_effect = p_custom_effect;
item->char_fx_transform->environment = p_environment;
@@ -2669,15 +3157,23 @@ void RichTextLabel::push_customfx(Ref<RichTextEffect> p_custom_effect, Dictionar
}
void RichTextLabel::set_table_column_expand(int p_column, bool p_expand, int p_ratio) {
+ _stop_thread();
+ MutexLock data_lock(data_mutex);
+
ERR_FAIL_COND(current->type != ITEM_TABLE);
+
ItemTable *table = static_cast<ItemTable *>(current);
- ERR_FAIL_INDEX(p_column, table->columns.size());
- table->columns.write[p_column].expand = p_expand;
- table->columns.write[p_column].expand_ratio = p_ratio;
+ ERR_FAIL_INDEX(p_column, (int)table->columns.size());
+ table->columns[p_column].expand = p_expand;
+ table->columns[p_column].expand_ratio = p_ratio;
}
void RichTextLabel::set_cell_row_background_color(const Color &p_odd_row_bg, const Color &p_even_row_bg) {
+ _stop_thread();
+ MutexLock data_lock(data_mutex);
+
ERR_FAIL_COND(current->type != ITEM_FRAME);
+
ItemFrame *cell = static_cast<ItemFrame *>(current);
ERR_FAIL_COND(!cell->cell);
cell->odd_row_bg = p_odd_row_bg;
@@ -2685,14 +3181,22 @@ void RichTextLabel::set_cell_row_background_color(const Color &p_odd_row_bg, con
}
void RichTextLabel::set_cell_border_color(const Color &p_color) {
+ _stop_thread();
+ MutexLock data_lock(data_mutex);
+
ERR_FAIL_COND(current->type != ITEM_FRAME);
+
ItemFrame *cell = static_cast<ItemFrame *>(current);
ERR_FAIL_COND(!cell->cell);
cell->border = p_color;
}
void RichTextLabel::set_cell_size_override(const Size2 &p_min_size, const Size2 &p_max_size) {
+ _stop_thread();
+ MutexLock data_lock(data_mutex);
+
ERR_FAIL_COND(current->type != ITEM_FRAME);
+
ItemFrame *cell = static_cast<ItemFrame *>(current);
ERR_FAIL_COND(!cell->cell);
cell->min_size_over = p_min_size;
@@ -2700,13 +3204,20 @@ void RichTextLabel::set_cell_size_override(const Size2 &p_min_size, const Size2
}
void RichTextLabel::set_cell_padding(const Rect2 &p_padding) {
+ _stop_thread();
+ MutexLock data_lock(data_mutex);
+
ERR_FAIL_COND(current->type != ITEM_FRAME);
+
ItemFrame *cell = static_cast<ItemFrame *>(current);
ERR_FAIL_COND(!cell->cell);
cell->padding = p_padding;
}
void RichTextLabel::push_cell() {
+ _stop_thread();
+ MutexLock data_lock(data_mutex);
+
ERR_FAIL_COND(current->type != ITEM_TABLE);
ItemFrame *item = memnew(ItemFrame);
@@ -2715,20 +3226,23 @@ void RichTextLabel::push_cell() {
current_frame = item;
item->cell = true;
item->lines.resize(1);
- item->lines.write[0].from = nullptr;
- item->first_invalid_line = 0; // parent frame last line ???
+ item->lines[0].from = nullptr;
+ item->first_invalid_line.store(0); // parent frame last line ???
}
int RichTextLabel::get_current_table_column() const {
ERR_FAIL_COND_V(current->type != ITEM_TABLE, -1);
ItemTable *table = static_cast<ItemTable *>(current);
-
return table->subitems.size() % table->columns.size();
}
void RichTextLabel::pop() {
+ _stop_thread();
+ MutexLock data_lock(data_mutex);
+
ERR_FAIL_COND(!current->parent);
+
if (current->type == ITEM_FRAME) {
current_frame = static_cast<ItemFrame *>(current)->parent_frame;
}
@@ -2736,17 +3250,19 @@ void RichTextLabel::pop() {
}
void RichTextLabel::clear() {
+ _stop_thread();
+ MutexLock data_lock(data_mutex);
+
main->_clear_children();
current = main;
current_frame = main;
main->lines.clear();
main->lines.resize(1);
- main->first_invalid_line = 0;
- update();
+ main->first_invalid_line.store(0);
selection.click_frame = nullptr;
selection.click_item = nullptr;
- selection.active = false;
+ deselect();
current_idx = 1;
current_char_ofs = 0;
@@ -2760,8 +3276,10 @@ void RichTextLabel::clear() {
}
void RichTextLabel::set_tab_size(int p_spaces) {
+ _stop_thread();
+
tab_size = p_spaces;
- main->first_resized_line = 0;
+ main->first_resized_line.store(0);
update();
}
@@ -2789,6 +3307,15 @@ bool RichTextLabel::is_meta_underlined() const {
return underline_meta;
}
+void RichTextLabel::set_hint_underline(bool p_underline) {
+ underline_hint = p_underline;
+ update();
+}
+
+bool RichTextLabel::is_hint_underlined() const {
+ return underline_hint;
+}
+
void RichTextLabel::set_override_selected_font_color(bool p_override_selected_font_color) {
override_selected_font_color = p_override_selected_font_color;
}
@@ -2832,6 +3359,9 @@ void RichTextLabel::parse_bbcode(const String &p_bbcode) {
}
void RichTextLabel::append_text(const String &p_bbcode) {
+ _stop_thread();
+ MutexLock data_lock(data_mutex);
+
int pos = 0;
List<String> tag_stack;
@@ -2847,21 +3377,34 @@ void RichTextLabel::append_text(const String &p_bbcode) {
bool in_bold = false;
bool in_italics = false;
+ bool after_list_open_tag = false;
+ bool after_list_close_tag = false;
set_process_internal(false);
- while (pos < p_bbcode.length()) {
+ while (pos <= p_bbcode.length()) {
int brk_pos = p_bbcode.find("[", pos);
if (brk_pos < 0) {
brk_pos = p_bbcode.length();
}
- if (brk_pos > pos) {
- add_text(p_bbcode.substr(pos, brk_pos - pos));
+ String text = brk_pos > pos ? p_bbcode.substr(pos, brk_pos - pos) : "";
+
+ // Trim the first newline character, it may be added later as needed.
+ if (after_list_close_tag || after_list_open_tag) {
+ text = text.trim_prefix("\n");
}
if (brk_pos == p_bbcode.length()) {
+ // For tags that are not properly closed.
+ if (text.is_empty() && after_list_open_tag) {
+ text = "\n";
+ }
+
+ if (!text.is_empty()) {
+ add_text(text);
+ }
break; //nothing else to add
}
@@ -2869,7 +3412,8 @@ void RichTextLabel::append_text(const String &p_bbcode) {
if (brk_end == -1) {
//no close, add the rest
- add_text(p_bbcode.substr(brk_pos, p_bbcode.length() - brk_pos));
+ text += p_bbcode.substr(brk_pos, p_bbcode.length() - brk_pos);
+ add_text(text);
break;
}
@@ -2878,7 +3422,7 @@ void RichTextLabel::append_text(const String &p_bbcode) {
// Find optional parameters.
String bbcode_name;
- typedef Map<String, String> OptionMap;
+ typedef HashMap<String, String> OptionMap;
OptionMap bbcode_options;
if (!split_tag_block.is_empty()) {
bbcode_name = split_tag_block[0];
@@ -2915,18 +3459,60 @@ void RichTextLabel::append_text(const String &p_bbcode) {
}
if (!tag_ok) {
- add_text("[" + tag);
+ text += "[" + tag;
+ add_text(text);
+ after_list_open_tag = false;
+ after_list_close_tag = false;
pos = brk_end;
continue;
}
+ if (text.is_empty() && after_list_open_tag) {
+ text = "\n"; // Make empty list have at least one item.
+ }
+ after_list_open_tag = false;
+
+ if (tag == "/ol" || tag == "/ul") {
+ if (!text.is_empty()) {
+ // Make sure text ends with a newline character, that is, the last item
+ // will wrap at the end of block.
+ if (!text.ends_with("\n")) {
+ text += "\n";
+ }
+ } else if (!after_list_close_tag) {
+ text = "\n"; // Make the innermost list item wrap at the end of lists.
+ }
+ after_list_close_tag = true;
+ } else {
+ after_list_close_tag = false;
+ }
+
+ if (!text.is_empty()) {
+ add_text(text);
+ }
+
tag_stack.pop_front();
pos = brk_end + 1;
if (tag != "/img" && tag != "/dropcap") {
pop();
}
+ continue;
+ }
- } else if (tag == "b") {
+ if (tag == "ol" || tag.begins_with("ol ") || tag == "ul" || tag.begins_with("ul ")) {
+ if (text.is_empty() && after_list_open_tag) {
+ text = "\n"; // Make each list have at least one item at the beginning.
+ }
+ after_list_open_tag = true;
+ } else {
+ after_list_open_tag = false;
+ }
+ if (!text.is_empty()) {
+ add_text(text);
+ }
+ after_list_close_tag = false;
+
+ if (tag == "b") {
//use bold font
in_bold = true;
if (in_italics) {
@@ -3050,6 +3636,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;
@@ -3106,6 +3698,10 @@ void RichTextLabel::append_text(const String &p_bbcode) {
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(HORIZONTAL_ALIGNMENT_RIGHT);
pos = brk_end + 1;
@@ -3119,7 +3715,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);
@@ -3154,7 +3750,7 @@ void RichTextLabel::append_text(const String &p_bbcode) {
HorizontalAlignment alignment = HORIZONTAL_ALIGNMENT_LEFT;
Control::TextDirection dir = Control::TEXT_DIRECTION_INHERITED;
String lang;
- Control::StructuredTextParser st_parser = STRUCTURED_TEXT_DEFAULT;
+ TextServer::StructuredTextParser st_parser = TextServer::STRUCTURED_TEXT_DEFAULT;
for (int i = 0; i < subtag.size(); i++) {
Vector<String> subtag_a = subtag[i].split("=");
if (subtag_a.size() == 2) {
@@ -3180,19 +3776,19 @@ void RichTextLabel::append_text(const String &p_bbcode) {
lang = subtag_a[1];
} else if (subtag_a[0] == "st" || subtag_a[0] == "bidi_override") {
if (subtag_a[1] == "d" || subtag_a[1] == "default") {
- st_parser = STRUCTURED_TEXT_DEFAULT;
+ st_parser = TextServer::STRUCTURED_TEXT_DEFAULT;
} else if (subtag_a[1] == "u" || subtag_a[1] == "uri") {
- st_parser = STRUCTURED_TEXT_URI;
+ st_parser = TextServer::STRUCTURED_TEXT_URI;
} else if (subtag_a[1] == "f" || subtag_a[1] == "file") {
- st_parser = STRUCTURED_TEXT_FILE;
+ st_parser = TextServer::STRUCTURED_TEXT_FILE;
} else if (subtag_a[1] == "e" || subtag_a[1] == "email") {
- st_parser = STRUCTURED_TEXT_EMAIL;
+ st_parser = TextServer::STRUCTURED_TEXT_EMAIL;
} else if (subtag_a[1] == "l" || subtag_a[1] == "list") {
- st_parser = STRUCTURED_TEXT_LIST;
+ st_parser = TextServer::STRUCTURED_TEXT_LIST;
} else if (subtag_a[1] == "n" || subtag_a[1] == "none") {
- st_parser = STRUCTURED_TEXT_NONE;
+ st_parser = TextServer::STRUCTURED_TEXT_NONE;
} else if (subtag_a[1] == "c" || subtag_a[1] == "custom") {
- st_parser = STRUCTURED_TEXT_CUSTOM;
+ st_parser = TextServer::STRUCTURED_TEXT_CUSTOM;
}
}
}
@@ -3216,6 +3812,11 @@ void RichTextLabel::append_text(const String &p_bbcode) {
push_meta(url);
pos = brk_end + 1;
tag_stack.push_front("url");
+ } else if (tag.begins_with("hint=")) {
+ String description = tag.substr(5, tag.length());
+ push_hint(description);
+ pos = brk_end + 1;
+ tag_stack.push_front("hint");
} else if (tag.begins_with("dropcap")) {
Vector<String> subtag = tag.substr(5, tag.length()).split(" ");
Ref<Font> f = get_theme_font(SNAME("normal_font"));
@@ -3306,9 +3907,9 @@ void RichTextLabel::append_text(const String &p_bbcode) {
Ref<Texture2D> texture = ResourceLoader::load(image, "Texture2D");
if (texture.is_valid()) {
Color color = Color(1.0, 1.0, 1.0);
- OptionMap::Element *color_option = bbcode_options.find("color");
+ OptionMap::Iterator color_option = bbcode_options.find("color");
if (color_option) {
- color = Color::from_string(color_option->value(), color);
+ color = Color::from_string(color_option->value, color);
}
int width = 0;
@@ -3322,14 +3923,14 @@ void RichTextLabel::append_text(const String &p_bbcode) {
height = bbcode_value.substr(sep + 1).to_int();
}
} else {
- OptionMap::Element *width_option = bbcode_options.find("width");
+ OptionMap::Iterator width_option = bbcode_options.find("width");
if (width_option) {
- width = width_option->value().to_int();
+ width = width_option->value.to_int();
}
- OptionMap::Element *height_option = bbcode_options.find("height");
+ OptionMap::Iterator height_option = bbcode_options.find("height");
if (height_option) {
- height = height_option->value().to_int();
+ height = height_option->value.to_int();
}
}
@@ -3415,15 +4016,15 @@ void RichTextLabel::append_text(const String &p_bbcode) {
} else if (bbcode_name == "fade") {
int start_index = 0;
- OptionMap::Element *start_option = bbcode_options.find("start");
+ OptionMap::Iterator start_option = bbcode_options.find("start");
if (start_option) {
- start_index = start_option->value().to_int();
+ start_index = start_option->value.to_int();
}
int length = 10;
- OptionMap::Element *length_option = bbcode_options.find("length");
+ OptionMap::Iterator length_option = bbcode_options.find("length");
if (length_option) {
- length = length_option->value().to_int();
+ length = length_option->value.to_int();
}
push_fade(start_index, length);
@@ -3431,15 +4032,15 @@ void RichTextLabel::append_text(const String &p_bbcode) {
tag_stack.push_front("fade");
} else if (bbcode_name == "shake") {
int strength = 5;
- OptionMap::Element *strength_option = bbcode_options.find("level");
+ OptionMap::Iterator strength_option = bbcode_options.find("level");
if (strength_option) {
- strength = strength_option->value().to_int();
+ strength = strength_option->value.to_int();
}
float rate = 20.0f;
- OptionMap::Element *rate_option = bbcode_options.find("rate");
+ OptionMap::Iterator rate_option = bbcode_options.find("rate");
if (rate_option) {
- rate = rate_option->value().to_float();
+ rate = rate_option->value.to_float();
}
push_shake(strength, rate);
@@ -3448,15 +4049,15 @@ void RichTextLabel::append_text(const String &p_bbcode) {
set_process_internal(true);
} else if (bbcode_name == "wave") {
float amplitude = 20.0f;
- OptionMap::Element *amplitude_option = bbcode_options.find("amp");
+ OptionMap::Iterator amplitude_option = bbcode_options.find("amp");
if (amplitude_option) {
- amplitude = amplitude_option->value().to_float();
+ amplitude = amplitude_option->value.to_float();
}
float period = 5.0f;
- OptionMap::Element *period_option = bbcode_options.find("freq");
+ OptionMap::Iterator period_option = bbcode_options.find("freq");
if (period_option) {
- period = period_option->value().to_float();
+ period = period_option->value.to_float();
}
push_wave(period, amplitude);
@@ -3465,15 +4066,15 @@ void RichTextLabel::append_text(const String &p_bbcode) {
set_process_internal(true);
} else if (bbcode_name == "tornado") {
float radius = 10.0f;
- OptionMap::Element *radius_option = bbcode_options.find("radius");
+ OptionMap::Iterator radius_option = bbcode_options.find("radius");
if (radius_option) {
- radius = radius_option->value().to_float();
+ radius = radius_option->value.to_float();
}
float frequency = 1.0f;
- OptionMap::Element *frequency_option = bbcode_options.find("freq");
+ OptionMap::Iterator frequency_option = bbcode_options.find("freq");
if (frequency_option) {
- frequency = frequency_option->value().to_float();
+ frequency = frequency_option->value.to_float();
}
push_tornado(frequency, radius);
@@ -3482,21 +4083,21 @@ void RichTextLabel::append_text(const String &p_bbcode) {
set_process_internal(true);
} else if (bbcode_name == "rainbow") {
float saturation = 0.8f;
- OptionMap::Element *saturation_option = bbcode_options.find("sat");
+ OptionMap::Iterator saturation_option = bbcode_options.find("sat");
if (saturation_option) {
- saturation = saturation_option->value().to_float();
+ saturation = saturation_option->value.to_float();
}
float value = 0.8f;
- OptionMap::Element *value_option = bbcode_options.find("val");
+ OptionMap::Iterator value_option = bbcode_options.find("val");
if (value_option) {
- value = value_option->value().to_float();
+ value = value_option->value.to_float();
}
float frequency = 1.0f;
- OptionMap::Element *frequency_option = bbcode_options.find("freq");
+ OptionMap::Iterator frequency_option = bbcode_options.find("freq");
if (frequency_option) {
- frequency = frequency_option->value().to_float();
+ frequency = frequency_option->value.to_float();
}
push_rainbow(saturation, value, frequency);
@@ -3557,9 +4158,13 @@ void RichTextLabel::append_text(const String &p_bbcode) {
}
void RichTextLabel::scroll_to_paragraph(int p_paragraph) {
- ERR_FAIL_INDEX(p_paragraph, main->lines.size());
- _validate_line_caches(main);
- vscroll->set_value(main->lines[p_paragraph].offset.y);
+ if (p_paragraph <= 0) {
+ vscroll->set_value(0);
+ } else if (p_paragraph >= main->first_invalid_line.load()) {
+ vscroll->set_value(vscroll->get_max());
+ } else {
+ vscroll->set_value(main->lines[p_paragraph].offset.y);
+ }
}
int RichTextLabel::get_paragraph_count() const {
@@ -3574,25 +4179,57 @@ int RichTextLabel::get_visible_paragraph_count() const {
}
void RichTextLabel::scroll_to_line(int p_line) {
- _validate_line_caches(main);
-
+ if (p_line <= 0) {
+ vscroll->set_value(0);
+ return;
+ }
int line_count = 0;
- for (int i = 0; i < main->lines.size(); i++) {
+ int to_line = main->first_invalid_line.load();
+ for (int i = 0; i < to_line; i++) {
+ MutexLock lock(main->lines[i].text_buf->get_mutex());
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;
}
line_count += main->lines[i].text_buf->get_line_count();
}
+ vscroll->set_value(vscroll->get_max());
+}
+
+float RichTextLabel::get_line_offset(int p_line) {
+ int line_count = 0;
+ int to_line = main->first_invalid_line.load();
+ for (int i = 0; i < to_line; i++) {
+ MutexLock lock(main->lines[i].text_buf->get_mutex());
+ 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) {
+ int to_line = main->first_invalid_line.load();
+ if (0 <= p_paragraph && p_paragraph < to_line) {
+ 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++) {
+ int to_line = main->first_invalid_line.load();
+ for (int i = 0; i < to_line; i++) {
+ MutexLock lock(main->lines[i].text_buf->get_mutex());
line_count += main->lines[i].text_buf->get_line_count();
}
return line_count;
@@ -3609,8 +4246,7 @@ void RichTextLabel::set_selection_enabled(bool p_enabled) {
selection.enabled = p_enabled;
if (!p_enabled) {
if (selection.active) {
- selection.active = false;
- update();
+ deselect();
}
set_focus_mode(FOCUS_NONE);
} else {
@@ -3621,8 +4257,30 @@ void RichTextLabel::set_selection_enabled(bool p_enabled) {
void RichTextLabel::set_deselect_on_focus_loss_enabled(const bool p_enabled) {
deselect_on_focus_loss_enabled = p_enabled;
if (p_enabled && selection.active && !has_focus()) {
- selection.active = false;
- update();
+ deselect();
+ }
+}
+
+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;
}
}
@@ -3632,13 +4290,13 @@ bool RichTextLabel::_search_table(ItemTable *p_table, List<Item *>::Element *p_f
ERR_CONTINUE(E->get()->type != ITEM_FRAME); // Children should all be frames.
ItemFrame *frame = static_cast<ItemFrame *>(E->get());
if (p_reverse_search) {
- for (int i = frame->lines.size() - 1; i >= 0; i--) {
+ for (int i = (int)frame->lines.size() - 1; i >= 0; i--) {
if (_search_line(frame, i, p_string, -1, p_reverse_search)) {
return true;
}
}
} else {
- for (int i = 0; i < frame->lines.size(); i++) {
+ for (int i = 0; i < (int)frame->lines.size(); i++) {
if (_search_line(frame, i, p_string, 0, p_reverse_search)) {
return true;
}
@@ -3651,19 +4309,19 @@ bool RichTextLabel::_search_table(ItemTable *p_table, List<Item *>::Element *p_f
bool RichTextLabel::_search_line(ItemFrame *p_frame, int p_line, const String &p_string, int p_char_idx, bool p_reverse_search) {
ERR_FAIL_COND_V(p_frame == nullptr, false);
- ERR_FAIL_COND_V(p_line < 0 || p_line >= p_frame->lines.size(), false);
+ ERR_FAIL_COND_V(p_line < 0 || p_line >= (int)p_frame->lines.size(), false);
- Line &l = p_frame->lines.write[p_line];
+ Line &l = p_frame->lines[p_line];
String text;
- Item *it_to = (p_line + 1 < p_frame->lines.size()) ? p_frame->lines[p_line + 1].from : nullptr;
+ Item *it_to = (p_line + 1 < (int)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_NEWLINE: {
text += "\n";
} break;
case ITEM_TEXT: {
- ItemText *t = (ItemText *)it;
+ ItemText *t = static_cast<ItemText *>(it);
text += t->text;
} break;
case ITEM_IMAGE: {
@@ -3714,7 +4372,8 @@ bool RichTextLabel::search(const String &p_string, bool p_from_selection, bool p
int char_idx = p_search_previous ? -1 : 0;
int current_line = 0;
- int ending_line = main->lines.size() - 1;
+ int to_line = main->first_invalid_line.load();
+ int ending_line = to_line - 1;
if (p_from_selection && selection.active) {
// First check to see if other results exist in current line
char_idx = p_search_previous ? selection.from_char - 1 : selection.to_char;
@@ -3763,8 +4422,8 @@ bool RichTextLabel::search(const String &p_string, bool p_from_selection, bool p
while (current_line != ending_line) {
// Wrap around
if (current_line < 0) {
- current_line = main->lines.size() - 1;
- } else if (current_line >= main->lines.size()) {
+ current_line = to_line - 1;
+ } else if (current_line >= to_line) {
current_line = 0;
}
@@ -3786,17 +4445,18 @@ bool RichTextLabel::search(const String &p_string, bool p_from_selection, bool p
String RichTextLabel::_get_line_text(ItemFrame *p_frame, int p_line, Selection p_selection) const {
String text;
+
ERR_FAIL_COND_V(p_frame == nullptr, text);
- ERR_FAIL_COND_V(p_line < 0 || p_line >= p_frame->lines.size(), text);
+ ERR_FAIL_COND_V(p_line < 0 || p_line >= (int)p_frame->lines.size(), text);
- Line &l = p_frame->lines.write[p_line];
+ Line &l = p_frame->lines[p_line];
- Item *it_to = (p_line + 1 < p_frame->lines.size()) ? p_frame->lines[p_line + 1].from : nullptr;
+ Item *it_to = (p_line + 1 < (int)p_frame->lines.size()) ? p_frame->lines[p_line + 1].from : nullptr;
int end_idx = 0;
if (it_to != nullptr) {
end_idx = it_to->index;
} else {
- for (Item *it = l.from; it && it != it_to; it = _get_next_item(it)) {
+ for (Item *it = l.from; it; it = _get_next_item(it)) {
end_idx = it->index + 1;
}
}
@@ -3806,7 +4466,7 @@ String RichTextLabel::_get_line_text(ItemFrame *p_frame, int p_line, Selection p
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++) {
+ for (int i = 0; i < (int)frame->lines.size(); i++) {
text += _get_line_text(frame, i, p_selection);
}
}
@@ -3838,18 +4498,50 @@ String RichTextLabel::_get_line_text(ItemFrame *p_frame, int p_line, Selection p
return text;
}
+void RichTextLabel::set_context_menu_enabled(bool p_enabled) {
+ context_menu_enabled = p_enabled;
+}
+
+bool RichTextLabel::is_context_menu_enabled() const {
+ return context_menu_enabled;
+}
+
+void RichTextLabel::set_shortcut_keys_enabled(bool p_enabled) {
+ shortcut_keys_enabled = p_enabled;
+}
+
+bool RichTextLabel::is_shortcut_keys_enabled() const {
+ return shortcut_keys_enabled;
+}
+
+// Context menu.
+PopupMenu *RichTextLabel::get_menu() const {
+ const_cast<RichTextLabel *>(this)->_generate_context_menu();
+ return menu;
+}
+
+bool RichTextLabel::is_menu_visible() const {
+ return menu && menu->is_visible();
+}
+
String RichTextLabel::get_selected_text() const {
if (!selection.active || !selection.enabled) {
return "";
}
String text;
- for (int i = 0; i < main->lines.size(); i++) {
+ int to_line = main->first_invalid_line.load();
+ for (int i = 0; i < to_line; i++) {
text += _get_line_text(main, i, selection);
}
return text;
}
+void RichTextLabel::deselect() {
+ selection.active = false;
+ update();
+}
+
void RichTextLabel::selection_copy() {
String text = get_selected_text();
@@ -3858,6 +4550,53 @@ void RichTextLabel::selection_copy() {
}
}
+void RichTextLabel::select_all() {
+ if (!selection.enabled) {
+ return;
+ }
+
+ Item *it = main;
+ Item *from_item = nullptr;
+ Item *to_item = nullptr;
+
+ while (it) {
+ if (it->type != ITEM_FRAME) {
+ if (!from_item) {
+ from_item = it;
+ } else {
+ to_item = it;
+ }
+ }
+ it = _get_next_item(it, true);
+ }
+ if (!from_item || !to_item) {
+ return;
+ }
+
+ ItemFrame *from_frame = nullptr;
+ int from_line = 0;
+ _find_frame(from_item, &from_frame, &from_line);
+ if (!from_frame) {
+ return;
+ }
+ ItemFrame *to_frame = nullptr;
+ int to_line = 0;
+ _find_frame(to_item, &to_frame, &to_line);
+ if (!to_frame) {
+ return;
+ }
+ selection.from_line = from_line;
+ selection.from_frame = from_frame;
+ selection.from_char = 0;
+ selection.from_item = from_item;
+ selection.to_line = to_line;
+ selection.to_frame = to_frame;
+ selection.to_char = to_frame->lines[to_line].char_count;
+ selection.to_item = to_item;
+ selection.active = true;
+ update();
+}
+
bool RichTextLabel::is_selection_enabled() const {
return selection.enabled;
}
@@ -3884,7 +4623,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();
@@ -3914,10 +4653,8 @@ String RichTextLabel::get_parsed_text() const {
Item *it = main;
while (it) {
if (it->type == ITEM_DROPCAP) {
- const ItemDropcap *dc = (ItemDropcap *)it;
- if (dc != nullptr) {
- text += dc->text;
- }
+ ItemDropcap *dc = static_cast<ItemDropcap *>(it);
+ text += dc->text;
} else if (it->type == ITEM_TEXT) {
ItemText *t = static_cast<ItemText *>(it);
text += t->text;
@@ -3935,32 +4672,40 @@ String RichTextLabel::get_parsed_text() const {
void RichTextLabel::set_text_direction(Control::TextDirection p_text_direction) {
ERR_FAIL_COND((int)p_text_direction < -1 || (int)p_text_direction > 3);
+ _stop_thread();
+
if (text_direction != p_text_direction) {
text_direction = p_text_direction;
- main->first_invalid_line = 0; //invalidate ALL
- _validate_line_caches(main);
+ main->first_invalid_line.store(0); //invalidate ALL
+ _validate_line_caches();
update();
}
}
-void RichTextLabel::set_structured_text_bidi_override(Control::StructuredTextParser p_parser) {
+void RichTextLabel::set_structured_text_bidi_override(TextServer::StructuredTextParser p_parser) {
if (st_parser != p_parser) {
+ _stop_thread();
+
st_parser = p_parser;
- main->first_invalid_line = 0; //invalidate ALL
- _validate_line_caches(main);
+ main->first_invalid_line.store(0); //invalidate ALL
+ _validate_line_caches();
update();
}
}
-Control::StructuredTextParser RichTextLabel::get_structured_text_bidi_override() const {
+TextServer::StructuredTextParser RichTextLabel::get_structured_text_bidi_override() const {
return st_parser;
}
void RichTextLabel::set_structured_text_bidi_override_options(Array p_args) {
- st_args = p_args;
- main->first_invalid_line = 0; //invalidate ALL
- _validate_line_caches(main);
- update();
+ if (st_args != p_args) {
+ _stop_thread();
+
+ st_args = p_args;
+ main->first_invalid_line.store(0); //invalidate ALL
+ _validate_line_caches();
+ update();
+ }
}
Array RichTextLabel::get_structured_text_bidi_override_options() const {
@@ -3973,9 +4718,11 @@ Control::TextDirection RichTextLabel::get_text_direction() const {
void RichTextLabel::set_language(const String &p_language) {
if (language != p_language) {
+ _stop_thread();
+
language = p_language;
- main->first_invalid_line = 0; //invalidate ALL
- _validate_line_caches(main);
+ main->first_invalid_line.store(0); //invalidate ALL
+ _validate_line_caches();
update();
}
}
@@ -3984,8 +4731,25 @@ String RichTextLabel::get_language() const {
return language;
}
+void RichTextLabel::set_autowrap_mode(RichTextLabel::AutowrapMode p_mode) {
+ if (autowrap_mode != p_mode) {
+ _stop_thread();
+
+ autowrap_mode = p_mode;
+ main->first_invalid_line = 0; //invalidate ALL
+ _validate_line_caches();
+ update();
+ }
+}
+
+RichTextLabel::AutowrapMode RichTextLabel::get_autowrap_mode() const {
+ return autowrap_mode;
+}
+
void RichTextLabel::set_percent_visible(float p_percent) {
if (percent_visible != p_percent) {
+ _stop_thread();
+
if (p_percent < 0 || p_percent >= 1) {
visible_characters = -1;
percent_visible = 1;
@@ -3993,8 +4757,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.store(0); //invalidate ALL
+ _validate_line_caches();
+ }
update();
}
}
@@ -4028,15 +4794,27 @@ 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;
+ int to_line = main->first_invalid_line.load();
+ if (to_line) {
+ MutexLock lock(main->lines[to_line - 1].text_buf->get_mutex());
+ total_height = main->lines[to_line - 1].offset.y + main->lines[to_line - 1].text_buf->get_size().y + main->lines[to_line - 1].text_buf->get_line_count() * get_theme_constant(SNAME("line_separation"));
}
return total_height;
}
+int RichTextLabel::get_content_width() const {
+ int total_width = 0;
+ int to_line = main->first_invalid_line.load();
+ for (int i = 0; i < to_line; i++) {
+ MutexLock lock(main->lines[i].text_buf->get_mutex());
+ 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);
@@ -4064,10 +4842,11 @@ 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", "alignment", "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(TextServer::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_hint", "description"), &RichTextLabel::push_hint);
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_ALIGNMENT_TOP));
@@ -4093,9 +4872,15 @@ 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);
+ ClassDB::bind_method(D_METHOD("set_hint_underline", "enable"), &RichTextLabel::set_hint_underline);
+ ClassDB::bind_method(D_METHOD("is_hint_underlined"), &RichTextLabel::is_hint_underlined);
+
ClassDB::bind_method(D_METHOD("set_override_selected_font_color", "override"), &RichTextLabel::set_override_selected_font_color);
ClassDB::bind_method(D_METHOD("is_overriding_selected_font_color"), &RichTextLabel::is_overriding_selected_font_color);
@@ -4105,7 +4890,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);
@@ -4119,25 +4904,46 @@ void RichTextLabel::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_selection_enabled", "enabled"), &RichTextLabel::set_selection_enabled);
ClassDB::bind_method(D_METHOD("is_selection_enabled"), &RichTextLabel::is_selection_enabled);
+ ClassDB::bind_method(D_METHOD("set_context_menu_enabled", "enabled"), &RichTextLabel::set_context_menu_enabled);
+ ClassDB::bind_method(D_METHOD("is_context_menu_enabled"), &RichTextLabel::is_context_menu_enabled);
+
+ ClassDB::bind_method(D_METHOD("set_shortcut_keys_enabled", "enabled"), &RichTextLabel::set_shortcut_keys_enabled);
+ ClassDB::bind_method(D_METHOD("is_shortcut_keys_enabled"), &RichTextLabel::is_shortcut_keys_enabled);
+
ClassDB::bind_method(D_METHOD("set_deselect_on_focus_loss_enabled", "enable"), &RichTextLabel::set_deselect_on_focus_loss_enabled);
ClassDB::bind_method(D_METHOD("is_deselect_on_focus_loss_enabled"), &RichTextLabel::is_deselect_on_focus_loss_enabled);
ClassDB::bind_method(D_METHOD("get_selection_from"), &RichTextLabel::get_selection_from);
ClassDB::bind_method(D_METHOD("get_selection_to"), &RichTextLabel::get_selection_to);
+ ClassDB::bind_method(D_METHOD("select_all"), &RichTextLabel::select_all);
ClassDB::bind_method(D_METHOD("get_selected_text"), &RichTextLabel::get_selected_text);
+ ClassDB::bind_method(D_METHOD("deselect"), &RichTextLabel::deselect);
ClassDB::bind_method(D_METHOD("parse_bbcode", "bbcode"), &RichTextLabel::parse_bbcode);
ClassDB::bind_method(D_METHOD("append_text", "bbcode"), &RichTextLabel::append_text);
ClassDB::bind_method(D_METHOD("get_text"), &RichTextLabel::get_text);
+ ClassDB::bind_method(D_METHOD("is_ready"), &RichTextLabel::is_ready);
+
+ ClassDB::bind_method(D_METHOD("set_threaded", "threaded"), &RichTextLabel::set_threaded);
+ ClassDB::bind_method(D_METHOD("is_threaded"), &RichTextLabel::is_threaded);
+
+ ClassDB::bind_method(D_METHOD("set_progress_bar_delay", "delay_ms"), &RichTextLabel::set_progress_bar_delay);
+ ClassDB::bind_method(D_METHOD("get_progress_bar_delay"), &RichTextLabel::get_progress_bar_delay);
+
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);
@@ -4150,6 +4956,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);
@@ -4157,28 +4967,39 @@ 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");
+ ClassDB::bind_method(D_METHOD("get_menu"), &RichTextLabel::get_menu);
+ ClassDB::bind_method(D_METHOD("is_menu_visible"), &RichTextLabel::is_menu_visible);
- 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");
+ // Note: set "bbcode_enabled" first, to avoid unnecessary "text" resets.
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "bbcode_enabled"), "set_use_bbcode", "is_using_bbcode");
- ADD_PROPERTY(PropertyInfo(Variant::BOOL, "fit_content_height"), "set_fit_content_height", "is_fit_content_height_enabled");
+ ADD_PROPERTY(PropertyInfo(Variant::BOOL, "threaded"), "set_threaded", "is_threaded");
+ ADD_PROPERTY(PropertyInfo(Variant::INT, "progress_bar_delay"), "set_progress_bar_delay", "get_progress_bar_delay");
+ ADD_PROPERTY(PropertyInfo(Variant::INT, "tab_size", PROPERTY_HINT_RANGE, "0,24,1"), "set_tab_size", "get_tab_size");
+ ADD_PROPERTY(PropertyInfo(Variant::STRING, "text", PROPERTY_HINT_MULTILINE_TEXT), "set_text", "get_text");
+ ADD_PROPERTY(PropertyInfo(Variant::BOOL, "fit_content_height"), "set_fit_content_height", "is_fit_content_height_enabled");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "scroll_active"), "set_scroll_active", "is_scroll_active");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "scroll_following"), "set_scroll_follow", "is_scroll_following");
-
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "selection_enabled"), "set_selection_enabled", "is_selection_enabled");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "override_selected_font_color"), "set_override_selected_font_color", "is_overriding_selected_font_color");
-
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "deselect_on_focus_loss_enabled"), "set_deselect_on_focus_loss_enabled", "is_deselect_on_focus_loss_enabled");
-
ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "custom_effects", PROPERTY_HINT_ARRAY_TYPE, vformat("%s/%s:%s", Variant::OBJECT, PROPERTY_HINT_RESOURCE_TYPE, "RichTextEffect"), (PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_SCRIPT_VARIABLE)), "set_effects", "get_effects");
+ ADD_PROPERTY(PropertyInfo(Variant::BOOL, "meta_underlined"), "set_meta_underline", "is_meta_underlined");
+ ADD_PROPERTY(PropertyInfo(Variant::BOOL, "hint_underlined"), "set_hint_underline", "is_hint_underlined");
+ ADD_PROPERTY(PropertyInfo(Variant::INT, "autowrap_mode", PROPERTY_HINT_ENUM, "Off,Arbitrary,Word,Word (Smart)"), "set_autowrap_mode", "get_autowrap_mode");
+
+ // Note: "visible_characters" and "percent_visible" should be set after "text" to be correctly applied.
+ ADD_PROPERTY(PropertyInfo(Variant::INT, "visible_characters", PROPERTY_HINT_RANGE, "-1,128000,1"), "set_visible_characters", "get_visible_characters");
+ ADD_PROPERTY(PropertyInfo(Variant::INT, "visible_characters_behavior", PROPERTY_HINT_ENUM, "Characters Before Shaping,Characters After Shaping,Glyphs (Layout Direction),Glyphs (Left-to-Right),Glyphs (Right-to-Left)"), "set_visible_characters_behavior", "get_visible_characters_behavior");
+ ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "percent_visible", PROPERTY_HINT_RANGE, "0,1,0.001"), "set_percent_visible", "get_percent_visible");
+
+ ADD_PROPERTY(PropertyInfo(Variant::BOOL, "context_menu_enabled"), "set_context_menu_enabled", "is_context_menu_enabled");
+ ADD_PROPERTY(PropertyInfo(Variant::BOOL, "shortcut_keys_enabled"), "set_shortcut_keys_enabled", "is_shortcut_keys_enabled");
+ ADD_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");
@@ -4188,6 +5009,13 @@ 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)));
+ ADD_SIGNAL(MethodInfo("finished"));
+
+ 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);
BIND_ENUM_CONSTANT(LIST_ROMAN);
@@ -4217,12 +5045,36 @@ void RichTextLabel::_bind_methods() {
BIND_ENUM_CONSTANT(ITEM_BGCOLOR);
BIND_ENUM_CONSTANT(ITEM_FGCOLOR);
BIND_ENUM_CONSTANT(ITEM_META);
+ BIND_ENUM_CONSTANT(ITEM_HINT);
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) {
+ _stop_thread();
+
+ visible_chars_behavior = p_behavior;
+ main->first_invalid_line.store(0); //invalidate ALL
+ _validate_line_caches();
+ update();
+ }
}
void RichTextLabel::set_visible_characters(int p_visible) {
if (visible_characters != p_visible) {
+ _stop_thread();
+
visible_characters = p_visible;
if (p_visible == -1) {
percent_visible = 1;
@@ -4232,8 +5084,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.store(0); //invalidate ALL
+ _validate_line_caches();
+ }
update();
}
}
@@ -4242,6 +5096,39 @@ int RichTextLabel::get_visible_characters() const {
return visible_characters;
}
+int RichTextLabel::get_character_line(int p_char) {
+ int line_count = 0;
+ int to_line = main->first_invalid_line.load();
+ for (int i = 0; i < to_line; i++) {
+ MutexLock lock(main->lines[i].text_buf->get_mutex());
+ 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;
+ int to_line = main->first_invalid_line.load();
+ for (int i = 0; i < to_line; 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;
@@ -4257,10 +5144,26 @@ int RichTextLabel::get_total_character_count() const {
}
it = _get_next_item(it, true);
}
-
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 < (int)f->lines.size(); i++) {
+ MutexLock lock(f->lines[i].text_buf->get_mutex());
+ 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;
update_minimum_size();
@@ -4274,14 +5177,66 @@ Size2 RichTextLabel::get_minimum_size() const {
size.x += fixed_width;
}
- if (fixed_width != -1 || fit_content_height) {
- const_cast<RichTextLabel *>(this)->_validate_line_caches(main);
+ if (fit_content_height) {
size.y += get_content_height();
}
return size;
}
+// Context menu.
+void RichTextLabel::_generate_context_menu() {
+ if (!menu) {
+ menu = memnew(PopupMenu);
+ add_child(menu, false, INTERNAL_MODE_FRONT);
+
+ menu->connect("id_pressed", callable_mp(this, &RichTextLabel::_menu_option));
+ }
+
+ // Reorganize context menu.
+ menu->clear();
+ if (selection.enabled) {
+ menu->add_item(RTR("Copy"), MENU_COPY, is_shortcut_keys_enabled() ? _get_menu_action_accelerator("ui_copy") : Key::NONE);
+ menu->add_item(RTR("Select All"), MENU_SELECT_ALL, is_shortcut_keys_enabled() ? _get_menu_action_accelerator("ui_text_select_all") : Key::NONE);
+ }
+}
+
+Key RichTextLabel::_get_menu_action_accelerator(const String &p_action) {
+ const List<Ref<InputEvent>> *events = InputMap::get_singleton()->action_get_events(p_action);
+ if (!events) {
+ 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 Key::NONE;
+ }
+
+ const Ref<InputEventKey> event = first_event->get();
+ if (event.is_null()) {
+ return Key::NONE;
+ }
+
+ // Use physical keycode if non-zero
+ if (event->get_physical_keycode() != Key::NONE) {
+ return event->get_physical_keycode_with_modifiers();
+ } else {
+ return event->get_keycode_with_modifiers();
+ }
+}
+
+void RichTextLabel::_menu_option(int p_option) {
+ switch (p_option) {
+ case MENU_COPY: {
+ selection_copy();
+ } break;
+ case MENU_SELECT_ALL: {
+ select_all();
+ } break;
+ }
+}
+
void RichTextLabel::_draw_fbg_boxes(RID p_ci, RID p_rid, Vector2 line_off, Item *it_from, Item *it_to, int start, int end, int fbg_flag) {
Vector2i fbg_index = Vector2i(end, start);
Color last_color = Color(0, 0, 0, 0);
@@ -4289,7 +5244,7 @@ void RichTextLabel::_draw_fbg_boxes(RID p_ci, RID p_rid, Vector2 line_off, Item
// Draw a box based on color tags associated with glyphs
for (int i = start; i < end; i++) {
Item *it = _get_item_at_pos(it_from, it_to, i);
- Color color = Color(0, 0, 0, 0);
+ Color color;
if (fbg_flag == 0) {
color = _find_bgcolor(it);
@@ -4415,14 +5370,15 @@ 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;
main->lines.resize(1);
- main->lines.write[0].from = main;
- main->first_invalid_line = 0;
- main->first_resized_line = 0;
+ main->lines[0].from = main;
+ main->first_invalid_line.store(0);
+ main->first_resized_line.store(0);
+ main->first_invalid_font_line.store(0);
current_frame = main;
vscroll = memnew(VScrollBar);
@@ -4436,9 +5392,14 @@ RichTextLabel::RichTextLabel() {
vscroll->set_step(1);
vscroll->hide();
+ set_text(p_text);
+ updating.store(false);
+ stop_thread.store(false);
+
set_clip_contents(true);
}
RichTextLabel::~RichTextLabel() {
+ _stop_thread();
memdelete(main);
}
diff --git a/scene/gui/rich_text_label.h b/scene/gui/rich_text_label.h
index 58a531e5ad..93e57058b0 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 */
@@ -32,6 +32,7 @@
#define RICH_TEXT_LABEL_H
#include "rich_text_effect.h"
+#include "scene/gui/popup_menu.h"
#include "scene/gui/scroll_bar.h"
#include "scene/resources/text_paragraph.h"
@@ -39,6 +40,13 @@ class RichTextLabel : public Control {
GDCLASS(RichTextLabel, Control);
public:
+ enum AutowrapMode {
+ AUTOWRAP_OFF,
+ AUTOWRAP_ARBITRARY,
+ AUTOWRAP_WORD,
+ AUTOWRAP_WORD_SMART
+ };
+
enum ListType {
LIST_NUMBERS,
LIST_LETTERS,
@@ -71,10 +79,24 @@ public:
ITEM_BGCOLOR,
ITEM_FGCOLOR,
ITEM_META,
+ ITEM_HINT,
ITEM_DROPCAP,
ITEM_CUSTOMFX
};
+ enum VisibleCharactersBehavior {
+ VC_CHARS_BEFORE_SHAPING,
+ VC_CHARS_AFTER_SHAPING,
+ VC_GLYPHS_AUTO,
+ VC_GLYPHS_LTR,
+ VC_GLYPHS_RTL,
+ };
+
+ enum MenuItems {
+ MENU_COPY,
+ MENU_SELECT_ALL,
+ };
+
protected:
void _notification(int p_what);
static void _bind_methods();
@@ -95,6 +117,10 @@ private:
int char_count = 0;
Line() { text_buf.instantiate(); }
+
+ _FORCE_INLINE_ float get_height(float line_separation) const {
+ return offset.y + text_buf->get_size().y + text_buf->get_line_count() * line_separation;
+ }
};
struct Item {
@@ -119,9 +145,10 @@ private:
struct ItemFrame : public Item {
bool cell = false;
- Vector<Line> lines;
- int first_invalid_line = 0;
- int first_resized_line = 0;
+ LocalVector<Line> lines;
+ std::atomic<int> first_invalid_line;
+ std::atomic<int> first_invalid_font_line;
+ std::atomic<int> first_resized_line;
ItemFrame *parent_frame = nullptr;
@@ -132,7 +159,12 @@ private:
Size2 max_size_over = Size2(-1, -1);
Rect2 padding;
- ItemFrame() { type = ITEM_FRAME; }
+ ItemFrame() {
+ type = ITEM_FRAME;
+ first_invalid_line.store(0);
+ first_invalid_font_line.store(0);
+ first_resized_line.store(0);
+ }
};
struct ItemText : public Item {
@@ -202,11 +234,16 @@ private:
ItemMeta() { type = ITEM_META; }
};
+ struct ItemHint : public Item {
+ String description;
+ ItemHint() { type = ITEM_HINT; }
+ };
+
struct ItemParagraph : public Item {
HorizontalAlignment alignment = HORIZONTAL_ALIGNMENT_LEFT;
String language;
Control::TextDirection direction = Control::TEXT_DIRECTION_AUTO;
- Control::StructuredTextParser st_parser = STRUCTURED_TEXT_DEFAULT;
+ TextServer::StructuredTextParser st_parser = TextServer::STRUCTURED_TEXT_DEFAULT;
ItemParagraph() { type = ITEM_PARAGRAPH; }
};
@@ -235,8 +272,8 @@ private:
int width = 0;
};
- Vector<Column> columns;
- Vector<float> rows;
+ LocalVector<Column> columns;
+ LocalVector<float> rows;
int total_width = 0;
int total_height = 0;
@@ -335,8 +372,20 @@ private:
Item *current = nullptr;
ItemFrame *current_frame = nullptr;
+ Thread thread;
+ Mutex data_mutex;
+ bool threaded = false;
+ std::atomic<bool> stop_thread;
+ std::atomic<bool> updating;
+ std::atomic<double> loaded;
+
+ uint64_t loading_started = 0;
+ int progress_delay = 1000;
+
VScrollBar *vscroll = nullptr;
+ AutowrapMode autowrap_mode = AUTOWRAP_WORD_SMART;
+
bool scroll_visible = false;
bool scroll_follow = false;
bool scroll_following = false;
@@ -351,6 +400,7 @@ private:
int tab_size = 4;
bool underline_meta = true;
+ bool underline_hint = true;
bool override_selected_font_color = false;
HorizontalAlignment default_alignment = HORIZONTAL_ALIGNMENT_LEFT;
@@ -361,14 +411,18 @@ private:
Array custom_effects;
void _invalidate_current_line(ItemFrame *p_frame);
- void _validate_line_caches(ItemFrame *p_frame);
+
+ static void _thread_function(void *self);
+ void _stop_thread();
+ bool _validate_line_caches();
+ void _process_line_caches();
void _add_item(Item *p_item, bool p_enter = false, bool p_ensure_newline = false);
void _remove_item(Item *p_item, const int p_line, const int p_subitem_line);
String language;
TextDirection text_direction = TEXT_DIRECTION_AUTO;
- Control::StructuredTextParser st_parser = STRUCTURED_TEXT_DEFAULT;
+ TextServer::StructuredTextParser st_parser = TextServer::STRUCTURED_TEXT_DEFAULT;
Array st_args;
struct Selection {
@@ -389,24 +443,38 @@ private:
bool active = false; // anything selected? i.e. from, to, etc. valid?
bool enabled = false; // allow selections?
+ bool drag_attempt = false;
};
Selection selection;
bool deselect_on_focus_loss_enabled = true;
+ bool context_menu_enabled = false;
+ bool shortcut_keys_enabled = true;
+
+ // Context menu.
+ PopupMenu *menu = nullptr;
+ void _generate_context_menu();
+ Key _get_menu_action_accelerator(const String &p_action);
+ void _menu_option(int p_option);
+
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;
bool _search_line(ItemFrame *p_frame, int p_line, const String &p_string, int p_char_idx, bool p_reverse_search);
bool _search_table(ItemTable *p_table, List<Item *>::Element *p_from, const String &p_string, bool p_reverse_search);
- 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, int p_shadow_outline_size, const Point2 &p_shadow_ofs);
- 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);
+ float _shape_line(ItemFrame *p_frame, int p_line, const Ref<Font> &p_base_font, int p_base_font_size, int p_width, float p_h, int *r_char_offset);
+ float _resize_line(ItemFrame *p_frame, int p_line, const Ref<Font> &p_base_font, int p_base_font_size, int p_width, float p_h);
+
+ void _update_line_font(ItemFrame *p_frame, int p_line, const Ref<Font> &p_base_font, int p_base_font_size);
+ int _draw_line(ItemFrame *p_frame, int p_line, const Vector2 &p_ofs, int p_width, const Color &p_base_color, int p_outline_size, const Color &p_outline_color, const Color &p_font_shadow_color, int p_shadow_outline_size, const Point2 &p_shadow_ofs, int &r_processed_glyphs);
+ float _find_click_in_line(ItemFrame *p_frame, int p_line, const Vector2 &p_ofs, int p_width, const Point2i &p_click, ItemFrame **r_click_frame = nullptr, int *r_click_line = nullptr, Item **r_click_item = nullptr, int *r_click_char = nullptr, bool p_table = false);
String _roman(int p_num, bool p_capitalize) const;
String _letters(int p_num, bool p_capitalize) const;
@@ -423,23 +491,27 @@ private:
int _find_margin(Item *p_item, const Ref<Font> &p_base_font, int p_base_font_size);
HorizontalAlignment _find_alignment(Item *p_item);
TextServer::Direction _find_direction(Item *p_item);
- Control::StructuredTextParser _find_stt(Item *p_item);
+ TextServer::StructuredTextParser _find_stt(Item *p_item);
String _find_language(Item *p_item);
Color _find_color(Item *p_item, const Color &p_default_color);
Color _find_outline_color(Item *p_item, const Color &p_default_color);
bool _find_underline(Item *p_item);
bool _find_strikethrough(Item *p_item);
bool _find_meta(Item *p_item, Variant *r_meta, ItemMeta **r_item = nullptr);
+ bool _find_hint(Item *p_item, String *r_description);
Color _find_bgcolor(Item *p_item);
Color _find_fgcolor(Item *p_item);
bool _find_layout_subitem(Item *from, Item *to);
void _fetch_item_fx_stack(Item *p_item, Vector<ItemFX *> &r_stack);
- void _update_scroll();
void _update_fx(ItemFrame *p_frame, double p_delta_time);
void _scroll_changed(double);
+ int _find_first_line(int p_from, int p_to, int p_vofs) const;
+
+ _FORCE_INLINE_ float _calculate_line_vertical_offset(const Line &line) const;
virtual void gui_input(const Ref<InputEvent> &p_event) override;
+ virtual String get_tooltip(const Point2 &p_pos) const override;
Item *_get_next_item(Item *p_item, bool p_free = false) const;
Item *_get_prev_item(Item *p_item, bool p_free = false) const;
@@ -479,10 +551,11 @@ public:
void push_outline_color(const Color &p_color);
void push_underline();
void push_strikethrough();
- 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_paragraph(HorizontalAlignment p_alignment, Control::TextDirection p_direction = Control::TEXT_DIRECTION_INHERITED, const String &p_language = "", TextServer::StructuredTextParser p_st_parser = TextServer::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_hint(const String &p_string);
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);
@@ -508,6 +581,9 @@ public:
void set_meta_underline(bool p_underline);
bool is_meta_underlined() const;
+ void set_hint_underline(bool p_underline);
+ bool is_hint_underlined() const;
+
void set_override_selected_font_color(bool p_override_selected_font_color);
bool is_overriding_selected_font_color() const;
@@ -520,6 +596,12 @@ public:
void set_tab_size(int p_spaces);
int get_tab_size() const;
+ void set_context_menu_enabled(bool p_enabled);
+ bool is_context_menu_enabled() const;
+
+ void set_shortcut_keys_enabled(bool p_enabled);
+ bool is_shortcut_keys_enabled() const;
+
void set_fit_content_height(bool p_enabled);
bool is_fit_content_height_enabled() const;
@@ -529,24 +611,43 @@ 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;
int get_selection_from() const;
int get_selection_to() const;
String get_selected_text() const;
+ void select_all();
void selection_copy();
void set_deselect_on_focus_loss_enabled(const bool p_enabled);
bool is_deselect_on_focus_loss_enabled() const;
+ void deselect();
+
+ bool is_ready() const;
+
+ void set_threaded(bool p_threaded);
+ bool is_threaded() const;
+
+ void set_progress_bar_delay(int p_delay_ms);
+ int get_progress_bar_delay() const;
+
+ // Context menu.
+ PopupMenu *get_menu() const;
+ bool is_menu_visible() const;
void parse_bbcode(const String &p_bbcode);
void append_text(const String &p_bbcode);
@@ -563,19 +664,28 @@ public:
void set_language(const String &p_language);
String get_language() const;
- void set_structured_text_bidi_override(Control::StructuredTextParser p_parser);
- Control::StructuredTextParser get_structured_text_bidi_override() const;
+ void set_autowrap_mode(AutowrapMode p_mode);
+ AutowrapMode get_autowrap_mode() const;
+
+ void set_structured_text_bidi_override(TextServer::StructuredTextParser p_parser);
+ TextServer::StructuredTextParser get_structured_text_bidi_override() const;
void set_structured_text_bidi_override_options(Array p_args);
Array get_structured_text_bidi_override_options() const;
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();
@@ -584,11 +694,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::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 8c292e663e..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 */
@@ -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();
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 dcd2c32a3b..135bad4689 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"
@@ -314,97 +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) {
- Viewport *viewport = get_viewport();
- ERR_FAIL_COND(!viewport);
- 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 (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);
+ 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();
@@ -526,17 +533,17 @@ TypedArray<String> ScrollContainer::get_configuration_warnings() const {
}
if (found != 1) {
- warnings.push_back(TTR("ScrollContainer is intended to work with a single child control.\nUse a container as child (VBox, HBox, etc.), or a Control and set the custom minimum size manually."));
+ warnings.push_back(RTR("ScrollContainer is intended to work with a single child control.\nUse a container as child (VBox, HBox, etc.), or a Control and set the custom minimum size manually."));
}
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;
}
@@ -561,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"));
diff --git a/scene/gui/scroll_container.h b/scene/gui/scroll_container.h
index 0cec4db57a..b9fcf64db6 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 */
@@ -47,8 +47,8 @@ public:
};
private:
- HScrollBar *h_scroll;
- VScrollBar *v_scroll;
+ HScrollBar *h_scroll = nullptr;
+ VScrollBar *v_scroll = nullptr;
Size2 child_max_size;
@@ -105,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;
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 f8cabe172c..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 {
@@ -70,8 +71,13 @@ 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() == MouseButton::WHEEL_UP) {
@@ -145,19 +151,23 @@ void Slider::_notification(int p_what) {
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 8d6315d085..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,7 +39,7 @@ 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.is_empty()) {
value = prefix + " " + value;
@@ -48,6 +48,7 @@ void SpinBox::_value_changed(double) {
value += " " + suffix;
}
line_edit->set_text(value);
+ Range::_value_changed(p_value);
}
void SpinBox::_text_submitted(const String &p_string) {
@@ -196,34 +197,44 @@ 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("update_minimum_size"));
- get_line_edit()->call_deferred(SNAME("update_minimum_size"));
- } 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;
}
}
diff --git a/scene/gui/spin_box.h b/scene/gui/spin_box.h
index 736a5d873d..d118b28334 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 */
@@ -38,16 +38,16 @@
class SpinBox : public Range {
GDCLASS(SpinBox, Range);
- LineEdit *line_edit;
+ LineEdit *line_edit = nullptr;
int last_w = 0;
bool update_on_text_changed = false;
- Timer *range_click_timer;
+ Timer *range_click_timer = nullptr;
void _range_click_timeout();
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;
diff --git a/scene/gui/split_container.cpp b/scene/gui/split_container.cpp
index 106bb7949f..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,6 +203,7 @@ 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: {
update_minimum_size();
} break;
@@ -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..68281b6a72 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>();
+}
- for (int i = 0; i < get_child_count(); i++) {
- SubViewport *c = Object::cast_to<SubViewport>(get_child(i));
- if (!c) {
- continue;
+Vector<int> SubViewportContainer::get_allowed_size_flags_vertical() const {
+ return Vector<int>();
+}
+
+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);
}
}
@@ -146,7 +176,7 @@ void SubViewportContainer::input(const Ref<InputEvent> &p_event) {
return;
}
- Transform2D xform = get_global_transform();
+ Transform2D xform = get_global_transform_with_canvas();
if (stretch) {
Transform2D scale_xf;
@@ -173,7 +203,7 @@ void SubViewportContainer::unhandled_input(const Ref<InputEvent> &p_event) {
return;
}
- Transform2D xform = get_global_transform();
+ Transform2D xform = get_global_transform_with_canvas();
if (stretch) {
Transform2D scale_xf;
@@ -193,6 +223,23 @@ void SubViewportContainer::unhandled_input(const Ref<InputEvent> &p_event) {
}
}
+TypedArray<String> SubViewportContainer::get_configuration_warnings() const {
+ TypedArray<String> warnings = Node::get_configuration_warnings();
+
+ bool has_viewport = false;
+ for (int i = 0; i < get_child_count(); i++) {
+ if (Object::cast_to<SubViewport>(get_child(i))) {
+ has_viewport = true;
+ break;
+ }
+ }
+ if (!has_viewport) {
+ warnings.push_back(RTR("This node doesn't have a SubViewport as child, so it can't display its intended content.\nConsider adding a SubViewport as a child to provide something displayable."));
+ }
+
+ return warnings;
+}
+
void SubViewportContainer::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_stretch", "enable"), &SubViewportContainer::set_stretch);
ClassDB::bind_method(D_METHOD("is_stretch_enabled"), &SubViewportContainer::is_stretch_enabled);
diff --git a/scene/gui/subviewport_container.h b/scene/gui/subviewport_container.h
index 7853f1590e..55b7802aa4 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,11 @@ 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;
+
+ TypedArray<String> get_configuration_warnings() const override;
+
SubViewportContainer();
};
diff --git a/scene/gui/tab_bar.cpp b/scene/gui/tab_bar.cpp
index 3d88c117e7..fec8d11bef 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,77 @@
#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"
+#include "scene/main/viewport.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("h_separation"));
int y_margin = MAX(MAX(tab_unselected->get_minimum_size().height, tab_selected->get_minimum_size().height), tab_disabled->get_minimum_size().height);
- Size2 ms(0, 0);
-
for (int i = 0; i < tabs.size(); i++) {
- Ref<Texture2D> tex = tabs[i].icon;
- if (tex.is_valid()) {
- ms.height = MAX(ms.height, tex->get_size().height);
- if (!tabs[i].text.is_empty()) {
- ms.width += get_theme_constant(SNAME("hseparation"));
- }
+ if (tabs[i].hidden) {
+ continue;
}
- ms.width += Math::ceil(tabs[i].text_buf->get_size().x);
- ms.height = MAX(ms.height, tabs[i].text_buf->get_size().y + y_margin);
+ int ofs = ms.width;
+ Ref<StyleBox> style;
if (tabs[i].disabled) {
- ms.width += tab_disabled->get_minimum_size().width;
+ style = tab_disabled;
} else if (current == i) {
- ms.width += tab_selected->get_minimum_size().width;
+ style = tab_selected;
} else {
- ms.width += tab_unselected->get_minimum_size().width;
+ style = tab_unselected;
}
+ ms.width += style->get_minimum_size().width;
+
+ Ref<Texture2D> tex = tabs[i].icon;
+ if (tex.is_valid()) {
+ ms.height = MAX(ms.height, tex->get_size().height + 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;
}
}
@@ -136,7 +159,13 @@ void TabBar::gui_input(const Ref<InputEvent> &p_event) {
}
}
+ if (get_viewport()->gui_is_dragging() && can_drop_data(pos, get_viewport()->gui_get_drag_data())) {
+ dragging_valid_tab = true;
+ update();
+ }
+
_update_hover();
+
return;
}
@@ -147,6 +176,7 @@ void TabBar::gui_input(const Ref<InputEvent> &p_event) {
if (scrolling_enabled && buttons_visible) {
if (offset > 0) {
offset--;
+ _update_cache();
update();
}
}
@@ -154,9 +184,9 @@ void TabBar::gui_input(const Ref<InputEvent> &p_event) {
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();
}
}
@@ -164,8 +194,7 @@ void TabBar::gui_input(const Ref<InputEvent> &p_event) {
if (rb_pressing && !mb->is_pressed() && mb->get_button_index() == MouseButton::LEFT) {
if (rb_hover != -1) {
- // Right mouse button clicked.
- emit_signal(SNAME("tab_rmb_clicked"), rb_hover);
+ emit_signal(SNAME("tab_button_pressed"), rb_hover);
}
rb_pressing = false;
@@ -174,7 +203,6 @@ void TabBar::gui_input(const Ref<InputEvent> &p_event) {
if (cb_pressing && !mb->is_pressed() && mb->get_button_index() == MouseButton::LEFT) {
if (cb_hover != -1) {
- // Close button pressed.
emit_signal(SNAME("tab_close_pressed"), cb_hover);
}
@@ -183,7 +211,6 @@ void TabBar::gui_input(const Ref<InputEvent> &p_event) {
}
if (mb->is_pressed() && (mb->get_button_index() == MouseButton::LEFT || (select_with_rmb && mb->get_button_index() == MouseButton::RIGHT))) {
- // Clicks.
Point2 pos = mb->get_position();
if (buttons_visible) {
@@ -194,12 +221,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 +238,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,6 +260,10 @@ void TabBar::gui_input(const Ref<InputEvent> &p_event) {
int found = -1;
for (int i = offset; i <= max_drawn_tab; i++) {
+ if (tabs[i].hidden) {
+ continue;
+ }
+
if (tabs[i].rb_rect.has_point(pos)) {
rb_pressing = true;
update();
@@ -251,6 +286,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 +311,46 @@ void TabBar::_shape(int p_tab) {
tabs.write[p_tab].text_buf->set_direction((TextServer::Direction)tabs[p_tab].text_direction);
}
- tabs.write[p_tab].text_buf->add_string(tabs.write[p_tab].xl_text, font, font_size, tabs[p_tab].opentype_features, !tabs[p_tab].language.is_empty() ? tabs[p_tab].language : TranslationServer::get_singleton()->get_tool_locale());
+ tabs.write[p_tab].text_buf->add_string(tabs[p_tab].xl_text, font, font_size, tabs[p_tab].opentype_features, !tabs[p_tab].language.is_empty() ? tabs[p_tab].language : TranslationServer::get_singleton()->get_tool_locale());
}
void TabBar::_notification(int p_what) {
switch (p_what) {
case NOTIFICATION_LAYOUT_DIRECTION_CHANGED: {
- _update_cache();
update();
} break;
+
case NOTIFICATION_THEME_CHANGED:
case NOTIFICATION_TRANSLATION_CHANGED: {
for (int i = 0; i < tabs.size(); ++i) {
_shape(i);
}
- _update_cache();
- update_minimum_size();
- update();
- } break;
+
+ [[fallthrough]];
+ }
case NOTIFICATION_RESIZED: {
+ int ofs_old = offset;
+ int max_old = max_drawn_tab;
+
_update_cache();
_ensure_no_over_offset();
- ensure_tab_visible(current);
+
+ if (scroll_to_selected && (offset != ofs_old || max_drawn_tab != max_old)) {
+ ensure_tab_visible(current);
+ }
} break;
+
+ case NOTIFICATION_DRAG_END: {
+ if (dragging_valid_tab) {
+ dragging_valid_tab = false;
+ update();
+ }
+ } 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,178 +358,55 @@ 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_alignment == ALIGNMENT_CENTER) {
- w = (get_size().width - mw) / 2;
- } else if (tab_alignment == ALIGNMENT_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);
-
- w += sb->get_margin(SIDE_LEFT);
+ bool rtl = is_layout_rtl();
+ Vector2 size = get_size();
+ int limit_minus_buttons = size.width - incr->get_width() - decr->get_width();
- 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.is_empty()) {
- w += icon->get_width() + get_theme_constant(SNAME("hseparation"));
- }
- }
+ int ofs = tabs[offset].ofs_cache;
- 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) {
- int vofs = (get_size().height - incr->get_size().height) / 2;
+ if (buttons_visible) {
+ Ref<Texture2D> incr_hl = get_theme_icon(SNAME("increment_highlight"));
+ Ref<Texture2D> decr_hl = get_theme_icon(SNAME("decrement_highlight"));
+
+ int vofs = (size.height - incr->get_size().height) / 2;
if (rtl) {
if (missing_right) {
@@ -501,29 +433,180 @@ void TabBar::_notification(int p_what) {
draw_texture(incr, Point2(limit_minus_buttons + decr->get_size().width, vofs), Color(1, 1, 1, 0.5));
}
}
+ }
- buttons_visible = true;
- } else {
- buttons_visible = false;
+ if (dragging_valid_tab) {
+ int x;
+
+ int tab_hover = get_hovered_tab();
+ if (tab_hover != -1) {
+ Rect2 tab_rect = get_tab_rect(tab_hover);
+
+ x = tab_rect.position.x;
+ if (get_local_mouse_position().x > x + tab_rect.size.width / 2) {
+ x += tab_rect.size.width;
+ }
+ } else {
+ if (rtl ^ (get_local_mouse_position().x < get_tab_rect(0).position.x)) {
+ x = get_tab_rect(0).position.x;
+ if (rtl) {
+ x += get_tab_rect(0).size.width;
+ }
+ } else {
+ Rect2 tab_rect = get_tab_rect(get_tab_count() - 1);
+
+ x = tab_rect.position.x;
+ if (!rtl) {
+ x += tab_rect.size.width;
+ }
+ }
+ }
+
+ Ref<Texture2D> drop_mark = get_theme_icon(SNAME("drop_mark"));
+ Color drop_mark_color = get_theme_color(SNAME("drop_mark_color"));
+
+ drop_mark->draw(get_canvas_item(), Point2(x - drop_mark->get_width() / 2, (size.height - drop_mark->get_height()) / 2), drop_mark_color);
}
} 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("h_separation"));
+
+ 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);
+
+ 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 {
+ style->draw(ci, rb_rect);
+ }
+ }
+
+ rb->draw(ci, Point2i(rb_rect.position.x + style->get_margin(SIDE_LEFT), rb_rect.position.y + style->get_margin(SIDE_TOP)));
+
+ p_x = rtl ? rb_rect.position.x : rb_rect.position.x + rb_rect.size.width;
+ }
+
+ // Draw and calculate rect of the close button.
+ if (cb_displaypolicy == CLOSE_BUTTON_SHOW_ALWAYS || (cb_displaypolicy == CLOSE_BUTTON_SHOW_ACTIVE_ONLY && p_index == current)) {
+ Ref<StyleBox> style = get_theme_stylebox(SNAME("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,7 +635,13 @@ bool TabBar::get_offset_buttons_visible() const {
void TabBar::set_tab_title(int p_tab, const String &p_title) {
ERR_FAIL_INDEX(p_tab, tabs.size());
tabs.write[p_tab].text = p_title;
+
_shape(p_tab);
+ _update_cache();
+ _ensure_no_over_offset();
+ if (scroll_to_selected) {
+ ensure_tab_visible(current);
+ }
update();
update_minimum_size();
}
@@ -565,6 +654,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 +670,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 +711,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,6 +733,12 @@ String TabBar::get_tab_language(int p_tab) const {
void TabBar::set_tab_icon(int p_tab, const Ref<Texture2D> &p_icon) {
ERR_FAIL_INDEX(p_tab, tabs.size());
tabs.write[p_tab].icon = p_icon;
+
+ _update_cache();
+ _ensure_no_over_offset();
+ if (scroll_to_selected) {
+ ensure_tab_visible(current);
+ }
update();
update_minimum_size();
}
@@ -632,23 +751,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].hidden = p_hidden;
+
+ _update_cache();
+ _ensure_no_over_offset();
+ if (scroll_to_selected) {
+ ensure_tab_visible(current);
+ }
+ update();
+ update_minimum_size();
+}
+
+bool TabBar::is_tab_hidden(int p_tab) const {
+ ERR_FAIL_INDEX_V(p_tab, tabs.size(), false);
+ return tabs[p_tab].hidden;
+}
+
+void TabBar::set_tab_button_icon(int p_tab, const Ref<Texture2D> &p_icon) {
ERR_FAIL_INDEX(p_tab, tabs.size());
- tabs.write[p_tab].right_button = p_right_button;
+ tabs.write[p_tab].right_button = p_icon;
+
_update_cache();
+ _ensure_no_over_offset();
+ if (scroll_to_selected) {
+ ensure_tab_visible(current);
+ }
update();
update_minimum_size();
}
-Ref<Texture2D> TabBar::get_tab_right_button(int p_tab) const {
+Ref<Texture2D> TabBar::get_tab_button_icon(int p_tab) const {
ERR_FAIL_INDEX_V(p_tab, tabs.size(), Ref<Texture2D>());
return tabs[p_tab].right_button;
}
@@ -658,99 +807,147 @@ 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.
+ 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()) {
+ buttons_visible = false;
+ 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() {
@@ -758,65 +955,87 @@ void TabBar::_on_mouse_exited() {
cb_hover = -1;
hover = -1;
highlight_arrow = -1;
+ dragging_valid_tab = false;
+
update();
}
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();
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_at(p_idx);
- if (current >= 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();
- update_minimum_size();
- 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 +1045,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;
@@ -868,16 +1086,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 +1103,84 @@ 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;
+ if (tab_from_id == hover_now) {
+ return;
}
+
+ // Drop the new tab to the left or right depending on where the target tab is being hovered.
+ if (hover_now != -1) {
+ Rect2 tab_rect = get_tab_rect(hover_now);
+ if (is_layout_rtl() ^ (p_point.x <= tab_rect.position.x + tab_rect.size.width / 2)) {
+ if (hover_now > tab_from_id) {
+ hover_now -= 1;
+ }
+ } else if (tab_from_id > hover_now) {
+ hover_now += 1;
+ }
+ } else {
+ hover_now = is_layout_rtl() ^ (p_point.x < get_tab_rect(0).position.x) ? 0 : get_tab_count() - 1;
+ }
+
move_tab(tab_from_id, hover_now);
- emit_signal(SNAME("active_tab_rearranged"), hover_now);
- set_current_tab(hover_now);
+ if (!is_tab_disabled(hover_now)) {
+ emit_signal(SNAME("active_tab_rearranged"), hover_now);
+ set_current_tab(hover_now);
+ }
} else if (get_tabs_rearrange_group() != -1) {
// Drag and drop between Tabs.
+
Node *from_node = get_node(from_path);
TabBar *from_tabs = Object::cast_to<TabBar>(from_node);
+
if (from_tabs && from_tabs->get_tabs_rearrange_group() == get_tabs_rearrange_group()) {
if (tab_from_id >= from_tabs->get_tab_count()) {
return;
}
- Tab moving_tab = from_tabs->tabs[tab_from_id];
- if (hover_now < 0) {
- hover_now = get_tab_count();
+
+ // Drop the new tab to the left or right depending on where the target tab is being hovered.
+ if (hover_now != -1) {
+ Rect2 tab_rect = get_tab_rect(hover_now);
+ if (is_layout_rtl() ^ (p_point.x > tab_rect.position.x + tab_rect.size.width / 2)) {
+ hover_now += 1;
+ }
+ } else {
+ hover_now = is_layout_rtl() ^ (p_point.x < get_tab_rect(0).position.x) ? 0 : get_tab_count();
}
- tabs.insert(hover_now, moving_tab);
+
+ Tab moving_tab = from_tabs->tabs[tab_from_id];
from_tabs->remove_tab(tab_from_id);
- set_current_tab(hover_now);
- emit_signal(SNAME("tab_changed"), hover_now);
- _update_cache();
+ tabs.insert(hover_now, moving_tab);
+
+ if (tabs.size() > 1) {
+ if (current >= hover_now) {
+ current++;
+ }
+ if (previous >= hover_now) {
+ previous++;
+ }
+ }
+
+ if (!is_tab_disabled(hover_now)) {
+ set_current_tab(hover_now);
+ } else {
+ _update_cache();
+ update();
+ }
+
+ 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 {
@@ -932,6 +1198,8 @@ int TabBar::get_tab_idx_at_point(const Point2 &p_point) const {
void TabBar::set_tab_alignment(AlignmentMode p_alignment) {
ERR_FAIL_INDEX(p_alignment, ALIGNMENT_MAX);
tab_alignment = p_alignment;
+
+ _update_cache();
update();
}
@@ -944,6 +1212,16 @@ void TabBar::set_clip_tabs(bool p_clip_tabs) {
return;
}
clip_tabs = p_clip_tabs;
+
+ if (!clip_tabs) {
+ offset = 0;
+ max_drawn_tab = 0;
+ }
+
+ _update_cache();
+ if (scroll_to_selected) {
+ ensure_tab_visible(current);
+ }
update();
update_minimum_size();
}
@@ -952,20 +1230,41 @@ 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_at(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 +1273,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("h_separation"));
- int x = 0;
+ Ref<StyleBox> style;
+
+ if (tabs[p_idx].disabled) {
+ style = tab_disabled;
+ } else if (current == p_idx) {
+ style = tab_selected;
+ } else {
+ style = tab_unselected;
+ }
+ int x = style->get_minimum_size().width;
Ref<Texture2D> tex = tabs[p_idx].icon;
if (tex.is_valid()) {
- x += tex->get_width();
- if (!tabs[p_idx].text.is_empty()) {
- x += get_theme_constant(SNAME("hseparation"));
- }
+ x += tex->get_width() + hseparation;
}
- x += Math::ceil(tabs[p_idx].text_buf->get_size().x);
-
- if (tabs[p_idx].disabled) {
- x += tab_disabled->get_minimum_size().width;
- } else if (current == p_idx) {
- x += tab_selected->get_minimum_size().width;
- } else {
- x += tab_unselected->get_minimum_size().width;
+ if (!tabs[p_idx].text.is_empty()) {
+ x += tabs[p_idx].size_text + hseparation;
}
+ bool close_visible = cb_displaypolicy == CLOSE_BUTTON_SHOW_ALWAYS || (cb_displaypolicy == CLOSE_BUTTON_SHOW_ACTIVE_ONLY && p_idx == current);
+
if (tabs[p_idx].right_button.is_valid()) {
+ Ref<StyleBox> btn_style = get_theme_stylebox(SNAME("button_highlight"));
Ref<Texture2D> rb = tabs[p_idx].right_button;
- x += rb->get_width();
- x += get_theme_constant(SNAME("hseparation"));
+
+ if (close_visible) {
+ x += btn_style->get_minimum_size().width + rb->get_width();
+ } else {
+ x += btn_style->get_margin(SIDE_LEFT) + rb->get_width() + hseparation;
+ }
}
- if (cb_displaypolicy == CLOSE_BUTTON_SHOW_ALWAYS || (cb_displaypolicy == CLOSE_BUTTON_SHOW_ACTIVE_ONLY && p_idx == current)) {
+ if (close_visible) {
+ Ref<StyleBox> btn_style = get_theme_stylebox(SNAME("button_highlight"));
Ref<Texture2D> cb = get_theme_icon(SNAME("close"));
- x += cb->get_width();
- x += get_theme_constant(SNAME("hseparation"));
+ x += btn_style->get_margin(SIDE_LEFT) + cb->get_width() + hseparation;
+ }
+
+ if (x > style->get_minimum_size().width) {
+ x -= hseparation;
}
return x;
}
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 +1417,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 +1472,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 +1491,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,10 +1560,15 @@ 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("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);
@@ -1160,29 +1580,40 @@ 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_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::INT, "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(ALIGNMENT_LEFT);
BIND_ENUM_CONSTANT(ALIGNMENT_CENTER);
@@ -1196,5 +1627,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 2d211937fc..548a2e62af 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 */
@@ -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;
- AlignmentMode tab_alignment = ALIGNMENT_CENTER;
+ AlignmentMode tab_alignment = ALIGNMENT_LEFT;
bool clip_tabs = true;
int rb_hover = -1;
bool rb_pressing = false;
@@ -95,9 +98,11 @@ 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 dragging_valid_tab = false;
+ bool scroll_to_selected = true;
int tabs_rearrange_group = -1;
int get_tab_width(int p_idx) const;
@@ -109,16 +114,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,10 +148,15 @@ 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;
+
+ int get_tab_idx_at_point(const Point2 &p_point) const;
void set_tab_alignment(AlignmentMode p_alignment);
AlignmentMode get_tab_alignment() const;
@@ -151,12 +164,14 @@ public:
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 +192,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;
diff --git a/scene/gui/tab_container.cpp b/scene/gui/tab_container.cpp
index fd5df5c93b..8299d73b68 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) {
@@ -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();
- }
- if (buttons_visible_cache) {
- header_width -= increment->get_width() + decrement->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 (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,173 @@ 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 (alignment) {
- case ALIGNMENT_LEFT:
- tabs_ofs_cache = header_x;
- break;
- case ALIGNMENT_CENTER:
- tabs_ofs_cache = header_x + (header_width / 2) - (all_tabs_width / 2);
- break;
- case ALIGNMENT_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));
- }
-
- // 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 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);
- }
- }
+ panel->draw(canvas, Rect2(0, header_height, size.width, size.height - header_height));
// 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();
- }
- }
+ if (get_popup()) {
+ Ref<Texture2D> menu = get_theme_icon(SNAME("menu"));
+ Ref<Texture2D> menu_hl = get_theme_icon(SNAME("menu_highlight"));
- // 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();
+ int x = is_layout_rtl() ? 0 : get_size().width - menu->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();
+ if (menu_hovered) {
+ menu_hl->draw(get_canvas_item(), Point2(x, (header_height - menu_hl->get_height()) / 2));
} 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));
- }
+ menu->draw(get_canvas_item(), Point2(x, (header_height - menu->get_height()) / 2));
}
}
} 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.is_empty()) {
- 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();
-
- update_minimum_size();
+ tab_bar->add_theme_style_override(SNAME("tab_unselected"), get_theme_stylebox(SNAME("tab_unselected")));
+ tab_bar->add_theme_style_override(SNAME("tab_selected"), get_theme_stylebox(SNAME("tab_selected")));
+ tab_bar->add_theme_style_override(SNAME("tab_disabled"), get_theme_stylebox(SNAME("tab_disabled")));
+ tab_bar->add_theme_icon_override(SNAME("increment"), get_theme_icon(SNAME("increment")));
+ tab_bar->add_theme_icon_override(SNAME("increment_highlight"), get_theme_icon(SNAME("increment_highlight")));
+ tab_bar->add_theme_icon_override(SNAME("decrement"), get_theme_icon(SNAME("decrement")));
+ tab_bar->add_theme_icon_override(SNAME("decrement_highlight"), get_theme_icon(SNAME("decrement_highlight")));
+ tab_bar->add_theme_icon_override(SNAME("drop_mark"), get_theme_icon(SNAME("drop_mark")));
+ tab_bar->add_theme_color_override(SNAME("drop_mark_color"), get_theme_color(SNAME("drop_mark_color")));
+ tab_bar->add_theme_color_override(SNAME("font_selected_color"), get_theme_color(SNAME("font_selected_color")));
+ tab_bar->add_theme_color_override(SNAME("font_unselected_color"), get_theme_color(SNAME("font_unselected_color")));
+ tab_bar->add_theme_color_override(SNAME("font_disabled_color"), get_theme_color(SNAME("font_disabled_color")));
+ tab_bar->add_theme_color_override(SNAME("font_outline_color"), get_theme_color(SNAME("font_outline_color")));
+ tab_bar->add_theme_font_override(SNAME("font"), get_theme_font(SNAME("font")));
+ tab_bar->add_theme_font_size_override(SNAME("font_size"), get_theme_font_size(SNAME("font_size")));
+ tab_bar->add_theme_constant_override(SNAME("h_separation"), get_theme_constant(SNAME("icon_separation")));
+ tab_bar->add_theme_constant_override(SNAME("outline_size"), get_theme_constant(SNAME("outline_size")));
+
+ _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();
- }
-}
-
-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;
- }
-
- // 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.is_empty()) {
- 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;
+ update_minimum_size();
}
-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::_update_margins() {
+ int menu_width = get_theme_icon(SNAME("menu"))->get_width();
+ int side_margin = get_theme_constant(SNAME("side_margin"));
-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 +340,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 +369,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,16 +377,15 @@ 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;
}
- int hover_now = get_tab_idx_at_point(p_point);
-
Dictionary d = p_data;
if (!d.has("type")) {
return;
@@ -890,91 +393,240 @@ void TabContainer::drop_data(const Point2 &p_point, const Variant &p_data) {
if (String(d["type"]) == "tabc_element") {
int tab_from_id = d["tabc_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;
+ if (tab_from_id == hover_now) {
+ return;
}
- move_child(get_tab_control(tab_from_id), get_tab_control(hover_now)->get_index());
- set_current_tab(hover_now);
+
+ // Drop the new tab to the left or right depending on where the target tab is being hovered.
+ if (hover_now != -1) {
+ Rect2 tab_rect = tab_bar->get_tab_rect(hover_now);
+ if (is_layout_rtl() ^ (p_point.x <= tab_rect.position.x + tab_rect.size.width / 2)) {
+ if (hover_now > tab_from_id) {
+ hover_now -= 1;
+ }
+ } else if (tab_from_id > hover_now) {
+ hover_now += 1;
+ }
+ } else {
+ hover_now = is_layout_rtl() ^ (p_point.x < tab_bar->get_tab_rect(0).position.x) ? 0 : get_tab_count() - 1;
+ }
+
+ 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()) {
+ // Get the tab properties before they get erased by the child removal.
+ String tab_title = from_tabc->get_tab_title(tab_from_id);
+ bool tab_disabled = from_tabc->is_tab_disabled(tab_from_id);
+
+ // Drop the new tab to the left or right depending on where the target tab is being hovered.
+ if (hover_now != -1) {
+ Rect2 tab_rect = tab_bar->get_tab_rect(hover_now);
+ if (is_layout_rtl() ^ (p_point.x > tab_rect.position.x + tab_rect.size.width / 2)) {
+ hover_now += 1;
+ }
+ } else {
+ hover_now = is_layout_rtl() ^ (p_point.x < tab_bar->get_tab_rect(0).position.x) ? 0 : get_tab_count();
+ }
+
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);
- if (hover_now < 0) {
- hover_now = get_tab_count() - 1;
+ add_child(moving_tabc, true);
+
+ set_tab_title(get_tab_count() - 1, tab_title);
+ set_tab_disabled(get_tab_count() - 1, tab_disabled);
+
+ move_child(moving_tabc, get_tab_control(hover_now)->get_index(false));
+ if (!is_tab_disabled(hover_now)) {
+ set_current_tab(hover_now);
}
- move_child(moving_tabc, get_tab_control(hover_now)->get_index());
- set_current_tab(hover_now);
- emit_signal(SNAME("tab_changed"), 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::_on_tab_button_pressed(int p_tab) {
+ emit_signal(SNAME("tab_button_pressed"), 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());
+ }
}
+}
- // must be on tabs in the tab header area.
- if (p_point.y > _get_top_margin()) {
- return -1;
+void TabContainer::add_child_notify(Node *p_child) {
+ if (p_child == tab_bar) {
+ return;
}
- Size2 size = get_size();
- int button_ofs = 0;
- int px = p_point.x;
+ Container::add_child_notify(p_child);
- if (is_layout_rtl()) {
- px = size.width - px;
+ 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());
- if (px < tabs_ofs_cache) {
- return -1;
+ _update_margins();
+ if (get_tab_count() == 1) {
+ update();
}
- Popup *popup = get_popup();
- if (popup) {
- Ref<Texture2D> menu = get_theme_icon(SNAME("menu"));
- button_ofs += menu->get_width();
+ p_child->connect("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");
+ }
+}
+
+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 = String(c->get_meta("_tab_name", 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;
+ }
+ }
+
+ tab_bar->move_tab(old_idx, get_tab_idx_from_control(c));
}
- 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();
+}
+
+void TabContainer::remove_child_notify(Node *p_child) {
+ if (p_child == tab_bar) {
+ return;
}
- if (px > size.width - button_ofs) {
- return -1;
+
+ Container::remove_child_notify(p_child);
+
+ Control *c = Object::cast_to<Control>(p_child);
+ if (!c || c->is_set_as_top_level()) {
+ return;
}
- // 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) {
+ tab_bar->remove_tab(get_tab_idx_from_control(c));
+
+ _update_margins();
+ if (get_tab_count() == 0) {
+ update();
+ }
+
+ p_child->remove_meta("_tab_name");
+ 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");
+ }
+}
+
+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());
+}
+
+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_alignment(AlignmentMode p_alignment) {
- ERR_FAIL_INDEX(p_alignment, 3);
- alignment = p_alignment;
- update();
+void TabContainer::set_tab_alignment(TabBar::AlignmentMode p_alignment) {
+ tab_bar->set_tab_alignment(p_alignment);
+ _update_margins();
+}
+
+TabBar::AlignmentMode TabContainer::get_tab_alignment() const {
+ return tab_bar->get_tab_alignment();
}
-TabContainer::AlignmentMode TabContainer::get_tab_alignment() const {
- return 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 +635,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);
@@ -1009,7 +662,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,95 +673,87 @@ 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()) {
+ 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;
+ tab_bar->set_tab_hidden(p_tab, p_hidden);
+ child->hide();
+
+ _update_margins();
+ if (!get_clip_tabs()) {
+ update_minimum_size();
}
+}
- //assumed no other tab can be switched to, just hide
- child->hide();
+bool TabContainer::is_tab_hidden(int p_tab) const {
+ return tab_bar->is_tab_hidden(p_tab);
}
-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;
- }
+void TabContainer::set_tab_button_icon(int p_tab, const Ref<Texture2D> &p_icon) {
+ tab_bar->set_tab_button_icon(p_tab, p_icon);
+
+ _update_margins();
+ _repaint();
+}
+
+Ref<Texture2D> TabContainer::get_tab_button_icon(int p_tab) const {
+ return tab_bar->get_tab_button_icon(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.is_empty()) {
p_strings->push_back(name);
}
@@ -1117,9 +763,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 +790,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 +828,7 @@ Popup *TabContainer::get_popup() const {
popup_obj_id = ObjectID();
}
}
+
return nullptr;
}
@@ -1176,21 +841,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);
@@ -1200,6 +874,8 @@ void TabContainer::_bind_methods() {
ClassDB::bind_method(D_METHOD("get_tab_control", "tab_idx"), &TabContainer::get_tab_control);
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 +885,51 @@ 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("set_tab_button_icon", "tab_idx", "icon"), &TabContainer::set_tab_button_icon);
+ ClassDB::bind_method(D_METHOD("get_tab_button_icon", "tab_idx"), &TabContainer::get_tab_button_icon);
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("tab_button_pressed", PropertyInfo(Variant::INT, "tab")));
ADD_SIGNAL(MethodInfo("pre_popup_pressed"));
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(ALIGNMENT_LEFT);
- BIND_ENUM_CONSTANT(ALIGNMENT_CENTER);
- BIND_ENUM_CONSTANT(ALIGNMENT_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));
+ tab_bar->connect("tab_button_pressed", callable_mp(this, &TabContainer::_on_tab_button_pressed));
+
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 6dfeb2b625..9adaa0d844 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,52 @@
#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 AlignmentMode {
- ALIGNMENT_LEFT,
- ALIGNMENT_CENTER,
- ALIGNMENT_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 = nullptr;
bool tabs_visible = true;
bool all_tabs_in_front = false;
- bool buttons_visible_cache = false;
bool menu_hovered = false;
- int highlight_arrow = -1;
- AlignmentMode alignment = ALIGNMENT_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);
+ void _on_tab_button_pressed(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_alignment(AlignmentMode p_alignment);
- AlignmentMode get_tab_alignment() 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 +93,13 @@ 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;
+
+ void set_tab_button_icon(int p_tab, const Ref<Texture2D> &p_icon);
+ Ref<Texture2D> get_tab_button_icon(int p_tab) const;
int get_tab_count() const;
void set_current_tab(int p_current);
@@ -133,9 +123,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::AlignmentMode);
-
#endif // TAB_CONTAINER_H
diff --git a/scene/gui/text_edit.cpp b/scene/gui/text_edit.cpp
index 63e3740a97..8e948203f1 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 */
@@ -43,18 +43,6 @@
#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 ///
///////////////////////////////////////////////////////////////////////////////
@@ -128,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);
@@ -183,29 +175,44 @@ void TextEdit::Text::_calculate_max_line_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;
@@ -262,6 +269,24 @@ 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;
@@ -275,7 +300,7 @@ void TextEdit::Text::invalidate_all() {
}
for (int i = 0; i < text.size(); i++) {
- invalidate_cache(i);
+ invalidate_cache(i, -1, true);
}
is_dirty = false;
}
@@ -290,7 +315,7 @@ void TextEdit::Text::clear() {
line.gutters.resize(gutter_count);
line.data = "";
text.insert(0, line);
- invalidate_cache(0);
+ invalidate_cache(0, -1, true);
}
int TextEdit::Text::get_max_width() const {
@@ -302,7 +327,7 @@ 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 Vector<String> &p_text, const Vector<Array> &p_bidi_override) {
@@ -327,7 +352,7 @@ void TextEdit::Text::insert(int p_at, const Vector<String> &p_text, const Vector
line.data = p_text[i];
line.bidi_override = p_bidi_override[i];
text.write[p_at + i] = line;
- invalidate_cache(p_at + i);
+ invalidate_cache(p_at + i, -1, true);
}
}
@@ -407,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();
@@ -440,6 +471,11 @@ void TextEdit::_notification(int p_what) {
// To ensure minimap is responsive override the speed setting.
double vel = ((target_y / dist) * ((minimap_clicked) ? 3000 : v_scroll_speed)) * get_physics_process_delta_time();
+ // Prevent too small velocity to block scrolling
+ if (Math::abs(vel) < v_scroll->get_step()) {
+ vel = v_scroll->get_step() * SIGN(vel);
+ }
+
if (Math::abs(vel) >= dist) {
set_v_scroll(target_v_scroll);
scrolling = false;
@@ -454,6 +490,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.
@@ -646,6 +683,8 @@ 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();
@@ -656,7 +695,7 @@ void TextEdit::_notification(int p_what) {
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);
// Draw minimap.
if (draw_minimap) {
@@ -781,8 +820,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) {
@@ -804,7 +843,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);
@@ -841,7 +880,7 @@ void TextEdit::_notification(int p_what) {
// Draw main text.
caret.visible = false;
line_drawing_cache.clear();
- int row_height = get_line_height();
+ 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++;
@@ -867,11 +906,14 @@ void TextEdit::_notification(int p_what) {
// Ensure we at least use the font color.
Color current_color = !editable ? font_readonly_color : font_color;
+ if (draw_placeholder) {
+ current_color = 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) {
@@ -931,7 +973,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 {
@@ -1008,15 +1050,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;
}
@@ -1100,7 +1144,7 @@ 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);
@@ -1135,7 +1179,7 @@ void TextEdit::_notification(int p_what) {
int first_visible_char = TS->shaped_text_get_range(rid).y;
int last_visible_char = TS->shaped_text_get_range(rid).x;
- int char_ofs = 0;
+ 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++) {
@@ -1170,7 +1214,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) ||
@@ -1244,7 +1288,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);
@@ -1383,7 +1428,9 @@ void TextEdit::_notification(int p_what) {
}
}
- line_drawing_cache[line] = cache_entry;
+ if (!draw_placeholder) {
+ line_drawing_cache[line] = cache_entry;
+ }
}
if (has_focus()) {
@@ -1393,6 +1440,7 @@ void TextEdit::_notification(int p_what) {
}
}
} break;
+
case NOTIFICATION_FOCUS_ENTER: {
if (caret_blink_enabled) {
caret_blink_timer->start();
@@ -1424,6 +1472,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();
@@ -1433,9 +1482,11 @@ 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();
@@ -1445,6 +1496,7 @@ void TextEdit::_notification(int p_what) {
deselect();
}
} break;
+
case MainLoop::NOTIFICATION_OS_IME_UPDATE: {
if (has_focus()) {
ime_text = DisplayServer::get_singleton()->ime_get_text();
@@ -1457,11 +1509,12 @@ 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 Control::NOTIFICATION_DRAG_BEGIN: {
+
+ case NOTIFICATION_DRAG_BEGIN: {
selection.selecting_mode = SelectionMode::SELECTION_MODE_NONE;
drag_action = true;
dragging_minimap = false;
@@ -1469,7 +1522,8 @@ void TextEdit::_notification(int p_what) {
can_drag_minimap = false;
click_select_held->stop();
} break;
- case Control::NOTIFICATION_DRAG_END: {
+
+ case NOTIFICATION_DRAG_END: {
if (is_drag_successful()) {
if (selection.drag_attempt) {
selection.drag_attempt = false;
@@ -1488,6 +1542,62 @@ void TextEdit::_notification(int p_what) {
}
}
+void TextEdit::unhandled_key_input(const Ref<InputEvent> &p_event) {
+ Ref<InputEventKey> k = p_event;
+
+ if (k.is_valid()) {
+ if (!k->is_pressed()) {
+ return;
+ }
+ // Handle Unicode (with modifiers active, process after shortcuts).
+ if (has_focus() && editable && (k->get_unicode() >= 32)) {
+ handle_unicode_input(k->get_unicode());
+ accept_event();
+ }
+ }
+}
+
+bool TextEdit::alt_input(const Ref<InputEvent> &p_gui_input) {
+ Ref<InputEventKey> k = p_gui_input;
+ if (k.is_valid()) {
+ if (!k->is_pressed()) {
+ if (alt_start && k->get_keycode() == Key::ALT) {
+ alt_start = false;
+ if ((alt_code > 0x31 && alt_code < 0xd800) || (alt_code > 0xdfff && alt_code <= 0x10ffff)) {
+ handle_unicode_input(alt_code);
+ }
+ return true;
+ }
+ return false;
+ }
+
+ if (k->is_alt_pressed()) {
+ if (!alt_start) {
+ if (k->get_keycode() == Key::KP_ADD) {
+ alt_start = true;
+ alt_code = 0;
+ return true;
+ }
+ } else {
+ if (k->get_keycode() >= Key::KEY_0 && k->get_keycode() <= Key::KEY_9) {
+ alt_code = alt_code << 4;
+ alt_code += (uint32_t)(k->get_keycode() - Key::KEY_0);
+ }
+ if (k->get_keycode() >= Key::KP_0 && k->get_keycode() <= Key::KP_9) {
+ alt_code = alt_code << 4;
+ alt_code += (uint32_t)(k->get_keycode() - Key::KP_0);
+ }
+ if (k->get_keycode() >= Key::A && k->get_keycode() <= Key::F) {
+ alt_code = alt_code << 4;
+ alt_code += (uint32_t)(k->get_keycode() - Key::A) + 10;
+ }
+ return true;
+ }
+ }
+ }
+ return false;
+}
+
void TextEdit::gui_input(const Ref<InputEvent> &p_gui_input) {
ERR_FAIL_COND(p_gui_input.is_null());
@@ -1571,7 +1681,7 @@ void TextEdit::gui_input(const Ref<InputEvent> &p_gui_input) {
set_caret_column(col);
selection.drag_attempt = false;
- if (mb->is_shift_pressed() && (caret.column != prev_col || caret.line != prev_line)) {
+ if (selecting_enabled && mb->is_shift_pressed() && (caret.column != prev_col || caret.line != prev_line)) {
if (!selection.active) {
selection.active = true;
selection.selecting_mode = SelectionMode::SELECTION_MODE_POINTER;
@@ -1639,7 +1749,6 @@ void TextEdit::gui_input(const Ref<InputEvent> &p_gui_input) {
last_dblclk = OS::get_singleton()->get_ticks_msec();
last_dblclk_pos = mb->get_position();
}
-
update();
}
@@ -1647,7 +1756,7 @@ void TextEdit::gui_input(const Ref<InputEvent> &p_gui_input) {
paste_primary_clipboard();
}
- if (mb->get_button_index() == MouseButton::RIGHT && context_menu_enabled) {
+ if (mb->get_button_index() == MouseButton::RIGHT && (context_menu_enabled || is_move_caret_on_right_click_enabled())) {
_reset_caret_blink_timer();
Point2i pos = get_line_column_at_pos(mpos);
@@ -1672,11 +1781,13 @@ void TextEdit::gui_input(const Ref<InputEvent> &p_gui_input) {
}
}
- _generate_context_menu();
- menu->set_position(get_screen_position() + mpos);
- menu->reset_size();
- menu->popup();
- grab_focus();
+ if (context_menu_enabled) {
+ _generate_context_menu();
+ menu->set_position(get_screen_position() + mpos);
+ menu->reset_size();
+ menu->popup();
+ grab_focus();
+ }
}
} else {
if (mb->get_button_index() == MouseButton::LEFT) {
@@ -1795,6 +1906,10 @@ void TextEdit::gui_input(const Ref<InputEvent> &p_gui_input) {
Ref<InputEventKey> k = p_gui_input;
if (k.is_valid()) {
+ if (alt_input(p_gui_input)) {
+ accept_event();
+ return;
+ }
if (!k->is_pressed()) {
return;
}
@@ -1875,44 +1990,45 @@ void TextEdit::gui_input(const Ref<InputEvent> &p_gui_input) {
return;
}
- // SELECT ALL, SELECT WORD UNDER CARET, CUT, COPY, PASTE.
-
- if (k->is_action("ui_text_select_all", true)) {
- select_all();
- accept_event();
- return;
- }
- if (k->is_action("ui_text_select_word_under_caret", true)) {
- select_word_under_caret();
- accept_event();
- return;
- }
- if (k->is_action("ui_cut", true)) {
- cut();
- accept_event();
- return;
- }
- if (k->is_action("ui_copy", true)) {
- copy();
- accept_event();
- return;
- }
- if (k->is_action("ui_paste", true)) {
- paste();
- accept_event();
- return;
- }
+ if (is_shortcut_keys_enabled()) {
+ // SELECT ALL, SELECT WORD UNDER CARET, CUT, COPY, PASTE.
+ if (k->is_action("ui_text_select_all", true)) {
+ select_all();
+ accept_event();
+ return;
+ }
+ if (k->is_action("ui_text_select_word_under_caret", true)) {
+ select_word_under_caret();
+ accept_event();
+ return;
+ }
+ if (k->is_action("ui_cut", true)) {
+ cut();
+ accept_event();
+ return;
+ }
+ if (k->is_action("ui_copy", true)) {
+ copy();
+ accept_event();
+ return;
+ }
+ if (k->is_action("ui_paste", true)) {
+ paste();
+ accept_event();
+ return;
+ }
- // UNDO/REDO.
- if (k->is_action("ui_undo", true)) {
- undo();
- accept_event();
- return;
- }
- if (k->is_action("ui_redo", true)) {
- redo();
- accept_event();
- return;
+ // UNDO/REDO.
+ if (k->is_action("ui_undo", true)) {
+ undo();
+ accept_event();
+ return;
+ }
+ if (k->is_action("ui_redo", true)) {
+ redo();
+ accept_event();
+ return;
+ }
}
// MISC.
@@ -2045,6 +2161,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);
@@ -2083,16 +2200,21 @@ void TextEdit::_move_caret_left(bool p_select, bool p_move_by_word) {
if (p_move_by_word) {
int cc = caret.column;
-
+ // If the caret is at the start of the line, and not on the first line, move it up to the end of the previous line.
if (cc == 0 && caret.line > 0) {
set_caret_line(caret.line - 1);
set_caret_column(text[caret.line].length());
} else {
PackedInt32Array words = TS->shaped_text_get_word_breaks(text.get_line_data(caret.line)->get_rid());
- for (int i = words.size() - 2; i >= 0; i = i - 2) {
- if (words[i] < cc) {
- cc = words[i];
- break;
+ if (words.is_empty() || cc <= words[0]) {
+ // This solves the scenario where there are no words but glyfs that can be ignored.
+ cc = 0;
+ } else {
+ for (int i = words.size() - 2; i >= 0; i = i - 2) {
+ if (words[i] < cc) {
+ cc = words[i];
+ break;
+ }
}
}
set_caret_column(cc);
@@ -2134,16 +2256,21 @@ void TextEdit::_move_caret_right(bool p_select, bool p_move_by_word) {
if (p_move_by_word) {
int cc = caret.column;
-
+ // If the caret is at the end of the line, and not on the last line, move it down to the beginning of the next line.
if (cc == text[caret.line].length() && caret.line < text.size() - 1) {
set_caret_line(caret.line + 1);
set_caret_column(0);
} else {
PackedInt32Array words = TS->shaped_text_get_word_breaks(text.get_line_data(caret.line)->get_rid());
- for (int i = 1; i < words.size(); i = i + 2) {
- if (words[i] > cc) {
- cc = words[i];
- break;
+ if (words.is_empty() || cc >= words[words.size() - 1]) {
+ // This solves the scenario where there are no words but glyfs that can be ignored.
+ cc = text[caret.line].length();
+ } else {
+ for (int i = 1; i < words.size(); i = i + 2) {
+ if (words[i] > cc) {
+ cc = words[i];
+ break;
+ }
}
}
set_caret_column(cc);
@@ -2233,15 +2360,7 @@ void TextEdit::_move_caret_to_line_start(bool p_select) {
}
if (caret.column == row_start_col || wi == 0) {
// Compute whitespace symbols sequence length.
- int current_line_whitespace_len = 0;
- while (current_line_whitespace_len < text[caret.line].length()) {
- char32_t c = text[caret.line][current_line_whitespace_len];
- if (c != '\t' && c != ' ') {
- break;
- }
- current_line_whitespace_len++;
- }
-
+ int current_line_whitespace_len = get_first_non_whitespace_column(caret.line);
if (get_caret_column() == current_line_whitespace_len) {
set_caret_column(0);
} else {
@@ -2314,37 +2433,47 @@ void TextEdit::_move_caret_page_down(bool p_select) {
}
void TextEdit::_do_backspace(bool p_word, bool p_all_to_left) {
- if (!editable) {
+ if (!editable || (caret.column == 0 && caret.line == 0 && !has_selection())) {
return;
}
- if (has_selection() || (!p_all_to_left && !p_word)) {
+ if (has_selection() || (!p_all_to_left && !p_word) || caret.column == 0) {
backspace();
return;
}
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;
}
if (p_word) {
- int line = caret.line;
int column = caret.column;
-
- PackedInt32Array words = TS->shaped_text_get_word_breaks(text.get_line_data(line)->get_rid());
- for (int i = words.size() - 2; i >= 0; i = i - 2) {
- if (words[i] < column) {
- column = words[i];
- break;
+ // Check for the case "<word><space><caret>" and ignore the space.
+ // No need to check for column being 0 since it is checked above.
+ if (is_whitespace(text[caret.line][caret.column - 1])) {
+ column -= 1;
+ }
+ // Get a list with the indices of the word bounds of the given text line.
+ const PackedInt32Array words = TS->shaped_text_get_word_breaks(text.get_line_data(caret.line)->get_rid());
+ if (words.is_empty() || column <= words[0]) {
+ // If "words" is empty, meaning no words are left, we can remove everything until the beginning of the line.
+ column = 0;
+ } else {
+ // Otherwise search for the first word break that is smaller than the index from which we're currently deleting.
+ for (int i = words.size() - 2; i >= 0; i = i - 2) {
+ if (words[i] < column) {
+ column = words[i];
+ break;
+ }
}
}
- _remove_text(line, column, caret.line, caret.column);
+ _remove_text(caret.line, column, caret.line, caret.column);
- set_caret_line(line, false);
+ set_caret_line(caret.line, false);
set_caret_column(column);
return;
}
@@ -2369,6 +2498,10 @@ void TextEdit::_delete(bool p_word, bool p_all_to_right) {
int next_column;
if (p_all_to_right) {
+ if (caret.column == curline_len) {
+ return;
+ }
+
// Delete everything to right of caret
next_column = curline_len;
next_line = caret.line;
@@ -2430,6 +2563,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"));
@@ -2460,6 +2634,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"));
@@ -2482,7 +2657,8 @@ void TextEdit::_update_caches() {
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()) {
@@ -2598,8 +2774,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);
@@ -2612,19 +2787,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 */
@@ -2661,7 +2837,8 @@ void TextEdit::set_text_direction(Control::TextDirection p_text_direction) {
dir = (TextServer::Direction)text_direction;
}
text.set_direction_and_language(dir, (!language.is_empty()) ? language : TranslationServer::get_singleton()->get_tool_locale());
- text.invalidate_all();
+ 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);
@@ -2682,7 +2859,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();
}
}
@@ -2698,7 +2876,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();
}
@@ -2713,6 +2892,7 @@ void TextEdit::set_language(const String &p_language) {
}
text.set_direction_and_language(dir, (!language.is_empty()) ? language : TranslationServer::get_singleton()->get_tool_locale());
text.invalidate_all();
+ _update_placeholder();
update();
}
}
@@ -2721,7 +2901,7 @@ String TextEdit::get_language() const {
return language;
}
-void TextEdit::set_structured_text_bidi_override(Control::StructuredTextParser p_parser) {
+void TextEdit::set_structured_text_bidi_override(TextServer::StructuredTextParser p_parser) {
if (st_parser != p_parser) {
st_parser = p_parser;
for (int i = 0; i < text.size(); i++) {
@@ -2731,7 +2911,7 @@ void TextEdit::set_structured_text_bidi_override(Control::StructuredTextParser p
}
}
-Control::StructuredTextParser TextEdit::get_structured_text_bidi_override() const {
+TextServer::StructuredTextParser TextEdit::get_structured_text_bidi_override() const {
return st_parser;
}
@@ -2754,6 +2934,7 @@ void TextEdit::set_tab_size(const int p_size) {
}
text.set_tab_size(p_size);
text.invalidate_all_lines();
+ _update_placeholder();
update();
}
@@ -2812,15 +2993,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) {
@@ -2865,18 +3062,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(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 {
@@ -2919,7 +3128,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;
@@ -2931,8 +3140,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) {
@@ -2941,7 +3152,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) {
@@ -2953,6 +3164,7 @@ void TextEdit::insert_line_at(int p_at, const String &p_text) {
++selection.to_line;
}
}
+ update();
}
void TextEdit::insert_text_at_caret(const String &p_text) {
@@ -3333,9 +3545,6 @@ void TextEdit::undo() {
TextOperation op = undo_stack_pos->get();
_do_text_op(op, true);
- if (op.type != TextOperation::TYPE_INSERT && (op.from_line != op.to_line || op.to_column != op.from_column + 1)) {
- select(op.from_line, op.from_column, op.to_line, op.to_column);
- }
current_op.version = op.prev_version;
if (undo_stack_pos->get().chain_backward) {
@@ -3351,6 +3560,10 @@ void TextEdit::undo() {
}
}
+ if (op.type != TextOperation::TYPE_INSERT && (op.from_line != op.to_line || op.to_column != op.from_column + 1)) {
+ select(op.from_line, op.from_column, op.to_line, op.to_column);
+ }
+
_update_scrollbars();
if (undo_stack_pos->get().type == TextOperation::TYPE_REMOVE) {
set_caret_line(undo_stack_pos->get().to_line, false);
@@ -3504,9 +3717,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;
}
}
@@ -3647,7 +3860,7 @@ Point2i TextEdit::get_line_column_at_pos(const Point2i &p_pos, bool p_allow_out_
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());
+ return rect.position.x == -1 ? rect.position : rect.position + Vector2i(0, get_line_height());
}
Rect2i TextEdit::get_rect_at_line_column(int p_line, int p_column) const {
@@ -3846,6 +4059,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);
@@ -3859,15 +4073,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");
}
@@ -3883,19 +4098,20 @@ void TextEdit::set_caret_column(int p_col, bool p_adjust_viewport) {
if (p_col < 0) {
p_col = 0;
}
+ if (p_col > get_line(caret.line).length()) {
+ p_col = get_line(caret.line).length();
+ }
+ 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();
- }
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");
}
@@ -4016,13 +4232,18 @@ void TextEdit::select_word_under_caret() {
int end = 0;
const PackedInt32Array words = TS->shaped_text_get_word_breaks(text.get_line_data(caret.line)->get_rid());
for (int i = 0; i < words.size(); i = i + 2) {
- if ((words[i] < caret.column && words[i + 1] > caret.column) || (i == words.size() - 2 && caret.column == words[i + 1])) {
+ if ((words[i] <= caret.column && words[i + 1] >= caret.column) || (i == words.size() - 2 && caret.column == words[i + 1])) {
begin = words[i];
end = words[i + 1];
break;
}
}
+ // No word found.
+ if (begin == 0 && end == 0) {
+ return;
+ }
+
select(caret.line, begin, caret.line, end);
/* Move the caret to the end of the word for easier editing. */
set_caret_column(end, false);
@@ -4098,10 +4319,12 @@ String TextEdit::get_selected_text() const {
}
int TextEdit::get_selection_line() const {
+ ERR_FAIL_COND_V(!selection.active, -1);
return selection.selecting_line;
}
int TextEdit::get_selection_column() const {
+ ERR_FAIL_COND_V(!selection.active, -1);
return selection.selecting_column;
}
@@ -4259,6 +4482,8 @@ int TextEdit::get_h_scroll() const {
}
void TextEdit::set_v_scroll_speed(float p_speed) {
+ // Prevent setting a vertical scroll speed value under 1.0
+ ERR_FAIL_COND(p_speed < 1.0);
v_scroll_speed = p_speed;
}
@@ -4301,9 +4526,13 @@ void TextEdit::set_line_as_center_visible(int p_line, int p_wrap_index) {
ERR_FAIL_COND(p_wrap_index > get_line_wrap_count(p_line));
int visible_rows = get_visible_line_count();
- Point2i next_line = get_next_visible_line_index_offset_from(p_line, p_wrap_index, -visible_rows / 2);
+ Point2i next_line = get_next_visible_line_index_offset_from(p_line, p_wrap_index, (-visible_rows / 2) - 1);
int first_line = p_line - next_line.x + 1;
+ if (first_line < 0) {
+ set_v_scroll(0);
+ return;
+ }
set_v_scroll(get_scroll_pos_for_line(first_line, next_line.y));
}
@@ -4315,6 +4544,12 @@ void TextEdit::set_line_as_last_visible(int p_line, int p_wrap_index) {
Point2i next_line = get_next_visible_line_index_offset_from(p_line, p_wrap_index, -get_visible_line_count() - 1);
int first_line = p_line - next_line.x + 1;
+ // Adding _get_visible_lines_offset is not 100% correct as we end up showing almost p_line + 1, however, it provides a
+ // better user experience. Therefore we need to special case < visible line count, else showing line 0 is impossible.
+ if (get_visible_line_count_in_range(0, p_line) < get_visible_line_count() + 1) {
+ set_v_scroll(0);
+ return;
+ }
set_v_scroll(get_scroll_pos_for_line(first_line, next_line.y) + _get_visible_lines_offset());
}
@@ -4338,7 +4573,11 @@ int TextEdit::get_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);
- ERR_FAIL_COND_V(p_from_line > p_to_line, 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) {
@@ -4517,9 +4756,7 @@ void TextEdit::add_gutter(int p_at) {
gutters.insert(p_at, GutterInfo());
}
- for (int i = 0; i < text.size() + 1; i++) {
- text.add_gutter(p_at);
- }
+ text.add_gutter(p_at);
emit_signal(SNAME("gutter_added"));
update();
}
@@ -4529,9 +4766,7 @@ void TextEdit::remove_gutter(int p_gutter) {
gutters.remove_at(p_gutter);
- for (int i = 0; i < text.size() + 1; i++) {
- text.remove_gutter(p_gutter);
- }
+ text.remove_gutter(p_gutter);
emit_signal(SNAME("gutter_removed"));
update();
}
@@ -4647,12 +4882,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();
}
@@ -4771,7 +5004,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();
}
}
@@ -4851,6 +5085,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);
@@ -4942,7 +5179,7 @@ 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);
@@ -5048,7 +5285,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);
@@ -5114,7 +5351,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.
@@ -5159,8 +5396,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");
@@ -5228,19 +5466,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();
}
}
@@ -5270,7 +5510,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));
}
@@ -5410,7 +5650,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) {
@@ -5528,8 +5768,10 @@ void TextEdit::_generate_context_menu() {
if (editable) {
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()) {
+ 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) {
@@ -5656,9 +5898,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;
}
}
@@ -5713,7 +5955,7 @@ int TextEdit::_get_column_x_offset_for_line(int p_char, int p_line) const {
int row = 0;
Vector<Vector2i> rows2 = text.get_line_wrap_ranges(p_line);
for (int i = 0; i < rows2.size(); i++) {
- if ((p_char >= rows2[i].x) && (p_char < rows2[i].y)) {
+ if ((p_char >= rows2[i].x) && (p_char <= rows2[i].y)) {
row = i;
break;
}
@@ -5797,8 +6039,8 @@ void TextEdit::_update_selection_mode_word() {
selection.selected_word_beg = beg;
selection.selected_word_end = end;
selection.selected_word_origin = beg;
- set_caret_line(selection.to_line, false);
- set_caret_column(selection.to_column);
+ set_caret_line(line, false);
+ set_caret_column(end);
} else {
if ((col <= selection.selected_word_origin && line == selection.selecting_line) || line < selection.selecting_line) {
selection.selecting_column = selection.selected_word_end;
@@ -5854,6 +6096,10 @@ void TextEdit::_update_selection_mode_line() {
}
void TextEdit::_pre_shift_selection() {
+ if (!selecting_enabled) {
+ return;
+ }
+
if (!selection.active || selection.selecting_mode == SelectionMode::SELECTION_MODE_NONE) {
selection.selecting_line = caret.line;
selection.selecting_column = caret.column;
@@ -5864,6 +6110,10 @@ void TextEdit::_pre_shift_selection() {
}
void TextEdit::_post_shift_selection() {
+ if (!selecting_enabled) {
+ return;
+ }
+
if (selection.active && selection.selecting_mode == SelectionMode::SELECTION_MODE_SHIFT) {
select(selection.selecting_line, selection.selecting_column, caret.line, caret.column);
update();
@@ -5892,6 +6142,7 @@ void TextEdit::_update_wrap_at_column(bool p_force) {
text.set_width(-1);
}
text.invalidate_all_lines();
+ _update_placeholder();
}
_update_caret_wrap_offset();
@@ -5919,14 +6170,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;
@@ -5949,6 +6202,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();
}
@@ -5966,6 +6220,7 @@ void TextEdit::_update_scrollbars() {
} else {
caret.x_ofs = 0;
h_scroll->set_value(0);
+ h_scroll->set_max(0);
h_scroll->hide();
}
@@ -5996,20 +6251,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);
@@ -6436,11 +6693,14 @@ 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();
set_default_cursor_shape(CURSOR_IBEAM);
+ set_process_unhandled_key_input(true);
text.set_tab_size(text.get_tab_size());
@@ -6476,5 +6736,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 df3edca943..993203bee6 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 {
@@ -190,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;
@@ -210,7 +210,8 @@ private:
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();
@@ -246,10 +247,24 @@ private:
bool setting_text = false;
+ bool alt_start = false;
+ uint32_t alt_code = 0;
+
// Text properties.
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;
@@ -259,7 +274,7 @@ private:
Dictionary opentype_features;
String language = "";
- Control::StructuredTextParser st_parser = STRUCTURED_TEXT_DEFAULT;
+ TextServer::StructuredTextParser st_parser = TextServer::STRUCTURED_TEXT_DEFAULT;
Array st_args;
void _clear();
@@ -313,7 +328,7 @@ private:
List<TextOperation> undo_stack;
List<TextOperation>::Element *undo_stack_pos = nullptr;
- Timer *idle_detect;
+ Timer *idle_detect = nullptr;
uint32_t version = 0;
uint32_t saved_version = 0;
@@ -332,9 +347,7 @@ 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 {
@@ -343,7 +356,7 @@ private:
Vector<int> last_visible_chars;
};
- Map<int, LineDrawingCache> line_drawing_cache;
+ HashMap<int, LineDrawingCache> line_drawing_cache;
int _get_char_pos_for_line(int p_px, int p_line, int p_wrap_index = 0) const;
@@ -370,11 +383,11 @@ private:
bool draw_caret = true;
bool caret_blink_enabled = false;
- Timer *caret_blink_timer;
+ Timer *caret_blink_timer = nullptr;
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;
@@ -416,7 +429,7 @@ private:
bool dragging_selection = false;
- Timer *click_select_held;
+ Timer *click_select_held = nullptr;
uint64_t last_dblclk = 0;
Vector2 last_dblclk_pos;
void _click_selection_held();
@@ -439,8 +452,8 @@ private:
void _update_caret_wrap_offset();
/* Viewport. */
- HScrollBar *h_scroll;
- VScrollBar *v_scroll;
+ HScrollBar *h_scroll = nullptr;
+ VScrollBar *v_scroll = nullptr;
bool scroll_past_end_of_file_enabled = false;
@@ -498,7 +511,6 @@ private:
/* Syntax highlighting. */
Ref<SyntaxHighlighter> syntax_highlighter;
- Map<int, Dictionary> syntax_highlighting_cache;
Dictionary _get_line_syntax_highlighting(int p_line);
@@ -514,6 +526,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);
@@ -612,7 +625,9 @@ protected:
public:
/* General overrides. */
+ virtual void unhandled_key_input(const Ref<InputEvent> &p_event) override;
virtual void gui_input(const Ref<InputEvent> &p_gui_input) override;
+ bool alt_input(const Ref<InputEvent> &p_gui_input);
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;
@@ -620,7 +635,7 @@ public:
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.
@@ -639,8 +654,8 @@ public:
void set_language(const String &p_language);
String get_language() const;
- void set_structured_text_bidi_override(Control::StructuredTextParser p_parser);
- Control::StructuredTextParser get_structured_text_bidi_override() const;
+ void set_structured_text_bidi_override(TextServer::StructuredTextParser p_parser);
+ TextServer::StructuredTextParser get_structured_text_bidi_override() const;
void set_structured_text_bidi_override_options(Array p_args);
Array get_structured_text_bidi_override_options() const;
@@ -670,6 +685,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;
@@ -884,7 +902,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);
@@ -926,7 +944,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 3f0d907a7e..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");
@@ -343,12 +350,12 @@ 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;
+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 6a926a0364..081d065efe 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 */
@@ -629,26 +629,30 @@ void TextureProgressBar::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_nine_patch_stretch", "stretch"), &TextureProgressBar::set_nine_patch_stretch);
ClassDB::bind_method(D_METHOD("get_nine_patch_stretch"), &TextureProgressBar::get_nine_patch_stretch);
+ ADD_PROPERTY(PropertyInfo(Variant::INT, "fill_mode", PROPERTY_HINT_ENUM, "Left to Right,Right to Left,Top to Bottom,Bottom to Top,Clockwise,Counter Clockwise,Bilinear (Left and Right),Bilinear (Top and Bottom),Clockwise and Counter Clockwise"), "set_fill_mode", "get_fill_mode");
+ ADD_PROPERTY(PropertyInfo(Variant::BOOL, "nine_patch_stretch"), "set_nine_patch_stretch", "get_nine_patch_stretch");
+
+ ADD_GROUP("Stretch Margin", "stretch_margin_");
+ ADD_PROPERTYI(PropertyInfo(Variant::INT, "stretch_margin_left", PROPERTY_HINT_RANGE, "0,16384,1"), "set_stretch_margin", "get_stretch_margin", SIDE_LEFT);
+ ADD_PROPERTYI(PropertyInfo(Variant::INT, "stretch_margin_top", PROPERTY_HINT_RANGE, "0,16384,1"), "set_stretch_margin", "get_stretch_margin", SIDE_TOP);
+ ADD_PROPERTYI(PropertyInfo(Variant::INT, "stretch_margin_right", PROPERTY_HINT_RANGE, "0,16384,1"), "set_stretch_margin", "get_stretch_margin", SIDE_RIGHT);
+ ADD_PROPERTYI(PropertyInfo(Variant::INT, "stretch_margin_bottom", PROPERTY_HINT_RANGE, "0,16384,1"), "set_stretch_margin", "get_stretch_margin", SIDE_BOTTOM);
+
ADD_GROUP("Textures", "texture_");
ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "texture_under", PROPERTY_HINT_RESOURCE_TYPE, "Texture2D"), "set_under_texture", "get_under_texture");
ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "texture_over", PROPERTY_HINT_RESOURCE_TYPE, "Texture2D"), "set_over_texture", "get_over_texture");
ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "texture_progress", PROPERTY_HINT_RESOURCE_TYPE, "Texture2D"), "set_progress_texture", "get_progress_texture");
ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "texture_progress_offset"), "set_texture_progress_offset", "get_texture_progress_offset");
- ADD_PROPERTY(PropertyInfo(Variant::INT, "fill_mode", PROPERTY_HINT_ENUM, "Left to Right,Right to Left,Top to Bottom,Bottom to Top,Clockwise,Counter Clockwise,Bilinear (Left and Right),Bilinear (Top and Bottom),Clockwise and Counter Clockwise"), "set_fill_mode", "get_fill_mode");
+
ADD_GROUP("Tint", "tint_");
ADD_PROPERTY(PropertyInfo(Variant::COLOR, "tint_under"), "set_tint_under", "get_tint_under");
ADD_PROPERTY(PropertyInfo(Variant::COLOR, "tint_over"), "set_tint_over", "get_tint_over");
ADD_PROPERTY(PropertyInfo(Variant::COLOR, "tint_progress"), "set_tint_progress", "get_tint_progress");
+
ADD_GROUP("Radial Fill", "radial_");
- ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "radial_initial_angle", PROPERTY_HINT_RANGE, "0.0,360.0,0.1,slider"), "set_radial_initial_angle", "get_radial_initial_angle");
- ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "radial_fill_degrees", PROPERTY_HINT_RANGE, "0.0,360.0,0.1,slider"), "set_fill_degrees", "get_fill_degrees");
+ ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "radial_initial_angle", PROPERTY_HINT_RANGE, "0.0,360.0,0.1,slider,degrees"), "set_radial_initial_angle", "get_radial_initial_angle");
+ ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "radial_fill_degrees", PROPERTY_HINT_RANGE, "0.0,360.0,0.1,slider,degrees"), "set_fill_degrees", "get_fill_degrees");
ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "radial_center_offset"), "set_radial_center_offset", "get_radial_center_offset");
- ADD_GROUP("Stretch", "stretch_");
- ADD_PROPERTY(PropertyInfo(Variant::BOOL, "nine_patch_stretch"), "set_nine_patch_stretch", "get_nine_patch_stretch");
- ADD_PROPERTYI(PropertyInfo(Variant::INT, "stretch_margin_left", PROPERTY_HINT_RANGE, "0,16384,1"), "set_stretch_margin", "get_stretch_margin", SIDE_LEFT);
- ADD_PROPERTYI(PropertyInfo(Variant::INT, "stretch_margin_top", PROPERTY_HINT_RANGE, "0,16384,1"), "set_stretch_margin", "get_stretch_margin", SIDE_TOP);
- ADD_PROPERTYI(PropertyInfo(Variant::INT, "stretch_margin_right", PROPERTY_HINT_RANGE, "0,16384,1"), "set_stretch_margin", "get_stretch_margin", SIDE_RIGHT);
- ADD_PROPERTYI(PropertyInfo(Variant::INT, "stretch_margin_bottom", PROPERTY_HINT_RANGE, "0,16384,1"), "set_stretch_margin", "get_stretch_margin", SIDE_BOTTOM);
BIND_ENUM_CONSTANT(FILL_LEFT_TO_RIGHT);
BIND_ENUM_CONSTANT(FILL_RIGHT_TO_LEFT);
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 85c15cdae7..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);
@@ -179,14 +178,14 @@ 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();
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 5a6ac7c0d2..d3e7540790 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 {
@@ -102,6 +101,7 @@ void TreeItem::_change_tree(Tree *p_tree) {
if (tree->popup_edited_item == this) {
tree->popup_edited_item = nullptr;
+ tree->popup_pressing_edited_item = nullptr;
tree->pressing_for_editor = false;
}
@@ -198,6 +198,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;
@@ -275,7 +334,7 @@ int TreeItem::get_opentype_feature(int p_column, const String &p_name) const {
return cells[p_column].opentype_features[tag];
}
-void TreeItem::set_structured_text_bidi_override(int p_column, Control::StructuredTextParser p_parser) {
+void TreeItem::set_structured_text_bidi_override(int p_column, TextServer::StructuredTextParser p_parser) {
ERR_FAIL_INDEX(p_column, cells.size());
if (cells[p_column].st_parser != p_parser) {
@@ -287,8 +346,8 @@ void TreeItem::set_structured_text_bidi_override(int p_column, Control::Structur
}
}
-Control::StructuredTextParser TreeItem::get_structured_text_bidi_override(int p_column) const {
- ERR_FAIL_INDEX_V(p_column, cells.size(), Control::STRUCTURED_TEXT_NONE);
+TextServer::StructuredTextParser TreeItem::get_structured_text_bidi_override(int p_column) const {
+ ERR_FAIL_INDEX_V(p_column, cells.size(), TextServer::STRUCTURED_TEXT_NONE);
return cells[p_column].st_parser;
}
@@ -485,6 +544,21 @@ bool TreeItem::is_collapsed() {
return collapsed;
}
+void TreeItem::set_visible(bool p_visible) {
+ if (visible == p_visible) {
+ return;
+ }
+ visible = p_visible;
+ if (tree) {
+ tree->update();
+ _changed_notify();
+ }
+}
+
+bool TreeItem::is_visible() {
+ return visible;
+}
+
void TreeItem::uncollapse_tree() {
TreeItem *t = this;
while (t) {
@@ -496,8 +570,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();
}
@@ -586,7 +661,7 @@ TreeItem *TreeItem::get_first_child() const {
return first_child;
}
-TreeItem *TreeItem::get_prev_visible(bool p_wrap) {
+TreeItem *TreeItem::_get_prev_visible(bool p_wrap) {
TreeItem *current = this;
TreeItem *prev = current->get_prev();
@@ -622,7 +697,21 @@ TreeItem *TreeItem::get_prev_visible(bool p_wrap) {
return current;
}
-TreeItem *TreeItem::get_next_visible(bool p_wrap) {
+TreeItem *TreeItem::get_prev_visible(bool p_wrap) {
+ TreeItem *loop = this;
+ TreeItem *prev = this->_get_prev_visible(p_wrap);
+ while (prev && !prev->is_visible()) {
+ prev = prev->_get_prev_visible(p_wrap);
+ if (prev == loop) {
+ // Check that we haven't looped all the way around to the start.
+ prev = nullptr;
+ break;
+ }
+ }
+ return prev;
+}
+
+TreeItem *TreeItem::_get_next_visible(bool p_wrap) {
TreeItem *current = this;
if (!current->collapsed && current->first_child) {
@@ -649,12 +738,37 @@ TreeItem *TreeItem::get_next_visible(bool p_wrap) {
return current;
}
+TreeItem *TreeItem::get_next_visible(bool p_wrap) {
+ TreeItem *loop = this;
+ TreeItem *next = this->_get_next_visible(p_wrap);
+ while (next && !next->is_visible()) {
+ next = next->_get_next_visible(p_wrap);
+ if (next == loop) {
+ // Check that we haven't looped all the way around to the start.
+ next = nullptr;
+ break;
+ }
+ }
+ return next;
+}
+
TreeItem *TreeItem::get_child(int p_idx) {
_create_children_cache();
ERR_FAIL_INDEX_V(p_idx, children_cache.size(), nullptr);
return children_cache.get(p_idx);
}
+int TreeItem::get_visible_child_count() {
+ _create_children_cache();
+ int visible_count = 0;
+ for (int i = 0; i < children_cache.size(); i++) {
+ if (children_cache[i]->is_visible()) {
+ visible_count += 1;
+ }
+ }
+ return visible_count;
+}
+
int TreeItem::get_child_count() {
_create_children_cache();
return children_cache.size();
@@ -845,6 +959,12 @@ 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());
@@ -1033,8 +1153,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);
}
@@ -1096,31 +1217,30 @@ Size2 TreeItem::get_minimum_size(int p_column) {
return cell.cached_minimum_size;
}
-Variant TreeItem::_call_recursive_bind(const Variant **p_args, int p_argcount, Callable::CallError &r_error) {
+void TreeItem::_call_recursive_bind(const Variant **p_args, int p_argcount, Callable::CallError &r_error) {
if (p_argcount < 1) {
r_error.error = Callable::CallError::CALL_ERROR_TOO_FEW_ARGUMENTS;
r_error.argument = 0;
- return Variant();
+ return;
}
if (p_args[0]->get_type() != Variant::STRING && p_args[0]->get_type() != Variant::STRING_NAME) {
r_error.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT;
r_error.argument = 0;
r_error.expected = Variant::STRING_NAME;
- return Variant();
+ return;
}
StringName method = *p_args[0];
call_recursive(method, &p_args[1], p_argcount - 1, r_error);
- return Variant();
}
void recursive_call_aux(TreeItem *p_item, const StringName &p_method, const Variant **p_args, int p_argcount, Callable::CallError &r_error) {
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 +1261,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);
@@ -1188,6 +1310,9 @@ void TreeItem::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_collapsed", "enable"), &TreeItem::set_collapsed);
ClassDB::bind_method(D_METHOD("is_collapsed"), &TreeItem::is_collapsed);
+ ClassDB::bind_method(D_METHOD("set_visible", "enable"), &TreeItem::set_visible);
+ ClassDB::bind_method(D_METHOD("is_visible"), &TreeItem::is_visible);
+
ClassDB::bind_method(D_METHOD("uncollapse_tree"), &TreeItem::uncollapse_tree);
ClassDB::bind_method(D_METHOD("set_custom_minimum_height", "height"), &TreeItem::set_custom_minimum_height);
@@ -1220,9 +1345,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);
@@ -1256,10 +1383,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;
@@ -1270,6 +1397,7 @@ void TreeItem::_bind_methods() {
}
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "collapsed"), "set_collapsed", "is_collapsed");
+ ADD_PROPERTY(PropertyInfo(Variant::BOOL, "visible"), "set_visible", "is_visible");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "disable_folding"), "set_disable_folding", "is_folding_disabled");
ADD_PROPERTY(PropertyInfo(Variant::INT, "custom_minimum_height", PROPERTY_HINT_RANGE, "0,1000,1"), "set_custom_minimum_height", "get_custom_minimum_height");
@@ -1342,8 +1470,8 @@ void Tree::update_cache() {
cache.font_color = get_theme_color(SNAME("font_color"));
cache.font_selected_color = get_theme_color(SNAME("font_selected_color"));
cache.drop_position_color = get_theme_color(SNAME("drop_position_color"));
- cache.hseparation = get_theme_constant(SNAME("hseparation"));
- cache.vseparation = get_theme_constant(SNAME("vseparation"));
+ cache.hseparation = get_theme_constant(SNAME("h_separation"));
+ cache.vseparation = get_theme_constant(SNAME("v_separation"));
cache.item_margin = get_theme_constant(SNAME("item_margin"));
cache.button_margin = get_theme_constant(SNAME("button_margin"));
@@ -1375,7 +1503,7 @@ void Tree::update_cache() {
}
int Tree::compute_item_height(TreeItem *p_item) const {
- if (p_item == root && hide_root) {
+ if ((p_item == root && hide_root) || !p_item->is_visible()) {
return 0;
}
@@ -1436,6 +1564,9 @@ int Tree::compute_item_height(TreeItem *p_item) const {
}
int Tree::get_item_height(TreeItem *p_item) const {
+ if (!p_item->is_visible()) {
+ return 0;
+ }
int height = compute_item_height(p_item);
height += cache.vseparation;
@@ -1616,6 +1747,10 @@ int Tree::draw_item(const Point2i &p_pos, const Point2 &p_draw_ofs, const Size2
return -1; //draw no more!
}
+ if (!p_item->is_visible()) {
+ return 0;
+ }
+
RID ci = get_canvas_item();
int htotal = 0;
@@ -1631,8 +1766,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);
@@ -1685,19 +1819,16 @@ int Tree::draw_item(const Point2i &p_pos, const Point2 &p_draw_ofs, const Size2
for (int j = p_item->cells[i].buttons.size() - 1; j >= 0; j--) {
Ref<Texture2D> b = p_item->cells[i].buttons[j].texture;
Size2 s = b->get_size() + cache.button_pressed->get_minimum_size();
- if (s.height < label_h) {
- s.height = label_h;
- }
Point2i o = Point2i(ofs + w - s.width, p_pos.y) - cache.offset + p_draw_ofs;
if (cache.click_type == Cache::CLICK_BUTTON && cache.click_item == p_item && cache.click_column == i && cache.click_index == j && !p_item->cells[i].buttons[j].disabled) {
- //being pressed
+ // Being pressed.
Point2 od = o;
if (rtl) {
od.x = get_size().width - od.x - s.x;
}
- cache.button_pressed->draw(get_canvas_item(), Rect2(od, s));
+ cache.button_pressed->draw(get_canvas_item(), Rect2(od.x, od.y, s.width, MAX(s.height, label_h)));
}
o.y += (label_h - s.height) / 2;
@@ -1990,7 +2121,7 @@ int Tree::draw_item(const Point2i &p_pos, const Point2 &p_draw_ofs, const Size2
}
}
- if (!p_item->disable_folding && !hide_folding && p_item->first_child) { //has children, draw the guide box
+ if (!p_item->disable_folding && !hide_folding && p_item->first_child && p_item->get_visible_child_count() != 0) { //has visible children, draw the guide box
Ref<Texture2D> arrow;
@@ -2020,7 +2151,6 @@ int Tree::draw_item(const Point2i &p_pos, const Point2 &p_draw_ofs, const Size2
}
if (!p_item->collapsed) { /* if not collapsed, check the children */
-
TreeItem *c = p_item->first_child;
int base_ofs = children_pos.y - cache.offset.y + p_draw_ofs.y;
@@ -2028,82 +2158,97 @@ int Tree::draw_item(const Point2i &p_pos, const Point2 &p_draw_ofs, const Size2
int prev_hl_ofs = base_ofs;
while (c) {
+ int child_h = -1;
if (htotal >= 0) {
- int child_h = draw_item(children_pos, p_draw_ofs, p_draw_size, c);
+ child_h = draw_item(children_pos, p_draw_ofs, p_draw_size, c);
+ }
- // Draw relationship lines.
- if (cache.draw_relationship_lines > 0 && (!hide_root || c->parent != root)) {
- int root_ofs = children_pos.x + ((p_item->disable_folding || hide_folding) ? cache.hseparation : cache.item_margin);
- int parent_ofs = p_pos.x + cache.item_margin;
- Point2i root_pos = Point2i(root_ofs, children_pos.y + label_h / 2) - cache.offset + p_draw_ofs;
+ // Draw relationship lines.
+ if (cache.draw_relationship_lines > 0 && (!hide_root || c->parent != root) && c->is_visible()) {
+ int root_ofs = children_pos.x + ((p_item->disable_folding || hide_folding) ? cache.hseparation : cache.item_margin);
+ int parent_ofs = p_pos.x + cache.item_margin;
+ Point2i root_pos = Point2i(root_ofs, children_pos.y + label_h / 2) - cache.offset + p_draw_ofs;
- if (c->get_first_child() != nullptr) {
- root_pos -= Point2i(cache.arrow->get_width(), 0);
- }
+ if (c->get_visible_child_count() > 0) {
+ root_pos -= Point2i(cache.arrow->get_width(), 0);
+ }
- float line_width = cache.relationship_line_width * Math::round(cache.base_scale);
- float parent_line_width = cache.parent_hl_line_width * Math::round(cache.base_scale);
- float children_line_width = cache.children_hl_line_width * Math::round(cache.base_scale);
+ float line_width = cache.relationship_line_width * Math::round(cache.base_scale);
+ float parent_line_width = cache.parent_hl_line_width * Math::round(cache.base_scale);
+ float children_line_width = cache.children_hl_line_width * Math::round(cache.base_scale);
- Point2i parent_pos = Point2i(parent_ofs - cache.arrow->get_width() / 2, p_pos.y + label_h / 2 + cache.arrow->get_height() / 2) - cache.offset + p_draw_ofs;
+ Point2i parent_pos = Point2i(parent_ofs - cache.arrow->get_width() / 2, p_pos.y + label_h / 2 + cache.arrow->get_height() / 2) - cache.offset + p_draw_ofs;
- int more_prev_ofs = 0;
+ int more_prev_ofs = 0;
- if (root_pos.y + line_width >= 0) {
- if (rtl) {
- root_pos.x = get_size().width - root_pos.x;
- parent_pos.x = get_size().width - parent_pos.x;
- }
+ if (root_pos.y + line_width >= 0) {
+ if (rtl) {
+ root_pos.x = get_size().width - root_pos.x;
+ parent_pos.x = get_size().width - parent_pos.x;
+ }
- // Order of parts on this bend: the horizontal line first, then the vertical line.
- if (_is_branch_selected(c)) {
- // If this item or one of its children is selected, we draw the line using parent highlight style.
+ // Order of parts on this bend: the horizontal line first, then the vertical line.
+ if (_is_branch_selected(c)) {
+ // If this item or one of its children is selected, we draw the line using parent highlight style.
+ if (htotal >= 0) {
RenderingServer::get_singleton()->canvas_item_add_line(ci, root_pos, Point2i(parent_pos.x + Math::floor(parent_line_width / 2), root_pos.y), cache.parent_hl_line_color, parent_line_width);
+ }
+ RenderingServer::get_singleton()->canvas_item_add_line(ci, Point2i(parent_pos.x, root_pos.y + Math::floor(parent_line_width / 2)), Point2i(parent_pos.x, prev_hl_ofs), cache.parent_hl_line_color, parent_line_width);
+
+ more_prev_ofs = cache.parent_hl_line_margin;
+ prev_hl_ofs = root_pos.y + Math::floor(parent_line_width / 2);
+ } else if (p_item->is_selected(0)) {
+ // If parent item is selected (but this item is not), we draw the line using children highlight style.
+ // Siblings of the selected branch can be drawn with a slight offset and their vertical line must appear as highlighted.
+ if (_is_sibling_branch_selected(c)) {
+ if (htotal >= 0) {
+ RenderingServer::get_singleton()->canvas_item_add_line(ci, root_pos, Point2i(parent_pos.x + Math::floor(parent_line_width / 2), root_pos.y), cache.children_hl_line_color, children_line_width);
+ }
RenderingServer::get_singleton()->canvas_item_add_line(ci, Point2i(parent_pos.x, root_pos.y + Math::floor(parent_line_width / 2)), Point2i(parent_pos.x, prev_hl_ofs), cache.parent_hl_line_color, parent_line_width);
- more_prev_ofs = cache.parent_hl_line_margin;
prev_hl_ofs = root_pos.y + Math::floor(parent_line_width / 2);
- } else if (p_item->is_selected(0)) {
- // If parent item is selected (but this item is not), we draw the line using children highlight style.
- // Siblings of the selected branch can be drawn with a slight offset and their vertical line must appear as highlighted.
- if (_is_sibling_branch_selected(c)) {
- RenderingServer::get_singleton()->canvas_item_add_line(ci, root_pos, Point2i(parent_pos.x + Math::floor(parent_line_width / 2), root_pos.y), cache.children_hl_line_color, children_line_width);
- RenderingServer::get_singleton()->canvas_item_add_line(ci, Point2i(parent_pos.x, root_pos.y + Math::floor(parent_line_width / 2)), Point2i(parent_pos.x, prev_hl_ofs), cache.parent_hl_line_color, parent_line_width);
-
- prev_hl_ofs = root_pos.y + Math::floor(parent_line_width / 2);
- } else {
+ } else {
+ if (htotal >= 0) {
RenderingServer::get_singleton()->canvas_item_add_line(ci, root_pos, Point2i(parent_pos.x + Math::floor(children_line_width / 2), root_pos.y), cache.children_hl_line_color, children_line_width);
- RenderingServer::get_singleton()->canvas_item_add_line(ci, Point2i(parent_pos.x, root_pos.y + Math::floor(children_line_width / 2)), Point2i(parent_pos.x, prev_ofs + Math::floor(children_line_width / 2)), cache.children_hl_line_color, children_line_width);
}
- } else {
- // If nothing of the above is true, we draw the line using normal style.
- // Siblings of the selected branch can be drawn with a slight offset and their vertical line must appear as highlighted.
- if (_is_sibling_branch_selected(c)) {
+ RenderingServer::get_singleton()->canvas_item_add_line(ci, Point2i(parent_pos.x, root_pos.y + Math::floor(children_line_width / 2)), Point2i(parent_pos.x, prev_ofs + Math::floor(children_line_width / 2)), cache.children_hl_line_color, children_line_width);
+ }
+ } else {
+ // If nothing of the above is true, we draw the line using normal style.
+ // Siblings of the selected branch can be drawn with a slight offset and their vertical line must appear as highlighted.
+ if (_is_sibling_branch_selected(c)) {
+ if (htotal >= 0) {
RenderingServer::get_singleton()->canvas_item_add_line(ci, root_pos, Point2i(parent_pos.x + cache.parent_hl_line_margin, root_pos.y), cache.relationship_line_color, line_width);
- RenderingServer::get_singleton()->canvas_item_add_line(ci, Point2i(parent_pos.x, root_pos.y + Math::floor(parent_line_width / 2)), Point2i(parent_pos.x, prev_hl_ofs), cache.parent_hl_line_color, parent_line_width);
+ }
+ RenderingServer::get_singleton()->canvas_item_add_line(ci, Point2i(parent_pos.x, root_pos.y + Math::floor(parent_line_width / 2)), Point2i(parent_pos.x, prev_hl_ofs), cache.parent_hl_line_color, parent_line_width);
- prev_hl_ofs = root_pos.y + Math::floor(parent_line_width / 2);
- } else {
+ prev_hl_ofs = root_pos.y + Math::floor(parent_line_width / 2);
+ } else {
+ if (htotal >= 0) {
RenderingServer::get_singleton()->canvas_item_add_line(ci, root_pos, Point2i(parent_pos.x + Math::floor(line_width / 2), root_pos.y), cache.relationship_line_color, line_width);
- RenderingServer::get_singleton()->canvas_item_add_line(ci, Point2i(parent_pos.x, root_pos.y + Math::floor(line_width / 2)), Point2i(parent_pos.x, prev_ofs + Math::floor(line_width / 2)), cache.relationship_line_color, line_width);
}
+ RenderingServer::get_singleton()->canvas_item_add_line(ci, Point2i(parent_pos.x, root_pos.y + Math::floor(line_width / 2)), Point2i(parent_pos.x, prev_ofs + Math::floor(line_width / 2)), cache.relationship_line_color, line_width);
}
}
-
- prev_ofs = root_pos.y + more_prev_ofs;
}
- if (child_h < 0) {
- if (cache.draw_relationship_lines == 0) {
- return -1; // break, stop drawing, no need to anymore
- }
+ prev_ofs = root_pos.y + more_prev_ofs;
+ }
- htotal = -1;
- children_pos.y = cache.offset.y + p_draw_size.height;
- } else {
- htotal += child_h;
- children_pos.y += child_h;
+ if (child_h < 0) {
+ if (htotal == -1) {
+ break; // Last loop done, stop.
+ }
+
+ if (cache.draw_relationship_lines == 0) {
+ return -1; // No need to draw anymore, full stop.
}
+
+ htotal = -1;
+ children_pos.y = cache.offset.y + p_draw_size.height;
+ } else {
+ htotal += child_h;
+ children_pos.y += child_h;
}
c = c->next;
@@ -2189,14 +2334,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) {
@@ -2305,6 +2447,11 @@ void Tree::_range_click_timeout() {
}
int Tree::propagate_mouse_event(const Point2i &p_pos, int x_ofs, int y_ofs, int x_limit, bool p_double_click, TreeItem *p_item, MouseButton p_button, const Ref<InputEventWithModifiers> &p_mod) {
+ if (p_item && !p_item->is_visible()) {
+ // Skip any processing of invisible items.
+ return 0;
+ }
+
int item_h = compute_item_height(p_item) + cache.vseparation;
bool skip = (p_item == root && hide_root);
@@ -2316,12 +2463,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;
@@ -2417,7 +2561,6 @@ int Tree::propagate_mouse_event(const Point2i &p_pos, int x_ofs, int y_ofs, int
cache.click_column = col;
cache.click_pos = click_pos;
update();
- //emit_signal(SNAME("button_pressed"));
return -1;
}
@@ -2428,7 +2571,7 @@ int Tree::propagate_mouse_event(const Point2i &p_pos, int x_ofs, int y_ofs, int
/* 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,9 +2582,7 @@ int Tree::propagate_mouse_event(const Point2i &p_pos, int x_ofs, int y_ofs, int
if (!c.selected || p_button == MouseButton::RIGHT) {
p_item->select(col);
emit_signal(SNAME("multi_selected"), p_item, col, true);
- if (p_button == MouseButton::RIGHT) {
- emit_signal(SNAME("item_rmb_selected"), get_local_mouse_position());
- }
+ emit_signal(SNAME("item_mouse_selected"), get_local_mouse_position(), p_button);
//p_item->selected_signal.call(col);
} else {
@@ -2456,9 +2597,7 @@ int Tree::propagate_mouse_event(const Point2i &p_pos, int x_ofs, int y_ofs, int
bool inrange = false;
select_single_item(p_item, root, col, selected_item, &inrange);
- if (p_button == MouseButton::RIGHT) {
- emit_signal(SNAME("item_rmb_selected"), get_local_mouse_position());
- }
+ emit_signal(SNAME("item_mouse_selected"), get_local_mouse_position(), p_button);
} else {
int icount = _count_selected_items(root);
@@ -2470,9 +2609,7 @@ int Tree::propagate_mouse_event(const Point2i &p_pos, int x_ofs, int y_ofs, int
select_single_item(p_item, root, col);
}
- if (p_button == MouseButton::RIGHT) {
- emit_signal(SNAME("item_rmb_selected"), get_local_mouse_position());
- }
+ emit_signal(SNAME("item_mouse_selected"), get_local_mouse_position(), p_button);
}
}
@@ -2509,11 +2646,11 @@ int Tree::propagate_mouse_event(const Point2i &p_pos, int x_ofs, int y_ofs, int
if (force_edit_checkbox_only_on_checkbox) {
if (x < cache.checked->get_width()) {
p_item->set_checked(col, !c.checked);
- item_edited(col, p_item);
+ item_edited(col, p_item, p_button);
}
} else {
p_item->set_checked(col, !c.checked);
- item_edited(col, p_item);
+ item_edited(col, p_item, p_button);
}
click_handled = true;
//p_item->edited_signal.call(col);
@@ -2555,17 +2692,17 @@ int Tree::propagate_mouse_event(const Point2i &p_pos, int x_ofs, int y_ofs, int
p_item->set_range(col, c.val + (up ? 1.0 : -1.0) * c.step);
- item_edited(col, p_item);
+ item_edited(col, p_item, p_button);
} else if (p_button == MouseButton::RIGHT) {
p_item->set_range(col, (up ? c.max : c.min));
- item_edited(col, p_item);
+ item_edited(col, p_item, p_button);
} else if (p_button == MouseButton::WHEEL_UP) {
p_item->set_range(col, c.val + c.step);
- item_edited(col, p_item);
+ item_edited(col, p_item, p_button);
} else if (p_button == MouseButton::WHEEL_DOWN) {
p_item->set_range(col, c.val - c.step);
- item_edited(col, p_item);
+ item_edited(col, p_item, p_button);
}
//p_item->edited_signal.call(col);
@@ -2596,7 +2733,7 @@ int Tree::propagate_mouse_event(const Point2i &p_pos, int x_ofs, int y_ofs, int
}
if (!p_item->cells[col].custom_button || !on_arrow) {
- item_edited(col, p_item, p_button == MouseButton::LEFT);
+ item_edited(col, p_item, p_button);
}
click_handled = true;
return -1;
@@ -2608,8 +2745,8 @@ int Tree::propagate_mouse_event(const Point2i &p_pos, int x_ofs, int y_ofs, int
}
click_handled = true;
- popup_edited_item = p_item;
- popup_edited_item_col = col;
+ popup_pressing_edited_item = p_item;
+ popup_pressing_edited_item_column = col;
pressing_item_rect = Rect2(get_global_position() + Point2i(col_ofs, _get_title_button_height() + y_ofs) - cache.offset, Size2(col_width, item_h));
pressing_for_editor_text = editor_text;
@@ -2643,8 +2780,8 @@ int Tree::propagate_mouse_event(const Point2i &p_pos, int x_ofs, int y_ofs, int
item_h += child_h;
}
}
- if (p_item == root && p_button == MouseButton::RIGHT) {
- emit_signal(SNAME("empty_rmb"), get_local_mouse_position());
+ if (p_item == root) {
+ emit_signal(SNAME("empty_clicked"), get_local_mouse_position(), p_button);
}
}
@@ -3052,7 +3189,6 @@ void Tree::gui_input(const Ref<InputEvent> &p_event) {
}
Ref<InputEventMouseMotion> mm = p_event;
-
if (mm.is_valid()) {
if (cache.font.is_null()) { // avoid a strange case that may corrupt stuff
update_cache();
@@ -3144,10 +3280,16 @@ void Tree::gui_input(const Ref<InputEvent> &p_event) {
update();
}
- if (pressing_for_editor && popup_edited_item && (popup_edited_item->get_cell_mode(popup_edited_item_col) == TreeItem::CELL_MODE_RANGE)) {
- //range drag
+ if (pressing_for_editor && popup_pressing_edited_item && (popup_pressing_edited_item->get_cell_mode(popup_pressing_edited_item_column) == TreeItem::CELL_MODE_RANGE)) {
+ /* This needs to happen now, because the popup can be closed when pressing another item, and must remain the popup edited item until it actually closes */
+ popup_edited_item = popup_pressing_edited_item;
+ popup_edited_item_col = popup_pressing_edited_item_column;
+
+ popup_pressing_edited_item = nullptr;
+ popup_pressing_edited_item_column = -1;
if (!range_drag_enabled) {
+ //range drag
Vector2 cpos = mm->get_position();
if (rtl) {
cpos.x = get_size().width - cpos.x;
@@ -3172,22 +3314,21 @@ 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;
}
}
- Ref<InputEventMouseButton> b = p_event;
-
- if (b.is_valid()) {
+ Ref<InputEventMouseButton> mb = p_event;
+ if (mb.is_valid()) {
if (cache.font.is_null()) { // avoid a strange case that may corrupt stuff
update_cache();
}
bool rtl = is_layout_rtl();
- if (!b->is_pressed()) {
- if (b->get_button_index() == MouseButton::LEFT) {
- Point2 pos = b->get_position();
+ if (!mb->is_pressed()) {
+ if (mb->get_button_index() == MouseButton::LEFT) {
+ Point2 pos = mb->get_position();
if (rtl) {
pos.x = get_size().width - pos.x;
}
@@ -3222,9 +3363,22 @@ void Tree::gui_input(const Ref<InputEvent> &p_event) {
warp_mouse(range_drag_capture_pos);
} else {
Rect2 rect = get_selected()->get_meta("__focus_rect");
- Point2 mpos = b->get_position();
+ Point2 mpos = mb->get_position();
+ int icon_size_x = 0;
+ Ref<Texture2D> icon = get_selected()->get_icon(selected_col);
+ if (icon.is_valid()) {
+ 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()) {
@@ -3237,17 +3391,6 @@ void Tree::gui_input(const Ref<InputEvent> &p_event) {
pressing_for_editor = false;
}
- if (cache.click_type == Cache::CLICK_BUTTON && cache.click_item != nullptr) {
- // make sure in case of wrong reference after reconstructing whole TreeItems
- cache.click_item = get_item_at_position(cache.click_pos);
- emit_signal(SNAME("button_pressed"), cache.click_item, cache.click_column, cache.click_id);
- }
- cache.click_type = Cache::CLICK_NONE;
- cache.click_index = -1;
- cache.click_id = -1;
- cache.click_item = nullptr;
- cache.click_column = 0;
-
if (drag_touching) {
if (drag_speed == 0) {
drag_touching_deaccel = false;
@@ -3257,8 +3400,20 @@ void Tree::gui_input(const Ref<InputEvent> &p_event) {
drag_touching_deaccel = true;
}
}
- update();
}
+
+ if (cache.click_type == Cache::CLICK_BUTTON && cache.click_item != nullptr) {
+ // make sure in case of wrong reference after reconstructing whole TreeItems
+ cache.click_item = get_item_at_position(cache.click_pos);
+ emit_signal("button_clicked", cache.click_item, cache.click_column, cache.click_id, mb->get_button_index());
+ }
+
+ cache.click_type = Cache::CLICK_NONE;
+ cache.click_index = -1;
+ cache.click_id = -1;
+ cache.click_item = nullptr;
+ cache.click_column = 0;
+ update();
return;
}
@@ -3266,12 +3421,12 @@ void Tree::gui_input(const Ref<InputEvent> &p_event) {
return;
}
- switch (b->get_button_index()) {
+ switch (mb->get_button_index()) {
case MouseButton::RIGHT:
case MouseButton::LEFT: {
Ref<StyleBox> bg = cache.bg;
- Point2 pos = b->get_position();
+ Point2 pos = mb->get_position();
if (rtl) {
pos.x = get_size().width - pos.x;
}
@@ -3281,7 +3436,7 @@ void Tree::gui_input(const Ref<InputEvent> &p_event) {
pos.y -= _get_title_button_height();
if (pos.y < 0) {
- if (b->get_button_index() == MouseButton::LEFT) {
+ if (mb->get_button_index() == MouseButton::LEFT) {
pos.x += cache.offset.x;
int len = 0;
for (int i = 0; i < columns.size(); i++) {
@@ -3298,10 +3453,8 @@ void Tree::gui_input(const Ref<InputEvent> &p_event) {
break;
}
}
+
if (!root || (!root->get_first_child() && hide_root)) {
- if (b->get_button_index() == MouseButton::RIGHT && allow_rmb_select) {
- emit_signal(SNAME("empty_tree_rmb_selected"), get_local_mouse_position());
- }
break;
}
@@ -3316,17 +3469,17 @@ void Tree::gui_input(const Ref<InputEvent> &p_event) {
cache.rtl = is_layout_rtl();
blocked++;
- propagate_mouse_event(pos + cache.offset, 0, 0, x_limit + cache.offset.width, b->is_double_click(), root, b->get_button_index(), b);
+ propagate_mouse_event(pos + cache.offset, 0, 0, x_limit + cache.offset.width, mb->is_double_click(), root, mb->get_button_index(), mb);
blocked--;
if (pressing_for_editor) {
- pressing_pos = b->get_position();
+ pressing_pos = mb->get_position();
if (rtl) {
pressing_pos.x = get_size().width - pressing_pos.x;
}
}
- if (b->get_button_index() == MouseButton::RIGHT) {
+ if (mb->get_button_index() == MouseButton::RIGHT) {
break;
}
@@ -3349,8 +3502,8 @@ void Tree::gui_input(const Ref<InputEvent> &p_event) {
set_physics_process_internal(true);
}
- if (b->get_button_index() == MouseButton::LEFT) {
- if (get_item_at_position(b->get_position()) == nullptr && !b->is_shift_pressed() && !b->is_ctrl_pressed() && !b->is_command_pressed()) {
+ if (mb->get_button_index() == MouseButton::LEFT) {
+ if (get_item_at_position(mb->get_position()) == nullptr && !mb->is_shift_pressed() && !mb->is_ctrl_pressed() && !mb->is_command_pressed()) {
emit_signal(SNAME("nothing_selected"));
}
}
@@ -3364,7 +3517,7 @@ void Tree::gui_input(const Ref<InputEvent> &p_event) {
} break;
case MouseButton::WHEEL_UP: {
double prev_value = v_scroll->get_value();
- v_scroll->set_value(v_scroll->get_value() - v_scroll->get_page() * b->get_factor() / 8);
+ v_scroll->set_value(v_scroll->get_value() - v_scroll->get_page() * mb->get_factor() / 8);
if (v_scroll->get_value() != prev_value) {
accept_event();
}
@@ -3372,7 +3525,7 @@ void Tree::gui_input(const Ref<InputEvent> &p_event) {
} break;
case MouseButton::WHEEL_DOWN: {
double prev_value = v_scroll->get_value();
- v_scroll->set_value(v_scroll->get_value() + v_scroll->get_page() * b->get_factor() / 8);
+ v_scroll->set_value(v_scroll->get_value() + v_scroll->get_page() * mb->get_factor() / 8);
if (v_scroll->get_value() != prev_value) {
accept_event();
}
@@ -3566,178 +3719,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;
}
}
@@ -3809,16 +3971,15 @@ TreeItem *Tree::get_last_item() const {
return last;
}
-void Tree::item_edited(int p_column, TreeItem *p_item, bool p_lmb) {
+void Tree::item_edited(int p_column, TreeItem *p_item, MouseButton p_custom_mouse_index) {
edited_item = p_item;
edited_col = p_column;
if (p_item != nullptr && p_column >= 0 && p_column < p_item->cells.size()) {
edited_item->cells.write[p_column].dirty = true;
}
- if (p_lmb) {
- emit_signal(SNAME("item_edited"));
- } else {
- emit_signal(SNAME("item_rmb_edited"));
+ emit_signal(SNAME("item_edited"));
+ if (p_custom_mouse_index != MouseButton::NONE) {
+ emit_signal(SNAME("custom_item_clicked"), p_custom_mouse_index);
}
}
@@ -3910,6 +4071,7 @@ void Tree::clear() {
selected_item = nullptr;
edited_item = nullptr;
popup_edited_item = nullptr;
+ popup_pressing_edited_item = nullptr;
update();
};
@@ -3987,10 +4149,6 @@ int Tree::get_edited_column() const {
}
TreeItem *Tree::get_next_selected(TreeItem *p_item) {
- /*
- if (!p_item)
- return nullptr;
- */
if (!root) {
return nullptr;
}
@@ -4048,7 +4206,7 @@ int Tree::get_column_minimum_width(int p_column) const {
depth += 1;
} else {
TreeItem *common_parent = item->get_parent();
- while (common_parent != next->get_parent()) {
+ while (common_parent != next->get_parent() && common_parent) {
common_parent = common_parent->get_parent();
depth -= 1;
}
@@ -4229,12 +4387,16 @@ int Tree::get_pressed_button() const {
return pressed_button;
}
-Rect2 Tree::get_item_rect(TreeItem *p_item, int p_column) const {
+Rect2 Tree::get_item_rect(TreeItem *p_item, int p_column, int p_button) const {
ERR_FAIL_NULL_V(p_item, Rect2());
ERR_FAIL_COND_V(p_item->tree != this, Rect2());
if (p_column != -1) {
ERR_FAIL_INDEX_V(p_column, columns.size(), Rect2());
}
+ if (p_button != -1) {
+ ERR_FAIL_COND_V(p_column == -1, Rect2()); // pass a column if you want to pass a button
+ ERR_FAIL_INDEX_V(p_button, p_item->cells[p_column].buttons.size(), Rect2());
+ }
int ofs = get_item_offset(p_item);
int height = compute_item_height(p_item);
@@ -4252,6 +4414,19 @@ Rect2 Tree::get_item_rect(TreeItem *p_item, int p_column) const {
}
r.position.x = accum;
r.size.x = get_column_width(p_column);
+ if (p_button != -1) {
+ const TreeItem::Cell &c = p_item->cells[p_column];
+ Vector2 ofst = Vector2(r.position.x + r.size.x, r.position.y);
+ for (int j = c.buttons.size() - 1; j >= 0; j--) {
+ Ref<Texture2D> b = c.buttons[j].texture;
+ Size2 size = b->get_size() + cache.button_pressed->get_minimum_size();
+ ofst.x -= size.x;
+
+ if (j == p_button) {
+ return Rect2(ofst, size);
+ }
+ }
+ }
}
return r;
@@ -4347,21 +4522,30 @@ Point2 Tree::get_scroll() const {
return ofs;
}
-void Tree::scroll_to_item(TreeItem *p_item) {
- if (!is_visible_in_tree()) {
- // hack to work around crash in get_item_rect() if Tree is not in tree.
- return;
+void Tree::scroll_to_item(TreeItem *p_item, bool p_center_on_item) {
+ ERR_FAIL_NULL(p_item);
+ if (!is_visible_in_tree() || !p_item->is_visible()) {
+ return; // Hack to work around crash in get_item_rect() if Tree is not in tree.
}
- // 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);
+ }
+ }
}
}
@@ -4464,7 +4648,7 @@ void Tree::_do_incr_search(const String &p_add) {
TreeItem *Tree::_find_item_at_pos(TreeItem *p_item, const Point2 &p_pos, int &r_column, int &h, int &section) const {
Point2 pos = p_pos;
- if (root != p_item || !hide_root) {
+ if ((root != p_item || !hide_root) && p_item->is_visible()) {
h = compute_item_height(p_item) + cache.vseparation;
if (pos.y < h) {
if (drop_mode_flags == DROP_MODE_ON_ITEM) {
@@ -4497,7 +4681,7 @@ TreeItem *Tree::_find_item_at_pos(TreeItem *p_item, const Point2 &p_pos, int &r_
h = 0;
}
- if (p_item->is_collapsed()) {
+ if (p_item->is_collapsed() || !p_item->is_visible()) {
return nullptr; // do not try children, it's collapsed
}
@@ -4753,7 +4937,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);
@@ -4768,7 +4952,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);
@@ -4782,10 +4966,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", "button_index"), &Tree::get_item_rect, DEFVAL(-1), 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);
@@ -4806,7 +4991,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);
@@ -4827,7 +5012,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::INT, "column_titles_visible"), "set_column_titles_visible", "are_column_titles_visible");
+ 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");
@@ -4840,16 +5025,15 @@ void Tree::_bind_methods() {
ADD_SIGNAL(MethodInfo("item_selected"));
ADD_SIGNAL(MethodInfo("cell_selected"));
ADD_SIGNAL(MethodInfo("multi_selected", PropertyInfo(Variant::OBJECT, "item", PROPERTY_HINT_RESOURCE_TYPE, "TreeItem"), PropertyInfo(Variant::INT, "column"), PropertyInfo(Variant::BOOL, "selected")));
- ADD_SIGNAL(MethodInfo("item_rmb_selected", PropertyInfo(Variant::VECTOR2, "position")));
- ADD_SIGNAL(MethodInfo("empty_rmb", PropertyInfo(Variant::VECTOR2, "position")));
- ADD_SIGNAL(MethodInfo("empty_tree_rmb_selected", PropertyInfo(Variant::VECTOR2, "position")));
+ ADD_SIGNAL(MethodInfo("item_mouse_selected", PropertyInfo(Variant::VECTOR2, "position"), PropertyInfo(Variant::INT, "mouse_button_index")));
+ ADD_SIGNAL(MethodInfo("empty_clicked", PropertyInfo(Variant::VECTOR2, "position"), PropertyInfo(Variant::INT, "mouse_button_index")));
ADD_SIGNAL(MethodInfo("item_edited"));
- ADD_SIGNAL(MethodInfo("item_rmb_edited"));
+ ADD_SIGNAL(MethodInfo("custom_item_clicked", PropertyInfo(Variant::INT, "mouse_button_index")));
ADD_SIGNAL(MethodInfo("item_custom_button_pressed"));
ADD_SIGNAL(MethodInfo("item_double_clicked"));
ADD_SIGNAL(MethodInfo("item_collapsed", PropertyInfo(Variant::OBJECT, "item", PROPERTY_HINT_RESOURCE_TYPE, "TreeItem")));
- //ADD_SIGNAL( MethodInfo("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("check_propagated_to_item", PropertyInfo(Variant::OBJECT, "item", PROPERTY_HINT_RESOURCE_TYPE, "TreeItem"), PropertyInfo(Variant::INT, "column")));
+ ADD_SIGNAL(MethodInfo("button_clicked", PropertyInfo(Variant::OBJECT, "item", PROPERTY_HINT_RESOURCE_TYPE, "TreeItem"), PropertyInfo(Variant::INT, "column"), PropertyInfo(Variant::INT, "id"), PropertyInfo(Variant::INT, "mouse_button_index")));
ADD_SIGNAL(MethodInfo("custom_popup_edited", PropertyInfo(Variant::BOOL, "arrow_clicked")));
ADD_SIGNAL(MethodInfo("item_activated"));
ADD_SIGNAL(MethodInfo("column_title_pressed", PropertyInfo(Variant::INT, "column")));
diff --git a/scene/gui/tree.h b/scene/gui/tree.h
index a190567f0f..0a8dd3204a 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 */
@@ -65,7 +65,7 @@ private:
Ref<TextLine> text_buf;
Dictionary opentype_features;
String language;
- Control::StructuredTextParser st_parser = Control::STRUCTURED_TEXT_DEFAULT;
+ TextServer::StructuredTextParser st_parser = TextServer::STRUCTURED_TEXT_DEFAULT;
Array st_args;
Control::TextDirection text_direction = Control::TEXT_DIRECTION_INHERITED;
bool dirty = true;
@@ -124,6 +124,7 @@ private:
Vector<Cell> cells;
bool collapsed = false; // won't show children
+ bool visible = true;
bool disable_folding = false;
int custom_min_height = 0;
@@ -134,7 +135,7 @@ private:
Vector<TreeItem *> children_cache;
bool is_root = false; // for tree root
- Tree *tree; // tree (for reference)
+ Tree *tree = nullptr; // tree (for reference)
TreeItem(Tree *p_tree);
@@ -188,18 +189,8 @@ 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);
+ void _call_recursive_bind(const Variant **p_args, int p_argcount, Callable::CallError &r_error);
public:
/* cell mode */
@@ -212,6 +203,17 @@ 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);
+
+ TreeItem *_get_prev_visible(bool p_wrap = false);
+ TreeItem *_get_next_visible(bool p_wrap = false);
+
+public:
void set_text(int p_column, String p_text);
String get_text(int p_column) const;
@@ -222,8 +224,8 @@ public:
int get_opentype_feature(int p_column, const String &p_name) const;
void clear_opentype_features(int p_column);
- void set_structured_text_bidi_override(int p_column, Control::StructuredTextParser p_parser);
- Control::StructuredTextParser get_structured_text_bidi_override(int p_column) const;
+ void set_structured_text_bidi_override(int p_column, TextServer::StructuredTextParser p_parser);
+ TextServer::StructuredTextParser get_structured_text_bidi_override(int p_column) const;
void set_structured_text_bidi_override_options(int p_column, Array p_args);
Array get_structured_text_bidi_override_options(int p_column) const;
@@ -250,6 +252,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);
@@ -274,6 +277,9 @@ public:
void set_collapsed(bool p_collapsed);
bool is_collapsed();
+ void set_visible(bool p_visible);
+ bool is_visible();
+
void uncollapse_tree();
void set_custom_minimum_height(int p_height);
@@ -336,6 +342,7 @@ public:
TreeItem *get_next_visible(bool p_wrap = false);
TreeItem *get_child(int p_idx);
+ int get_visible_child_count();
int get_child_count();
Array get_children();
int get_index();
@@ -380,6 +387,9 @@ private:
TreeItem *selected_item = nullptr;
TreeItem *edited_item = nullptr;
+ TreeItem *popup_pressing_edited_item = nullptr; // Candidate.
+ int popup_pressing_edited_item_column = -1;
+
TreeItem *drop_mode_over = nullptr;
int drop_mode_section = 0;
@@ -429,18 +439,18 @@ private:
bool show_column_titles = false;
- VBoxContainer *popup_editor_vb;
+ VBoxContainer *popup_editor_vb = nullptr;
- Popup *popup_editor;
+ Popup *popup_editor = nullptr;
LineEdit *text_editor = nullptr;
- HSlider *value_editor;
+ HSlider *value_editor = nullptr;
bool updating_value_editor = false;
uint64_t focus_in_id = 0;
PopupMenu *popup_menu = nullptr;
Vector<ColumnInfo> columns;
- Timer *range_click_timer;
+ Timer *range_click_timer = nullptr;
TreeItem *range_item_last = nullptr;
bool range_up_last = false;
void _range_click_timeout();
@@ -464,7 +474,7 @@ private:
void _notification(int p_what);
- void item_edited(int p_column, TreeItem *p_item, bool p_lmb = true);
+ void item_edited(int p_column, TreeItem *p_item, MouseButton p_custom_mouse_index = MouseButton::NONE);
void item_changed(int p_column, TreeItem *p_item);
void item_selected(int p_column, TreeItem *p_item);
void item_deselected(int p_column, TreeItem *p_item);
@@ -554,8 +564,8 @@ private:
int _get_title_button_height() const;
void _scroll_moved(float p_value);
- HScrollBar *h_scroll;
- VScrollBar *v_scroll;
+ HScrollBar *h_scroll = nullptr;
+ VScrollBar *v_scroll = nullptr;
bool h_scroll_enabled = true;
bool v_scroll_enabled = true;
@@ -609,23 +619,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;
@@ -691,7 +684,7 @@ public:
Rect2 get_custom_popup_rect() const;
int get_item_offset(TreeItem *p_item) const;
- Rect2 get_item_rect(TreeItem *p_item, int p_column = -1) const;
+ Rect2 get_item_rect(TreeItem *p_item, int p_column = -1, int p_button = -1) const;
bool edit_selected();
bool is_editing();
@@ -701,7 +694,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_stream_player.cpp b/scene/gui/video_stream_player.cpp
index a11d56a2ed..20bc9a1028 100644
--- a/scene/gui/video_stream_player.cpp
+++ b/scene/gui/video_stream_player.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,7 +60,7 @@ int VideoStreamPlayer::_audio_mix_callback(void *p_udata, const float *p_data, i
ERR_FAIL_NULL_V(p_udata, 0);
ERR_FAIL_NULL_V(p_data, 0);
- VideoStreamPlayer *vp = (VideoStreamPlayer *)p_udata;
+ VideoStreamPlayer *vp = static_cast<VideoStreamPlayer *>(p_udata);
int todo = MIN(vp->resampler.get_writer_space(), p_frames);
@@ -77,7 +77,7 @@ int VideoStreamPlayer::_audio_mix_callback(void *p_udata, const float *p_data, i
void VideoStreamPlayer::_mix_audios(void *p_self) {
ERR_FAIL_NULL(p_self);
- reinterpret_cast<VideoStreamPlayer *>(p_self)->_mix_audio();
+ static_cast<VideoStreamPlayer *>(p_self)->_mix_audio();
}
// Called from audio thread
@@ -134,7 +134,6 @@ void VideoStreamPlayer::_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 VideoStreamPlayer::_notification(int p_notification) {
if (!playback->is_playing()) {
emit_signal(SceneStringNames::get_singleton()->finished);
}
-
} break;
case NOTIFICATION_DRAW: {
@@ -175,10 +173,9 @@ void VideoStreamPlayer::_notification(int p_notification) {
Size2 s = expand ? get_size() : texture->get_size();
draw_texture_rect(texture, Rect2(Point2(), s), false);
-
} break;
- };
-};
+ }
+}
Size2 VideoStreamPlayer::get_minimum_size() const {
if (!expand && !texture.is_null()) {
@@ -243,11 +240,11 @@ void VideoStreamPlayer::set_stream(const Ref<VideoStream> &p_stream) {
if (!expand) {
update_minimum_size();
}
-};
+}
Ref<VideoStream> VideoStreamPlayer::get_stream() const {
return stream;
-};
+}
void VideoStreamPlayer::play() {
ERR_FAIL_COND(!is_inside_tree());
@@ -257,10 +254,8 @@ void VideoStreamPlayer::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 VideoStreamPlayer::stop() {
if (!is_inside_tree()) {
@@ -271,11 +266,10 @@ void VideoStreamPlayer::stop() {
}
playback->stop();
- // AudioServer::get_singleton()->stream_set_active(stream_rid,false);
resampler.flush();
set_process_internal(false);
last_audio_time = 0;
-};
+}
bool VideoStreamPlayer::is_playing() const {
if (playback.is_null()) {
@@ -283,16 +277,16 @@ bool VideoStreamPlayer::is_playing() const {
}
return playback->is_playing();
-};
+}
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 VideoStreamPlayer::is_paused() const {
return paused;
@@ -316,11 +310,11 @@ int VideoStreamPlayer::get_audio_track() const {
void VideoStreamPlayer::set_volume(float p_vol) {
volume = p_vol;
-};
+}
float VideoStreamPlayer::get_volume() const {
return volume;
-};
+}
void VideoStreamPlayer::set_volume_db(float p_db) {
if (p_db < -79) {
@@ -328,7 +322,7 @@ void VideoStreamPlayer::set_volume_db(float p_db) {
} else {
set_volume(Math::db2linear(p_db));
}
-};
+}
float VideoStreamPlayer::get_volume_db() const {
if (volume == 0) {
@@ -336,21 +330,21 @@ float VideoStreamPlayer::get_volume_db() const {
} else {
return Math::linear2db(volume);
}
-};
+}
String VideoStreamPlayer::get_stream_name() const {
if (stream.is_null()) {
return "<No Stream>";
}
return stream->get_name();
-};
+}
float VideoStreamPlayer::get_stream_position() const {
if (playback.is_null()) {
return 0;
}
return playback->get_playback_position();
-};
+}
void VideoStreamPlayer::set_stream_position(float p_position) {
if (playback.is_valid()) {
@@ -368,14 +362,14 @@ Ref<Texture2D> VideoStreamPlayer::get_video_texture() const {
void VideoStreamPlayer::set_autoplay(bool p_enable) {
autoplay = p_enable;
-};
+}
bool VideoStreamPlayer::has_autoplay() const {
return autoplay;
-};
+}
void VideoStreamPlayer::set_bus(const StringName &p_bus) {
- //if audio is active, must lock this
+ // If audio is active, must lock this.
AudioServer::get_singleton()->lock();
bus = p_bus;
AudioServer::get_singleton()->unlock();
@@ -449,8 +443,7 @@ void VideoStreamPlayer::_bind_methods() {
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_db", PROPERTY_HINT_RANGE, "-80,24,0.01,suffix:dB"), "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");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "paused"), "set_paused", "is_paused");
@@ -464,7 +457,5 @@ void VideoStreamPlayer::_bind_methods() {
VideoStreamPlayer::VideoStreamPlayer() {}
VideoStreamPlayer::~VideoStreamPlayer() {
- // 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
-};
+ resampler.clear(); // Not necessary here, but make in consistent with other "stream_player" classes.
+}
diff --git a/scene/gui/video_stream_player.h b/scene/gui/video_stream_player.h
index ad4a3dd9e9..130b2901f1 100644
--- a/scene/gui/video_stream_player.h
+++ b/scene/gui/video_stream_player.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/view_panner.cpp b/scene/gui/view_panner.cpp
new file mode 100644
index 0000000000..892d0aba29
--- /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()) {
+ Vector2 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