summaryrefslogtreecommitdiffstats
path: root/scene/gui
diff options
context:
space:
mode:
Diffstat (limited to 'scene/gui')
-rw-r--r--scene/gui/base_button.cpp2
-rw-r--r--scene/gui/code_edit.cpp65
-rw-r--r--scene/gui/code_edit.h1
-rw-r--r--scene/gui/color_mode.h4
-rw-r--r--scene/gui/color_picker.cpp43
-rw-r--r--scene/gui/color_picker.h5
-rw-r--r--scene/gui/control.cpp36
-rw-r--r--scene/gui/control.h2
-rw-r--r--scene/gui/dialogs.cpp1
-rw-r--r--scene/gui/file_dialog.cpp28
-rw-r--r--scene/gui/file_dialog.h2
-rw-r--r--scene/gui/graph_edit.compat.inc46
-rw-r--r--scene/gui/graph_edit.cpp242
-rw-r--r--scene/gui/graph_edit.h50
-rw-r--r--scene/gui/graph_edit_arranger.cpp28
-rw-r--r--scene/gui/graph_element.cpp10
-rw-r--r--scene/gui/graph_node.cpp4
-rw-r--r--scene/gui/label.cpp15
-rw-r--r--scene/gui/line_edit.cpp30
-rw-r--r--scene/gui/line_edit.h2
-rw-r--r--scene/gui/link_button.cpp4
-rw-r--r--scene/gui/link_button.h2
-rw-r--r--scene/gui/menu_bar.cpp231
-rw-r--r--scene/gui/menu_bar.h24
-rw-r--r--scene/gui/popup_menu.cpp499
-rw-r--r--scene/gui/popup_menu.h9
-rw-r--r--scene/gui/range.h1
-rw-r--r--scene/gui/rich_text_label.cpp89
-rw-r--r--scene/gui/scroll_bar.cpp17
-rw-r--r--scene/gui/scroll_bar.h1
-rw-r--r--scene/gui/slider.cpp3
-rw-r--r--scene/gui/spin_box.cpp9
-rw-r--r--scene/gui/spin_box.h3
-rw-r--r--scene/gui/subviewport_container.cpp16
-rw-r--r--scene/gui/subviewport_container.h2
-rw-r--r--scene/gui/tab_bar.cpp145
-rw-r--r--scene/gui/tab_bar.h6
-rw-r--r--scene/gui/tab_container.cpp175
-rw-r--r--scene/gui/tab_container.h8
-rw-r--r--scene/gui/text_edit.cpp83
-rw-r--r--scene/gui/text_edit.h4
-rw-r--r--scene/gui/tree.cpp32
-rw-r--r--scene/gui/view_panner.cpp29
-rw-r--r--scene/gui/view_panner.h1
44 files changed, 1397 insertions, 612 deletions
diff --git a/scene/gui/base_button.cpp b/scene/gui/base_button.cpp
index f57afb66b3..66b14dc967 100644
--- a/scene/gui/base_button.cpp
+++ b/scene/gui/base_button.cpp
@@ -380,7 +380,7 @@ void BaseButton::shortcut_input(const Ref<InputEvent> &p_event) {
queue_redraw();
accept_event();
- if (shortcut_feedback) {
+ if (shortcut_feedback && is_inside_tree()) {
if (shortcut_feedback_timer == nullptr) {
shortcut_feedback_timer = memnew(Timer);
shortcut_feedback_timer->set_one_shot(true);
diff --git a/scene/gui/code_edit.cpp b/scene/gui/code_edit.cpp
index e7a2a26a29..291d578add 100644
--- a/scene/gui/code_edit.cpp
+++ b/scene/gui/code_edit.cpp
@@ -479,19 +479,8 @@ void CodeEdit::gui_input(const Ref<InputEvent> &p_gui_input) {
accept_event();
return;
}
- if (k->is_action("ui_home", true)) {
- code_completion_current_selected = 0;
- code_completion_force_item_center = -1;
- queue_redraw();
- accept_event();
- return;
- }
- if (k->is_action("ui_end", true)) {
- code_completion_current_selected = code_completion_options.size() - 1;
- code_completion_force_item_center = -1;
- queue_redraw();
- accept_event();
- return;
+ if (k->is_action("ui_text_caret_line_start", true) || k->is_action("ui_text_caret_line_end", true)) {
+ cancel_code_completion();
}
if (k->is_action("ui_text_completion_replace", true) || k->is_action("ui_text_completion_accept", true)) {
confirm_code_completion(k->is_action("ui_text_completion_replace", true));
@@ -2873,7 +2862,8 @@ void CodeEdit::_update_code_region_tags() {
return;
}
- for (int i = 0; i < delimiters.size(); i++) {
+ // A shorter delimiter has higher priority.
+ for (int i = delimiters.size() - 1; i >= 0; i--) {
if (delimiters[i].type != DelimiterType::TYPE_COMMENT) {
continue;
}
@@ -3115,6 +3105,8 @@ void CodeEdit::_add_delimiter(const String &p_start_key, const String &p_end_key
ERR_FAIL_COND_MSG(delimiters[i].start_key == p_start_key, "delimiter with start key '" + p_start_key + "' already exists.");
if (p_start_key.length() < delimiters[i].start_key.length()) {
at++;
+ } else {
+ break;
}
}
@@ -3223,7 +3215,7 @@ void CodeEdit::_filter_code_completion_candidates_impl() {
int line_height = get_line_height();
if (GDVIRTUAL_IS_OVERRIDDEN(_filter_code_completion_candidates)) {
- code_completion_options.clear();
+ Vector<ScriptLanguage::CodeCompletionOption> code_completion_options_new;
code_completion_base = "";
/* Build options argument. */
@@ -3273,11 +3265,15 @@ void CodeEdit::_filter_code_completion_candidates_impl() {
if (theme_cache.font.is_valid()) {
max_width = MAX(max_width, theme_cache.font->get_string_size(option.display, HORIZONTAL_ALIGNMENT_LEFT, -1, theme_cache.font_size).width + offset);
}
- code_completion_options.push_back(option);
+ code_completion_options_new.push_back(option);
+ }
+
+ if (_should_reset_selected_option_for_new_options(code_completion_options_new)) {
+ code_completion_current_selected = 0;
}
+ code_completion_options = code_completion_options_new;
code_completion_longest_line = MIN(max_width, theme_cache.code_completion_max_width * theme_cache.font_size);
- code_completion_current_selected = 0;
code_completion_force_item_center = -1;
code_completion_active = true;
queue_redraw();
@@ -3352,7 +3348,7 @@ void CodeEdit::_filter_code_completion_candidates_impl() {
/* For now handle only traditional quoted strings. */
bool single_quote = in_string != -1 && first_quote_col > 0 && delimiters[in_string].start_key == "'";
- code_completion_options.clear();
+ Vector<ScriptLanguage::CodeCompletionOption> code_completion_options_new;
code_completion_base = string_to_complete;
/* Don't autocomplete setting numerical values. */
@@ -3384,7 +3380,8 @@ void CodeEdit::_filter_code_completion_candidates_impl() {
if (string_to_complete.length() == 0) {
option.get_option_characteristics(string_to_complete);
- code_completion_options.push_back(option);
+ code_completion_options_new.push_back(option);
+
if (theme_cache.font.is_valid()) {
max_width = MAX(max_width, theme_cache.font->get_string_size(option.display, HORIZONTAL_ALIGNMENT_LEFT, -1, theme_cache.font_size).width + offset);
}
@@ -3459,7 +3456,7 @@ void CodeEdit::_filter_code_completion_candidates_impl() {
}
}
- code_completion_options.push_back(option);
+ code_completion_options_new.push_back(option);
if (theme_cache.font.is_valid()) {
max_width = MAX(max_width, theme_cache.font->get_string_size(option.display, HORIZONTAL_ALIGNMENT_LEFT, -1, theme_cache.font_size).width + offset);
}
@@ -3467,26 +3464,46 @@ void CodeEdit::_filter_code_completion_candidates_impl() {
}
/* No options to complete, cancel. */
- if (code_completion_options.size() == 0) {
+ if (code_completion_options_new.size() == 0) {
cancel_code_completion();
return;
}
/* A perfect match, stop completion. */
- if (code_completion_options.size() == 1 && string_to_complete == code_completion_options[0].display) {
+ if (code_completion_options_new.size() == 1 && string_to_complete == code_completion_options_new[0].display) {
cancel_code_completion();
return;
}
- code_completion_options.sort_custom<CodeCompletionOptionCompare>();
+ code_completion_options_new.sort_custom<CodeCompletionOptionCompare>();
+ if (_should_reset_selected_option_for_new_options(code_completion_options_new)) {
+ code_completion_current_selected = 0;
+ }
+ code_completion_options = code_completion_options_new;
code_completion_longest_line = MIN(max_width, theme_cache.code_completion_max_width * theme_cache.font_size);
- code_completion_current_selected = 0;
code_completion_force_item_center = -1;
code_completion_active = true;
queue_redraw();
}
+// Assumes both the new_options and the code_completion_options are sorted.
+bool CodeEdit::_should_reset_selected_option_for_new_options(const Vector<ScriptLanguage::CodeCompletionOption> &p_new_options) {
+ if (code_completion_current_selected >= p_new_options.size()) {
+ return true;
+ }
+
+ for (int i = 0; i < code_completion_options.size() && i < p_new_options.size(); i++) {
+ if (i > code_completion_current_selected) {
+ return false;
+ }
+ if (code_completion_options[i].display != p_new_options[i].display) {
+ return true;
+ }
+ }
+ return false;
+}
+
void CodeEdit::_lines_edited_from(int p_from_line, int p_to_line) {
_update_delimiter_cache(p_from_line, p_to_line);
diff --git a/scene/gui/code_edit.h b/scene/gui/code_edit.h
index 97c435b52d..4b0629d29d 100644
--- a/scene/gui/code_edit.h
+++ b/scene/gui/code_edit.h
@@ -222,6 +222,7 @@ private:
void _update_scroll_selected_line(float p_mouse_y);
void _filter_code_completion_candidates_impl();
+ bool _should_reset_selected_option_for_new_options(const Vector<ScriptLanguage::CodeCompletionOption> &p_new_options);
/* Line length guidelines */
TypedArray<int> line_length_guideline_columns;
diff --git a/scene/gui/color_mode.h b/scene/gui/color_mode.h
index bbbf3fbaac..84e9d4542d 100644
--- a/scene/gui/color_mode.h
+++ b/scene/gui/color_mode.h
@@ -43,6 +43,7 @@ public:
virtual int get_slider_count() const { return 3; };
virtual float get_slider_step() const = 0;
+ virtual float get_spinbox_arrow_step() const { return get_slider_step(); };
virtual String get_slider_label(int idx) const = 0;
virtual float get_slider_max(int idx) const = 0;
virtual float get_slider_value(int idx) const = 0;
@@ -109,7 +110,8 @@ public:
virtual String get_name() const override { return "RAW"; }
- virtual float get_slider_step() const override { return 0.01; }
+ virtual float get_slider_step() const override { return 0.001; }
+ virtual float get_spinbox_arrow_step() const override { return 0.01; }
virtual String get_slider_label(int idx) const override;
virtual float get_slider_max(int idx) const override;
virtual float get_slider_value(int idx) const override;
diff --git a/scene/gui/color_picker.cpp b/scene/gui/color_picker.cpp
index 5ec0714b64..f250662be0 100644
--- a/scene/gui/color_picker.cpp
+++ b/scene/gui/color_picker.cpp
@@ -77,9 +77,11 @@ void ColorPicker::_notification(int p_what) {
alpha_label->add_theme_constant_override(SNAME("center_grabber"), theme_cache.center_slider_grabbers);
for (int i = 0; i < MODE_BUTTON_COUNT; i++) {
+ mode_btns[i]->begin_bulk_theme_override();
mode_btns[i]->add_theme_style_override(SNAME("pressed"), theme_cache.mode_button_pressed);
mode_btns[i]->add_theme_style_override(SNAME("normal"), theme_cache.mode_button_normal);
mode_btns[i]->add_theme_style_override(SNAME("hover"), theme_cache.mode_button_hover);
+ mode_btns[i]->end_bulk_theme_override();
}
shape_popup->set_item_icon(shape_popup->get_item_index(SHAPE_HSV_RECTANGLE), theme_cache.shape_rect);
@@ -87,10 +89,16 @@ void ColorPicker::_notification(int p_what) {
shape_popup->set_item_icon(shape_popup->get_item_index(SHAPE_VHS_CIRCLE), theme_cache.shape_circle);
shape_popup->set_item_icon(shape_popup->get_item_index(SHAPE_OKHSL_CIRCLE), theme_cache.shape_circle);
+ if (current_shape != SHAPE_NONE) {
+ btn_shape->set_icon(shape_popup->get_item_icon(current_shape));
+ }
+
+ internal_margin->begin_bulk_theme_override();
internal_margin->add_theme_constant_override(SNAME("margin_bottom"), theme_cache.content_margin);
internal_margin->add_theme_constant_override(SNAME("margin_left"), theme_cache.content_margin);
internal_margin->add_theme_constant_override(SNAME("margin_right"), theme_cache.content_margin);
internal_margin->add_theme_constant_override(SNAME("margin_top"), theme_cache.content_margin);
+ internal_margin->end_bulk_theme_override();
_reset_sliders_theme();
@@ -341,7 +349,11 @@ bool ColorPicker::is_editing_alpha() const {
return edit_alpha;
}
-void ColorPicker::_value_changed(double) {
+void ColorPicker::_slider_drag_started() {
+ currently_dragging = true;
+}
+
+void ColorPicker::_slider_value_changed() {
if (updating) {
return;
}
@@ -357,7 +369,16 @@ void ColorPicker::_value_changed(double) {
}
_set_pick_color(color, false);
- emit_signal(SNAME("color_changed"), color);
+ if (!deferred_mode_enabled || !currently_dragging) {
+ emit_signal(SNAME("color_changed"), color);
+ }
+}
+
+void ColorPicker::_slider_drag_ended() {
+ currently_dragging = false;
+ if (deferred_mode_enabled) {
+ emit_signal(SNAME("color_changed"), color);
+ }
}
void ColorPicker::add_mode(ColorMode *p_mode) {
@@ -388,7 +409,9 @@ void ColorPicker::create_slider(GridContainer *gc, int idx) {
slider->set_h_size_flags(SIZE_EXPAND_FILL);
- slider->connect("value_changed", callable_mp(this, &ColorPicker::_value_changed));
+ slider->connect("drag_started", callable_mp(this, &ColorPicker::_slider_drag_started));
+ slider->connect("value_changed", callable_mp(this, &ColorPicker::_slider_value_changed).unbind(1));
+ slider->connect("drag_ended", callable_mp(this, &ColorPicker::_slider_drag_ended).unbind(1));
slider->connect("draw", callable_mp(this, &ColorPicker::_slider_draw).bind(idx));
slider->connect("gui_input", callable_mp(this, &ColorPicker::_slider_or_spin_input));
@@ -506,20 +529,26 @@ void ColorPicker::_reset_sliders_theme() {
Ref<StyleBoxFlat> style_box_flat(memnew(StyleBoxFlat));
style_box_flat->set_content_margin(SIDE_TOP, 16 * theme_cache.base_scale);
style_box_flat->set_bg_color(Color(0.2, 0.23, 0.31).lerp(Color(0, 0, 0, 1), 0.3).clamp());
+
for (int i = 0; i < SLIDER_COUNT; i++) {
+ sliders[i]->begin_bulk_theme_override();
sliders[i]->add_theme_icon_override("grabber", theme_cache.bar_arrow);
sliders[i]->add_theme_icon_override("grabber_highlight", theme_cache.bar_arrow);
sliders[i]->add_theme_constant_override("grabber_offset", 8 * theme_cache.base_scale);
if (!colorize_sliders) {
sliders[i]->add_theme_style_override("slider", style_box_flat);
}
+ sliders[i]->end_bulk_theme_override();
}
+
+ alpha_slider->begin_bulk_theme_override();
alpha_slider->add_theme_icon_override("grabber", theme_cache.bar_arrow);
alpha_slider->add_theme_icon_override("grabber_highlight", theme_cache.bar_arrow);
alpha_slider->add_theme_constant_override("grabber_offset", 8 * theme_cache.base_scale);
if (!colorize_sliders) {
alpha_slider->add_theme_style_override("slider", style_box_flat);
}
+ alpha_slider->end_bulk_theme_override();
}
void ColorPicker::_html_submitted(const String &p_html) {
@@ -550,9 +579,11 @@ void ColorPicker::_update_color(bool p_update_sliders) {
if (p_update_sliders) {
float step = modes[current_mode]->get_slider_step();
+ float spinbox_arrow_step = modes[current_mode]->get_spinbox_arrow_step();
for (int i = 0; i < current_slider_count; i++) {
sliders[i]->set_max(modes[current_mode]->get_slider_max(i));
sliders[i]->set_step(step);
+ values[i]->set_custom_arrow_step(spinbox_arrow_step);
sliders[i]->set_value(modes[current_mode]->get_slider_value(i));
}
alpha_slider->set_max(modes[current_mode]->get_slider_max(current_slider_count));
@@ -1242,7 +1273,6 @@ void ColorPicker::_uv_input(const Ref<InputEvent> &p_event, Control *c) {
_copy_hsv_to_color();
last_color = color;
set_pick_color(color);
- _update_color();
if (!deferred_mode_enabled) {
emit_signal(SNAME("color_changed"), color);
@@ -1293,7 +1323,6 @@ void ColorPicker::_uv_input(const Ref<InputEvent> &p_event, Control *c) {
_copy_hsv_to_color();
last_color = color;
set_pick_color(color);
- _update_color();
if (!deferred_mode_enabled) {
emit_signal(SNAME("color_changed"), color);
@@ -1321,7 +1350,6 @@ void ColorPicker::_w_input(const Ref<InputEvent> &p_event) {
_copy_hsv_to_color();
last_color = color;
set_pick_color(color);
- _update_color();
if (!bev->is_pressed() && bev->get_button_index() == MouseButton::LEFT) {
add_recent_preset(color);
@@ -1347,7 +1375,6 @@ void ColorPicker::_w_input(const Ref<InputEvent> &p_event) {
_copy_hsv_to_color();
last_color = color;
set_pick_color(color);
- _update_color();
if (!deferred_mode_enabled) {
emit_signal(SNAME("color_changed"), color);
@@ -1769,8 +1796,6 @@ ColorPicker::ColorPicker() {
shape_popup->set_item_checked(current_shape, true);
shape_popup->connect("id_pressed", callable_mp(this, &ColorPicker::set_picker_shape));
- btn_shape->set_icon(shape_popup->get_item_icon(current_shape));
-
add_mode(new ColorModeRGB(this));
add_mode(new ColorModeHSV(this));
add_mode(new ColorModeRAW(this));
diff --git a/scene/gui/color_picker.h b/scene/gui/color_picker.h
index 96dbca9a0c..dc547c8b0c 100644
--- a/scene/gui/color_picker.h
+++ b/scene/gui/color_picker.h
@@ -207,6 +207,7 @@ private:
bool hex_visible = true;
bool line_edit_mouse_release = false;
bool text_changed = false;
+ bool currently_dragging = false;
float h = 0.0;
float s = 0.0;
@@ -254,7 +255,9 @@ private:
void create_slider(GridContainer *gc, int idx);
void _reset_sliders_theme();
void _html_submitted(const String &p_html);
- void _value_changed(double);
+ void _slider_drag_started();
+ void _slider_value_changed();
+ void _slider_drag_ended();
void _update_controls();
void _update_color(bool p_update_sliders = true);
void _update_text_value();
diff --git a/scene/gui/control.cpp b/scene/gui/control.cpp
index 53e8945933..ed54bd000c 100644
--- a/scene/gui/control.cpp
+++ b/scene/gui/control.cpp
@@ -1653,6 +1653,7 @@ void Control::set_custom_minimum_size(const Size2 &p_custom) {
data.custom_minimum_size = p_custom;
update_minimum_size();
+ update_configuration_warnings();
}
Size2 Control::get_custom_minimum_size() const {
@@ -1831,9 +1832,18 @@ bool Control::has_point(const Point2 &p_point) const {
void Control::set_mouse_filter(MouseFilter p_filter) {
ERR_MAIN_THREAD_GUARD;
ERR_FAIL_INDEX(p_filter, 3);
+
+ if (data.mouse_filter == p_filter) {
+ return;
+ }
+
data.mouse_filter = p_filter;
notify_property_list_changed();
update_configuration_warnings();
+
+ if (get_viewport()) {
+ get_viewport()->_gui_update_mouse_over();
+ }
}
Control::MouseFilter Control::get_mouse_filter() const {
@@ -2533,7 +2543,7 @@ StringName Control::get_theme_type_variation() const {
Ref<Texture2D> Control::get_theme_icon(const StringName &p_name, const StringName &p_theme_type) const {
ERR_READ_THREAD_GUARD_V(Ref<Texture2D>());
if (!data.initialized) {
- WARN_PRINT_ONCE("Attempting to access theme items too early; prefer NOTIFICATION_POSTINITIALIZE and NOTIFICATION_THEME_CHANGED");
+ WARN_PRINT_ONCE(vformat("Attempting to access theme items too early in %s; prefer NOTIFICATION_POSTINITIALIZE and NOTIFICATION_THEME_CHANGED", this->get_description()));
}
if (p_theme_type == StringName() || p_theme_type == get_class_name() || p_theme_type == data.theme_type_variation) {
@@ -2557,7 +2567,7 @@ Ref<Texture2D> Control::get_theme_icon(const StringName &p_name, const StringNam
Ref<StyleBox> Control::get_theme_stylebox(const StringName &p_name, const StringName &p_theme_type) const {
ERR_READ_THREAD_GUARD_V(Ref<StyleBox>());
if (!data.initialized) {
- WARN_PRINT_ONCE("Attempting to access theme items too early; prefer NOTIFICATION_POSTINITIALIZE and NOTIFICATION_THEME_CHANGED");
+ WARN_PRINT_ONCE(vformat("Attempting to access theme items too early in %s; prefer NOTIFICATION_POSTINITIALIZE and NOTIFICATION_THEME_CHANGED", this->get_description()));
}
if (p_theme_type == StringName() || p_theme_type == get_class_name() || p_theme_type == data.theme_type_variation) {
@@ -2581,7 +2591,7 @@ Ref<StyleBox> Control::get_theme_stylebox(const StringName &p_name, const String
Ref<Font> Control::get_theme_font(const StringName &p_name, const StringName &p_theme_type) const {
ERR_READ_THREAD_GUARD_V(Ref<Font>());
if (!data.initialized) {
- WARN_PRINT_ONCE("Attempting to access theme items too early; prefer NOTIFICATION_POSTINITIALIZE and NOTIFICATION_THEME_CHANGED");
+ WARN_PRINT_ONCE(vformat("Attempting to access theme items too early in %s; prefer NOTIFICATION_POSTINITIALIZE and NOTIFICATION_THEME_CHANGED", this->get_description()));
}
if (p_theme_type == StringName() || p_theme_type == get_class_name() || p_theme_type == data.theme_type_variation) {
@@ -2605,7 +2615,7 @@ Ref<Font> Control::get_theme_font(const StringName &p_name, const StringName &p_
int Control::get_theme_font_size(const StringName &p_name, const StringName &p_theme_type) const {
ERR_READ_THREAD_GUARD_V(0);
if (!data.initialized) {
- WARN_PRINT_ONCE("Attempting to access theme items too early; prefer NOTIFICATION_POSTINITIALIZE and NOTIFICATION_THEME_CHANGED");
+ WARN_PRINT_ONCE(vformat("Attempting to access theme items too early in %s; prefer NOTIFICATION_POSTINITIALIZE and NOTIFICATION_THEME_CHANGED", this->get_description()));
}
if (p_theme_type == StringName() || p_theme_type == get_class_name() || p_theme_type == data.theme_type_variation) {
@@ -2629,7 +2639,7 @@ int Control::get_theme_font_size(const StringName &p_name, const StringName &p_t
Color Control::get_theme_color(const StringName &p_name, const StringName &p_theme_type) const {
ERR_READ_THREAD_GUARD_V(Color());
if (!data.initialized) {
- WARN_PRINT_ONCE("Attempting to access theme items too early; prefer NOTIFICATION_POSTINITIALIZE and NOTIFICATION_THEME_CHANGED");
+ WARN_PRINT_ONCE(vformat("Attempting to access theme items too early in %s; prefer NOTIFICATION_POSTINITIALIZE and NOTIFICATION_THEME_CHANGED", this->get_description()));
}
if (p_theme_type == StringName() || p_theme_type == get_class_name() || p_theme_type == data.theme_type_variation) {
@@ -2653,7 +2663,7 @@ Color Control::get_theme_color(const StringName &p_name, const StringName &p_the
int Control::get_theme_constant(const StringName &p_name, const StringName &p_theme_type) const {
ERR_READ_THREAD_GUARD_V(0);
if (!data.initialized) {
- WARN_PRINT_ONCE("Attempting to access theme items too early; prefer NOTIFICATION_POSTINITIALIZE and NOTIFICATION_THEME_CHANGED");
+ WARN_PRINT_ONCE(vformat("Attempting to access theme items too early in %s; prefer NOTIFICATION_POSTINITIALIZE and NOTIFICATION_THEME_CHANGED", this->get_description()));
}
if (p_theme_type == StringName() || p_theme_type == get_class_name() || p_theme_type == data.theme_type_variation) {
@@ -2704,7 +2714,7 @@ Ref<Texture2D> Control::get_editor_theme_icon(const StringName &p_name) const {
bool Control::has_theme_icon(const StringName &p_name, const StringName &p_theme_type) const {
ERR_READ_THREAD_GUARD_V(false);
if (!data.initialized) {
- WARN_PRINT_ONCE("Attempting to access theme items too early; prefer NOTIFICATION_POSTINITIALIZE and NOTIFICATION_THEME_CHANGED");
+ WARN_PRINT_ONCE(vformat("Attempting to access theme items too early in %s; prefer NOTIFICATION_POSTINITIALIZE and NOTIFICATION_THEME_CHANGED", this->get_description()));
}
if (p_theme_type == StringName() || p_theme_type == get_class_name() || p_theme_type == data.theme_type_variation) {
@@ -2721,7 +2731,7 @@ bool Control::has_theme_icon(const StringName &p_name, const StringName &p_theme
bool Control::has_theme_stylebox(const StringName &p_name, const StringName &p_theme_type) const {
ERR_READ_THREAD_GUARD_V(false);
if (!data.initialized) {
- WARN_PRINT_ONCE("Attempting to access theme items too early; prefer NOTIFICATION_POSTINITIALIZE and NOTIFICATION_THEME_CHANGED");
+ WARN_PRINT_ONCE(vformat("Attempting to access theme items too early in %s; prefer NOTIFICATION_POSTINITIALIZE and NOTIFICATION_THEME_CHANGED", this->get_description()));
}
if (p_theme_type == StringName() || p_theme_type == get_class_name() || p_theme_type == data.theme_type_variation) {
@@ -2738,7 +2748,7 @@ bool Control::has_theme_stylebox(const StringName &p_name, const StringName &p_t
bool Control::has_theme_font(const StringName &p_name, const StringName &p_theme_type) const {
ERR_READ_THREAD_GUARD_V(false);
if (!data.initialized) {
- WARN_PRINT_ONCE("Attempting to access theme items too early; prefer NOTIFICATION_POSTINITIALIZE and NOTIFICATION_THEME_CHANGED");
+ WARN_PRINT_ONCE(vformat("Attempting to access theme items too early in %s; prefer NOTIFICATION_POSTINITIALIZE and NOTIFICATION_THEME_CHANGED", this->get_description()));
}
if (p_theme_type == StringName() || p_theme_type == get_class_name() || p_theme_type == data.theme_type_variation) {
@@ -2755,7 +2765,7 @@ bool Control::has_theme_font(const StringName &p_name, const StringName &p_theme
bool Control::has_theme_font_size(const StringName &p_name, const StringName &p_theme_type) const {
ERR_READ_THREAD_GUARD_V(false);
if (!data.initialized) {
- WARN_PRINT_ONCE("Attempting to access theme items too early; prefer NOTIFICATION_POSTINITIALIZE and NOTIFICATION_THEME_CHANGED");
+ WARN_PRINT_ONCE(vformat("Attempting to access theme items too early in %s; prefer NOTIFICATION_POSTINITIALIZE and NOTIFICATION_THEME_CHANGED", this->get_description()));
}
if (p_theme_type == StringName() || p_theme_type == get_class_name() || p_theme_type == data.theme_type_variation) {
@@ -2772,7 +2782,7 @@ bool Control::has_theme_font_size(const StringName &p_name, const StringName &p_
bool Control::has_theme_color(const StringName &p_name, const StringName &p_theme_type) const {
ERR_READ_THREAD_GUARD_V(false);
if (!data.initialized) {
- WARN_PRINT_ONCE("Attempting to access theme items too early; prefer NOTIFICATION_POSTINITIALIZE and NOTIFICATION_THEME_CHANGED");
+ WARN_PRINT_ONCE(vformat("Attempting to access theme items too early in %s; prefer NOTIFICATION_POSTINITIALIZE and NOTIFICATION_THEME_CHANGED", this->get_description()));
}
if (p_theme_type == StringName() || p_theme_type == get_class_name() || p_theme_type == data.theme_type_variation) {
@@ -2789,7 +2799,7 @@ bool Control::has_theme_color(const StringName &p_name, const StringName &p_them
bool Control::has_theme_constant(const StringName &p_name, const StringName &p_theme_type) const {
ERR_READ_THREAD_GUARD_V(false);
if (!data.initialized) {
- WARN_PRINT_ONCE("Attempting to access theme items too early; prefer NOTIFICATION_POSTINITIALIZE and NOTIFICATION_THEME_CHANGED");
+ WARN_PRINT_ONCE(vformat("Attempting to access theme items too early in %s; prefer NOTIFICATION_POSTINITIALIZE and NOTIFICATION_THEME_CHANGED", this->get_description()));
}
if (p_theme_type == StringName() || p_theme_type == get_class_name() || p_theme_type == data.theme_type_variation) {
@@ -3568,6 +3578,8 @@ void Control::_bind_methods() {
BIND_CONSTANT(NOTIFICATION_RESIZED);
BIND_CONSTANT(NOTIFICATION_MOUSE_ENTER);
BIND_CONSTANT(NOTIFICATION_MOUSE_EXIT);
+ BIND_CONSTANT(NOTIFICATION_MOUSE_ENTER_SELF);
+ BIND_CONSTANT(NOTIFICATION_MOUSE_EXIT_SELF);
BIND_CONSTANT(NOTIFICATION_FOCUS_ENTER);
BIND_CONSTANT(NOTIFICATION_FOCUS_EXIT);
BIND_CONSTANT(NOTIFICATION_THEME_CHANGED);
diff --git a/scene/gui/control.h b/scene/gui/control.h
index abbdc42fa4..db1bd3a346 100644
--- a/scene/gui/control.h
+++ b/scene/gui/control.h
@@ -368,6 +368,8 @@ public:
NOTIFICATION_SCROLL_BEGIN = 47,
NOTIFICATION_SCROLL_END = 48,
NOTIFICATION_LAYOUT_DIRECTION_CHANGED = 49,
+ NOTIFICATION_MOUSE_ENTER_SELF = 60,
+ NOTIFICATION_MOUSE_EXIT_SELF = 61,
};
// Editor plugin interoperability.
diff --git a/scene/gui/dialogs.cpp b/scene/gui/dialogs.cpp
index aff0ed6f06..957a8f276e 100644
--- a/scene/gui/dialogs.cpp
+++ b/scene/gui/dialogs.cpp
@@ -405,6 +405,7 @@ AcceptDialog::AcceptDialog() {
set_transient(true);
set_exclusive(true);
set_clamp_to_embedder(true);
+ set_keep_title_visible(true);
bg_panel = memnew(Panel);
add_child(bg_panel, false, INTERNAL_MODE_FRONT);
diff --git a/scene/gui/file_dialog.cpp b/scene/gui/file_dialog.cpp
index 3857281a66..d721ee3ec3 100644
--- a/scene/gui/file_dialog.cpp
+++ b/scene/gui/file_dialog.cpp
@@ -56,6 +56,12 @@ void FileDialog::_focus_file_text() {
}
void FileDialog::popup(const Rect2i &p_rect) {
+#ifdef TOOLS_ENABLED
+ if (is_part_of_edited_scene()) {
+ ConfirmationDialog::popup(p_rect);
+ }
+#endif
+
if (access == ACCESS_FILESYSTEM && DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_NATIVE_DIALOG) && (use_native_dialog || OS::get_singleton()->is_sandboxed())) {
DisplayServer::get_singleton()->file_dialog_show(get_title(), dir->get_text(), file->get_text().get_file(), show_hidden_files, DisplayServer::FileDialogMode(mode), filters, callable_mp(this, &FileDialog::_native_dialog_cb));
} else {
@@ -64,6 +70,13 @@ void FileDialog::popup(const Rect2i &p_rect) {
}
void FileDialog::set_visible(bool p_visible) {
+#ifdef TOOLS_ENABLED
+ if (is_part_of_edited_scene()) {
+ ConfirmationDialog::set_visible(p_visible);
+ return;
+ }
+#endif
+
if (access == ACCESS_FILESYSTEM && DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_NATIVE_DIALOG) && (use_native_dialog || OS::get_singleton()->is_sandboxed())) {
if (p_visible) {
DisplayServer::get_singleton()->file_dialog_show(get_title(), dir->get_text(), file->get_text().get_file(), show_hidden_files, DisplayServer::FileDialogMode(mode), filters, callable_mp(this, &FileDialog::_native_dialog_cb));
@@ -73,7 +86,7 @@ void FileDialog::set_visible(bool p_visible) {
}
}
-void FileDialog::_native_dialog_cb(bool p_ok, const Vector<String> &p_files) {
+void FileDialog::_native_dialog_cb(bool p_ok, const Vector<String> &p_files, int p_filter) {
if (p_ok) {
if (p_files.size() > 0) {
String f = p_files[0];
@@ -90,6 +103,7 @@ void FileDialog::_native_dialog_cb(bool p_ok, const Vector<String> &p_files) {
}
file->set_text(f);
dir->set_text(f.get_base_dir());
+ _filter_selected(p_filter);
}
} else {
file->set_text("");
@@ -130,30 +144,40 @@ void FileDialog::_notification(int p_what) {
refresh->set_icon(theme_cache.reload);
show_hidden->set_icon(theme_cache.toggle_hidden);
+ dir_up->begin_bulk_theme_override();
dir_up->add_theme_color_override("icon_normal_color", theme_cache.icon_normal_color);
dir_up->add_theme_color_override("icon_hover_color", theme_cache.icon_hover_color);
dir_up->add_theme_color_override("icon_focus_color", theme_cache.icon_focus_color);
dir_up->add_theme_color_override("icon_pressed_color", theme_cache.icon_pressed_color);
+ dir_up->end_bulk_theme_override();
+ dir_prev->begin_bulk_theme_override();
dir_prev->add_theme_color_override("icon_color_normal", theme_cache.icon_normal_color);
dir_prev->add_theme_color_override("icon_color_hover", theme_cache.icon_hover_color);
dir_prev->add_theme_color_override("icon_focus_color", theme_cache.icon_focus_color);
dir_prev->add_theme_color_override("icon_color_pressed", theme_cache.icon_pressed_color);
+ dir_prev->end_bulk_theme_override();
+ dir_next->begin_bulk_theme_override();
dir_next->add_theme_color_override("icon_color_normal", theme_cache.icon_normal_color);
dir_next->add_theme_color_override("icon_color_hover", theme_cache.icon_hover_color);
dir_next->add_theme_color_override("icon_focus_color", theme_cache.icon_focus_color);
dir_next->add_theme_color_override("icon_color_pressed", theme_cache.icon_pressed_color);
+ dir_next->end_bulk_theme_override();
+ refresh->begin_bulk_theme_override();
refresh->add_theme_color_override("icon_normal_color", theme_cache.icon_normal_color);
refresh->add_theme_color_override("icon_hover_color", theme_cache.icon_hover_color);
refresh->add_theme_color_override("icon_focus_color", theme_cache.icon_focus_color);
refresh->add_theme_color_override("icon_pressed_color", theme_cache.icon_pressed_color);
+ refresh->end_bulk_theme_override();
+ show_hidden->begin_bulk_theme_override();
show_hidden->add_theme_color_override("icon_normal_color", theme_cache.icon_normal_color);
show_hidden->add_theme_color_override("icon_hover_color", theme_cache.icon_hover_color);
show_hidden->add_theme_color_override("icon_focus_color", theme_cache.icon_focus_color);
show_hidden->add_theme_color_override("icon_pressed_color", theme_cache.icon_pressed_color);
+ show_hidden->end_bulk_theme_override();
invalidate();
} break;
@@ -662,7 +686,7 @@ void FileDialog::update_file_list() {
files.pop_front();
}
- if (mode != FILE_MODE_SAVE_FILE) {
+ if (mode != FILE_MODE_SAVE_FILE && mode != FILE_MODE_OPEN_DIR) {
// Select the first file from list if nothing is selected.
if (tree->get_root() && tree->get_root()->get_first_child() && tree->get_selected() == nullptr) {
tree->get_root()->get_first_child()->select(0);
diff --git a/scene/gui/file_dialog.h b/scene/gui/file_dialog.h
index 1a87b79fdd..8ae84fc9dc 100644
--- a/scene/gui/file_dialog.h
+++ b/scene/gui/file_dialog.h
@@ -159,7 +159,7 @@ private:
virtual void shortcut_input(const Ref<InputEvent> &p_event) override;
- void _native_dialog_cb(bool p_ok, const Vector<String> &p_files);
+ void _native_dialog_cb(bool p_ok, const Vector<String> &p_files, int p_filter);
bool _is_open_should_be_disabled();
diff --git a/scene/gui/graph_edit.compat.inc b/scene/gui/graph_edit.compat.inc
new file mode 100644
index 0000000000..9059637a2a
--- /dev/null
+++ b/scene/gui/graph_edit.compat.inc
@@ -0,0 +1,46 @@
+/**************************************************************************/
+/* graph_edit.compat.inc */
+/**************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/**************************************************************************/
+/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
+/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
+/* */
+/* 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 DISABLE_DEPRECATED
+
+bool GraphEdit::_is_arrange_nodes_button_hidden_bind_compat_81582() const {
+ return !is_showing_arrange_button();
+}
+
+void GraphEdit::_set_arrange_nodes_button_hidden_bind_compat_81582(bool p_enable) {
+ set_show_arrange_button(!p_enable);
+}
+
+void GraphEdit::_bind_compatibility_methods() {
+ ClassDB::bind_compatibility_method(D_METHOD("is_arrange_nodes_button_hidden"), &GraphEdit::_is_arrange_nodes_button_hidden_bind_compat_81582);
+ ClassDB::bind_compatibility_method(D_METHOD("set_arrange_nodes_button_hidden", "enable"), &GraphEdit::_set_arrange_nodes_button_hidden_bind_compat_81582);
+}
+
+#endif
diff --git a/scene/gui/graph_edit.cpp b/scene/gui/graph_edit.cpp
index 6e12e7f196..8dddbf78cf 100644
--- a/scene/gui/graph_edit.cpp
+++ b/scene/gui/graph_edit.cpp
@@ -29,6 +29,7 @@
/**************************************************************************/
#include "graph_edit.h"
+#include "graph_edit.compat.inc"
#include "core/input/input.h"
#include "core/math/math_funcs.h"
@@ -36,6 +37,10 @@
#include "scene/gui/box_container.h"
#include "scene/gui/button.h"
#include "scene/gui/graph_edit_arranger.h"
+#include "scene/gui/label.h"
+#include "scene/gui/panel_container.h"
+#include "scene/gui/scroll_bar.h"
+#include "scene/gui/spin_box.h"
#include "scene/gui/view_panner.h"
#include "scene/resources/style_box_flat.h"
#include "scene/theme/theme_db.h"
@@ -507,11 +512,13 @@ void GraphEdit::_notification(int p_what) {
zoom_plus_button->set_icon(theme_cache.zoom_in);
toggle_snapping_button->set_icon(theme_cache.snapping_toggle);
- show_grid_button->set_icon(theme_cache.grid_toggle);
+ toggle_grid_button->set_icon(theme_cache.grid_toggle);
minimap_button->set_icon(theme_cache.minimap_toggle);
- layout_button->set_icon(theme_cache.layout);
+ arrange_button->set_icon(theme_cache.layout);
zoom_label->set_custom_minimum_size(Size2(48, 0) * theme_cache.base_scale);
+
+ menu_panel->add_theme_style_override("panel", theme_cache.menu_panel);
} break;
case NOTIFICATION_READY: {
@@ -591,7 +598,7 @@ bool GraphEdit::_filter_input(const Point2 &p_point) {
// Determine slot height.
int slot_index = graph_node->get_input_port_slot(j);
- Control *child = Object::cast_to<Control>(graph_node->get_child(slot_index));
+ Control *child = Object::cast_to<Control>(graph_node->get_child(slot_index, false));
port_size.height = MAX(port_size.height, child ? child->get_size().y : 0);
@@ -605,7 +612,7 @@ bool GraphEdit::_filter_input(const Point2 &p_point) {
// Determine slot height.
int slot_index = graph_node->get_output_port_slot(j);
- Control *child = Object::cast_to<Control>(graph_node->get_child(slot_index));
+ Control *child = Object::cast_to<Control>(graph_node->get_child(slot_index, false));
port_size.height = MAX(port_size.height, child ? child->get_size().y : 0);
if (is_in_output_hotzone(graph_node, j, p_point / zoom, port_size)) {
@@ -636,7 +643,7 @@ void GraphEdit::_top_layer_input(const Ref<InputEvent> &p_ev) {
// Determine slot height.
int slot_index = graph_node->get_output_port_slot(j);
- Control *child = Object::cast_to<Control>(graph_node->get_child(slot_index));
+ Control *child = Object::cast_to<Control>(graph_node->get_child(slot_index, false));
port_size.height = MAX(port_size.height, child ? child->get_size().y : 0);
if (is_in_output_hotzone(graph_node, j, click_pos, port_size)) {
@@ -693,7 +700,7 @@ void GraphEdit::_top_layer_input(const Ref<InputEvent> &p_ev) {
// Determine slot height.
int slot_index = graph_node->get_input_port_slot(j);
- Control *child = Object::cast_to<Control>(graph_node->get_child(slot_index));
+ Control *child = Object::cast_to<Control>(graph_node->get_child(slot_index, false));
port_size.height = MAX(port_size.height, child ? child->get_size().y : 0);
if (is_in_input_hotzone(graph_node, j, click_pos, port_size)) {
@@ -770,7 +777,7 @@ void GraphEdit::_top_layer_input(const Ref<InputEvent> &p_ev) {
// Determine slot height.
int slot_index = graph_node->get_output_port_slot(j);
- Control *child = Object::cast_to<Control>(graph_node->get_child(slot_index));
+ Control *child = Object::cast_to<Control>(graph_node->get_child(slot_index, false));
port_size.height = MAX(port_size.height, child ? child->get_size().y : 0);
int type = graph_node->get_output_port_type(j);
@@ -794,7 +801,7 @@ void GraphEdit::_top_layer_input(const Ref<InputEvent> &p_ev) {
// Determine slot height.
int slot_index = graph_node->get_input_port_slot(j);
- Control *child = Object::cast_to<Control>(graph_node->get_child(slot_index));
+ Control *child = Object::cast_to<Control>(graph_node->get_child(slot_index, false));
port_size.height = MAX(port_size.height, child ? child->get_size().y : 0);
int type = graph_node->get_input_port_type(j);
@@ -1384,7 +1391,7 @@ void GraphEdit::gui_input(const Ref<InputEvent> &p_ev) {
}
}
- emit_signal(SNAME("close_nodes_request"), nodes);
+ emit_signal(SNAME("delete_nodes_request"), nodes);
accept_event();
}
}
@@ -1402,7 +1409,7 @@ void GraphEdit::_zoom_callback(float p_zoom_factor, Vector2 p_origin, Ref<InputE
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_node == p_from && E.from_port == p_from_port && E.to_node == p_to && E.to_port == p_to_port) {
- if (Math::is_equal_approx(E.activity, p_activity)) {
+ if (!Math::is_equal_approx(E.activity, p_activity)) {
// Update only if changed.
top_layer->queue_redraw();
minimap->queue_redraw();
@@ -1528,18 +1535,6 @@ float GraphEdit::get_zoom_max() const {
return zoom_max;
}
-void GraphEdit::set_show_zoom_label(bool p_enable) {
- if (zoom_label->is_visible() == p_enable) {
- return;
- }
-
- zoom_label->set_visible(p_enable);
-}
-
-bool GraphEdit::is_showing_zoom_label() const {
- return zoom_label->is_visible();
-}
-
void GraphEdit::set_right_disconnects(bool p_enable) {
right_disconnects = p_enable;
}
@@ -1644,7 +1639,7 @@ void GraphEdit::set_show_grid(bool p_show) {
}
show_grid = p_show;
- show_grid_button->set_pressed(p_show);
+ toggle_grid_button->set_pressed(p_show);
queue_redraw();
}
@@ -1662,7 +1657,7 @@ void GraphEdit::_snapping_distance_changed(double) {
}
void GraphEdit::_show_grid_toggled() {
- show_grid = show_grid_button->is_pressed();
+ show_grid = toggle_grid_button->is_pressed();
queue_redraw();
}
@@ -1708,17 +1703,64 @@ bool GraphEdit::is_minimap_enabled() const {
return minimap_button->is_pressed();
}
-void GraphEdit::set_arrange_nodes_button_hidden(bool p_enable) {
- arrange_nodes_button_hidden = p_enable;
- if (arrange_nodes_button_hidden) {
- layout_button->hide();
- } else {
- layout_button->show();
- }
+void GraphEdit::set_show_menu(bool p_hidden) {
+ show_menu = p_hidden;
+ menu_panel->set_visible(show_menu);
+}
+
+bool GraphEdit::is_showing_menu() const {
+ return show_menu;
+}
+
+void GraphEdit::set_show_zoom_label(bool p_hidden) {
+ show_zoom_label = p_hidden;
+ zoom_label->set_visible(show_zoom_label);
+}
+
+bool GraphEdit::is_showing_zoom_label() const {
+ return show_zoom_label;
+}
+
+void GraphEdit::set_show_zoom_buttons(bool p_hidden) {
+ show_zoom_buttons = p_hidden;
+
+ zoom_minus_button->set_visible(show_zoom_buttons);
+ zoom_reset_button->set_visible(show_zoom_buttons);
+ zoom_plus_button->set_visible(show_zoom_buttons);
+}
+
+bool GraphEdit::is_showing_zoom_buttons() const {
+ return show_zoom_buttons;
}
-bool GraphEdit::is_arrange_nodes_button_hidden() const {
- return arrange_nodes_button_hidden;
+void GraphEdit::set_show_grid_buttons(bool p_hidden) {
+ show_grid_buttons = p_hidden;
+
+ toggle_grid_button->set_visible(show_grid_buttons);
+ toggle_snapping_button->set_visible(show_grid_buttons);
+ snapping_distance_spinbox->set_visible(show_grid_buttons);
+}
+
+bool GraphEdit::is_showing_grid_buttons() const {
+ return show_grid_buttons;
+}
+
+void GraphEdit::set_show_minimap_button(bool p_hidden) {
+ show_minimap_button = p_hidden;
+ minimap_button->set_visible(show_minimap_button);
+}
+
+bool GraphEdit::is_showing_minimap_button() const {
+ return show_minimap_button;
+}
+
+void GraphEdit::set_show_arrange_button(bool p_hidden) {
+ show_arrange_button = p_hidden;
+ arrange_button->set_visible(show_arrange_button);
+}
+
+bool GraphEdit::is_showing_arrange_button() const {
+ return show_arrange_button;
}
void GraphEdit::_minimap_toggled() {
@@ -1814,9 +1856,6 @@ void GraphEdit::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_zoom_step", "zoom_step"), &GraphEdit::set_zoom_step);
ClassDB::bind_method(D_METHOD("get_zoom_step"), &GraphEdit::get_zoom_step);
- ClassDB::bind_method(D_METHOD("set_show_zoom_label", "enable"), &GraphEdit::set_show_zoom_label);
- ClassDB::bind_method(D_METHOD("is_showing_zoom_label"), &GraphEdit::is_showing_zoom_label);
-
ClassDB::bind_method(D_METHOD("set_show_grid", "enable"), &GraphEdit::set_show_grid);
ClassDB::bind_method(D_METHOD("is_showing_grid"), &GraphEdit::is_showing_grid);
@@ -1843,8 +1882,23 @@ void GraphEdit::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_minimap_enabled", "enable"), &GraphEdit::set_minimap_enabled);
ClassDB::bind_method(D_METHOD("is_minimap_enabled"), &GraphEdit::is_minimap_enabled);
- ClassDB::bind_method(D_METHOD("set_arrange_nodes_button_hidden", "enable"), &GraphEdit::set_arrange_nodes_button_hidden);
- ClassDB::bind_method(D_METHOD("is_arrange_nodes_button_hidden"), &GraphEdit::is_arrange_nodes_button_hidden);
+ ClassDB::bind_method(D_METHOD("set_show_menu", "hidden"), &GraphEdit::set_show_menu);
+ ClassDB::bind_method(D_METHOD("is_showing_menu"), &GraphEdit::is_showing_menu);
+
+ ClassDB::bind_method(D_METHOD("set_show_zoom_label", "enable"), &GraphEdit::set_show_zoom_label);
+ ClassDB::bind_method(D_METHOD("is_showing_zoom_label"), &GraphEdit::is_showing_zoom_label);
+
+ ClassDB::bind_method(D_METHOD("set_show_grid_buttons", "hidden"), &GraphEdit::set_show_grid_buttons);
+ ClassDB::bind_method(D_METHOD("is_showing_grid_buttons"), &GraphEdit::is_showing_grid_buttons);
+
+ ClassDB::bind_method(D_METHOD("set_show_zoom_buttons", "hidden"), &GraphEdit::set_show_zoom_buttons);
+ ClassDB::bind_method(D_METHOD("is_showing_zoom_buttons"), &GraphEdit::is_showing_zoom_buttons);
+
+ ClassDB::bind_method(D_METHOD("set_show_minimap_button", "hidden"), &GraphEdit::set_show_minimap_button);
+ ClassDB::bind_method(D_METHOD("is_showing_minimap_button"), &GraphEdit::is_showing_minimap_button);
+
+ ClassDB::bind_method(D_METHOD("set_show_arrange_button", "hidden"), &GraphEdit::set_show_arrange_button);
+ ClassDB::bind_method(D_METHOD("is_showing_arrange_button"), &GraphEdit::is_showing_arrange_button);
ClassDB::bind_method(D_METHOD("set_right_disconnects", "enable"), &GraphEdit::set_right_disconnects);
ClassDB::bind_method(D_METHOD("is_right_disconnects_enabled"), &GraphEdit::is_right_disconnects_enabled);
@@ -1861,12 +1915,12 @@ void GraphEdit::_bind_methods() {
GDVIRTUAL_BIND(_get_connection_line, "from_position", "to_position")
GDVIRTUAL_BIND(_is_node_hover_valid, "from_node", "from_port", "to_node", "to_port");
- ADD_PROPERTY(PropertyInfo(Variant::BOOL, "right_disconnects"), "set_right_disconnects", "is_right_disconnects_enabled");
ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "scroll_offset", PROPERTY_HINT_NONE, "suffix:px"), "set_scroll_offset", "get_scroll_offset");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "show_grid"), "set_show_grid", "is_showing_grid");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "snapping_enabled"), "set_snapping_enabled", "is_snapping_enabled");
ADD_PROPERTY(PropertyInfo(Variant::INT, "snapping_distance", PROPERTY_HINT_NONE, "suffix:px"), "set_snapping_distance", "get_snapping_distance");
ADD_PROPERTY(PropertyInfo(Variant::INT, "panning_scheme", PROPERTY_HINT_ENUM, "Scroll Zooms,Scroll Pans"), "set_panning_scheme", "get_panning_scheme");
+ ADD_PROPERTY(PropertyInfo(Variant::BOOL, "right_disconnects"), "set_right_disconnects", "is_right_disconnects_enabled");
ADD_GROUP("Connection Lines", "connection_lines");
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "connection_lines_curvature"), "set_connection_lines_curvature", "get_connection_lines_curvature");
@@ -1878,32 +1932,40 @@ void GraphEdit::_bind_methods() {
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "zoom_min"), "set_zoom_min", "get_zoom_min");
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "zoom_max"), "set_zoom_max", "get_zoom_max");
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "zoom_step"), "set_zoom_step", "get_zoom_step");
- ADD_PROPERTY(PropertyInfo(Variant::BOOL, "show_zoom_label"), "set_show_zoom_label", "is_showing_zoom_label");
ADD_GROUP("Minimap", "minimap_");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "minimap_enabled"), "set_minimap_enabled", "is_minimap_enabled");
ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "minimap_size", PROPERTY_HINT_NONE, "suffix:px"), "set_minimap_size", "get_minimap_size");
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "minimap_opacity"), "set_minimap_opacity", "get_minimap_opacity");
- ADD_GROUP("UI", "");
- ADD_PROPERTY(PropertyInfo(Variant::BOOL, "arrange_nodes_button_hidden"), "set_arrange_nodes_button_hidden", "is_arrange_nodes_button_hidden");
+ ADD_GROUP("Toolbar Menu", "");
+ ADD_PROPERTY(PropertyInfo(Variant::BOOL, "show_menu"), "set_show_menu", "is_showing_menu");
+ ADD_PROPERTY(PropertyInfo(Variant::BOOL, "show_zoom_label"), "set_show_zoom_label", "is_showing_zoom_label");
+ ADD_PROPERTY(PropertyInfo(Variant::BOOL, "show_zoom_buttons"), "set_show_zoom_buttons", "is_showing_zoom_buttons");
+ ADD_PROPERTY(PropertyInfo(Variant::BOOL, "show_grid_buttons"), "set_show_grid_buttons", "is_showing_grid_buttons");
+ ADD_PROPERTY(PropertyInfo(Variant::BOOL, "show_minimap_button"), "set_show_minimap_button", "is_showing_minimap_button");
+ ADD_PROPERTY(PropertyInfo(Variant::BOOL, "show_arrange_button"), "set_show_arrange_button", "is_showing_arrange_button");
ADD_SIGNAL(MethodInfo("connection_request", PropertyInfo(Variant::STRING_NAME, "from_node"), PropertyInfo(Variant::INT, "from_port"), PropertyInfo(Variant::STRING_NAME, "to_node"), PropertyInfo(Variant::INT, "to_port")));
ADD_SIGNAL(MethodInfo("disconnection_request", PropertyInfo(Variant::STRING_NAME, "from_node"), PropertyInfo(Variant::INT, "from_port"), PropertyInfo(Variant::STRING_NAME, "to_node"), PropertyInfo(Variant::INT, "to_port")));
- ADD_SIGNAL(MethodInfo("popup_request", PropertyInfo(Variant::VECTOR2, "position")));
- ADD_SIGNAL(MethodInfo("duplicate_nodes_request"));
+ ADD_SIGNAL(MethodInfo("connection_to_empty", PropertyInfo(Variant::STRING_NAME, "from_node"), PropertyInfo(Variant::INT, "from_port"), PropertyInfo(Variant::VECTOR2, "release_position")));
+ ADD_SIGNAL(MethodInfo("connection_from_empty", PropertyInfo(Variant::STRING_NAME, "to_node"), PropertyInfo(Variant::INT, "to_port"), PropertyInfo(Variant::VECTOR2, "release_position")));
+ ADD_SIGNAL(MethodInfo("connection_drag_started", PropertyInfo(Variant::STRING_NAME, "from_node"), PropertyInfo(Variant::INT, "from_port"), PropertyInfo(Variant::BOOL, "is_output")));
+ ADD_SIGNAL(MethodInfo("connection_drag_ended"));
+
ADD_SIGNAL(MethodInfo("copy_nodes_request"));
ADD_SIGNAL(MethodInfo("paste_nodes_request"));
+ ADD_SIGNAL(MethodInfo("duplicate_nodes_request"));
+ ADD_SIGNAL(MethodInfo("delete_nodes_request", PropertyInfo(Variant::ARRAY, "nodes", PROPERTY_HINT_ARRAY_TYPE, "StringName")));
+
ADD_SIGNAL(MethodInfo("node_selected", PropertyInfo(Variant::OBJECT, "node", PROPERTY_HINT_RESOURCE_TYPE, "Node")));
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_node"), PropertyInfo(Variant::INT, "from_port"), PropertyInfo(Variant::VECTOR2, "release_position")));
- ADD_SIGNAL(MethodInfo("connection_from_empty", PropertyInfo(Variant::STRING_NAME, "to_node"), PropertyInfo(Variant::INT, "to_port"), PropertyInfo(Variant::VECTOR2, "release_position")));
- ADD_SIGNAL(MethodInfo("close_nodes_request", PropertyInfo(Variant::ARRAY, "nodes", PROPERTY_HINT_ARRAY_TYPE, "StringName")));
+
+ ADD_SIGNAL(MethodInfo("popup_request", PropertyInfo(Variant::VECTOR2, "position")));
+
ADD_SIGNAL(MethodInfo("begin_node_move"));
ADD_SIGNAL(MethodInfo("end_node_move"));
ADD_SIGNAL(MethodInfo("scroll_offset_changed", PropertyInfo(Variant::VECTOR2, "offset")));
- ADD_SIGNAL(MethodInfo("connection_drag_started", PropertyInfo(Variant::STRING_NAME, "from_node"), PropertyInfo(Variant::INT, "from_port"), PropertyInfo(Variant::BOOL, "is_output")));
- ADD_SIGNAL(MethodInfo("connection_drag_ended"));
BIND_ENUM_CONSTANT(SCROLL_ZOOMS);
BIND_ENUM_CONSTANT(SCROLL_PANS);
@@ -1916,6 +1978,8 @@ void GraphEdit::_bind_methods() {
BIND_THEME_ITEM(Theme::DATA_TYPE_COLOR, GraphEdit, selection_fill);
BIND_THEME_ITEM(Theme::DATA_TYPE_COLOR, GraphEdit, selection_stroke);
+ BIND_THEME_ITEM(Theme::DATA_TYPE_STYLEBOX, GraphEdit, menu_panel);
+
BIND_THEME_ITEM(Theme::DATA_TYPE_ICON, GraphEdit, zoom_in);
BIND_THEME_ITEM(Theme::DATA_TYPE_ICON, GraphEdit, zoom_out);
BIND_THEME_ITEM(Theme::DATA_TYPE_ICON, GraphEdit, zoom_reset);
@@ -1975,84 +2039,106 @@ GraphEdit::GraphEdit() {
h_scrollbar->connect("value_changed", callable_mp(this, &GraphEdit::_scroll_moved));
v_scrollbar->connect("value_changed", callable_mp(this, &GraphEdit::_scroll_moved));
+ // Toolbar menu.
+
+ menu_panel = memnew(PanelContainer);
+ menu_panel->set_visible(show_menu);
+ top_layer->add_child(menu_panel);
+ menu_panel->set_position(Vector2(10, 10));
+
menu_hbox = memnew(HBoxContainer);
- top_layer->add_child(menu_hbox);
- menu_hbox->set_position(Vector2(10, 10));
+ menu_panel->add_child(menu_hbox);
+
+ // Zoom label and controls.
zoom_label = memnew(Label);
- menu_hbox->add_child(zoom_label);
- zoom_label->set_visible(false);
+ zoom_label->set_visible(show_zoom_label);
zoom_label->set_v_size_flags(Control::SIZE_SHRINK_CENTER);
zoom_label->set_horizontal_alignment(HORIZONTAL_ALIGNMENT_CENTER);
zoom_label->set_custom_minimum_size(Size2(48, 0));
+ menu_hbox->add_child(zoom_label);
_update_zoom_label();
zoom_minus_button = memnew(Button);
zoom_minus_button->set_theme_type_variation("FlatButton");
- menu_hbox->add_child(zoom_minus_button);
+ zoom_minus_button->set_visible(show_zoom_buttons);
zoom_minus_button->set_tooltip_text(RTR("Zoom Out"));
- zoom_minus_button->connect("pressed", callable_mp(this, &GraphEdit::_zoom_minus));
zoom_minus_button->set_focus_mode(FOCUS_NONE);
+ menu_hbox->add_child(zoom_minus_button);
+ zoom_minus_button->connect("pressed", callable_mp(this, &GraphEdit::_zoom_minus));
zoom_reset_button = memnew(Button);
zoom_reset_button->set_theme_type_variation("FlatButton");
- menu_hbox->add_child(zoom_reset_button);
+ zoom_reset_button->set_visible(show_zoom_buttons);
zoom_reset_button->set_tooltip_text(RTR("Zoom Reset"));
- zoom_reset_button->connect("pressed", callable_mp(this, &GraphEdit::_zoom_reset));
zoom_reset_button->set_focus_mode(FOCUS_NONE);
+ menu_hbox->add_child(zoom_reset_button);
+ zoom_reset_button->connect("pressed", callable_mp(this, &GraphEdit::_zoom_reset));
zoom_plus_button = memnew(Button);
zoom_plus_button->set_theme_type_variation("FlatButton");
- menu_hbox->add_child(zoom_plus_button);
+ zoom_plus_button->set_visible(show_zoom_buttons);
zoom_plus_button->set_tooltip_text(RTR("Zoom In"));
- zoom_plus_button->connect("pressed", callable_mp(this, &GraphEdit::_zoom_plus));
zoom_plus_button->set_focus_mode(FOCUS_NONE);
+ menu_hbox->add_child(zoom_plus_button);
+ zoom_plus_button->connect("pressed", callable_mp(this, &GraphEdit::_zoom_plus));
+
+ // Grid controls.
- show_grid_button = memnew(Button);
- show_grid_button->set_theme_type_variation("FlatButton");
- show_grid_button->set_toggle_mode(true);
- show_grid_button->set_tooltip_text(RTR("Toggle the visual grid."));
- show_grid_button->connect("pressed", callable_mp(this, &GraphEdit::_show_grid_toggled));
- show_grid_button->set_pressed(true);
- show_grid_button->set_focus_mode(FOCUS_NONE);
- menu_hbox->add_child(show_grid_button);
+ toggle_grid_button = memnew(Button);
+ toggle_grid_button->set_theme_type_variation("FlatButton");
+ toggle_grid_button->set_visible(show_grid_buttons);
+ toggle_grid_button->set_toggle_mode(true);
+ toggle_grid_button->set_pressed(true);
+ toggle_grid_button->set_tooltip_text(RTR("Toggle the visual grid."));
+ toggle_grid_button->set_focus_mode(FOCUS_NONE);
+ menu_hbox->add_child(toggle_grid_button);
+ toggle_grid_button->connect("pressed", callable_mp(this, &GraphEdit::_show_grid_toggled));
toggle_snapping_button = memnew(Button);
toggle_snapping_button->set_theme_type_variation("FlatButton");
+ toggle_snapping_button->set_visible(show_grid_buttons);
toggle_snapping_button->set_toggle_mode(true);
toggle_snapping_button->set_tooltip_text(RTR("Toggle snapping to the grid."));
- toggle_snapping_button->connect("pressed", callable_mp(this, &GraphEdit::_snapping_toggled));
toggle_snapping_button->set_pressed(snapping_enabled);
toggle_snapping_button->set_focus_mode(FOCUS_NONE);
menu_hbox->add_child(toggle_snapping_button);
+ toggle_snapping_button->connect("pressed", callable_mp(this, &GraphEdit::_snapping_toggled));
snapping_distance_spinbox = memnew(SpinBox);
+ snapping_distance_spinbox->set_visible(show_grid_buttons);
snapping_distance_spinbox->set_min(GRID_MIN_SNAPPING_DISTANCE);
snapping_distance_spinbox->set_max(GRID_MAX_SNAPPING_DISTANCE);
snapping_distance_spinbox->set_step(1);
snapping_distance_spinbox->set_value(snapping_distance);
snapping_distance_spinbox->set_tooltip_text(RTR("Change the snapping distance."));
- snapping_distance_spinbox->connect("value_changed", callable_mp(this, &GraphEdit::_snapping_distance_changed));
menu_hbox->add_child(snapping_distance_spinbox);
+ snapping_distance_spinbox->connect("value_changed", callable_mp(this, &GraphEdit::_snapping_distance_changed));
+
+ // Extra controls.
minimap_button = memnew(Button);
minimap_button->set_theme_type_variation("FlatButton");
+ minimap_button->set_visible(show_minimap_button);
minimap_button->set_toggle_mode(true);
minimap_button->set_tooltip_text(RTR("Toggle the graph minimap."));
- minimap_button->connect("pressed", callable_mp(this, &GraphEdit::_minimap_toggled));
minimap_button->set_pressed(show_grid);
minimap_button->set_focus_mode(FOCUS_NONE);
menu_hbox->add_child(minimap_button);
+ minimap_button->connect("pressed", callable_mp(this, &GraphEdit::_minimap_toggled));
+
+ arrange_button = memnew(Button);
+ arrange_button->set_theme_type_variation("FlatButton");
+ arrange_button->set_visible(show_arrange_button);
+ arrange_button->connect("pressed", callable_mp(this, &GraphEdit::arrange_nodes));
+ arrange_button->set_focus_mode(FOCUS_NONE);
+ menu_hbox->add_child(arrange_button);
+ arrange_button->set_tooltip_text(RTR("Automatically arrange selected nodes."));
- layout_button = memnew(Button);
- layout_button->set_theme_type_variation("FlatButton");
- menu_hbox->add_child(layout_button);
- layout_button->set_tooltip_text(RTR("Automatically arrange selected nodes."));
- layout_button->connect("pressed", callable_mp(this, &GraphEdit::arrange_nodes));
- layout_button->set_focus_mode(FOCUS_NONE);
+ // Minimap.
- Vector2 minimap_size = Vector2(240, 160);
- float minimap_opacity = 0.65;
+ const Vector2 minimap_size = Vector2(240, 160);
+ const float minimap_opacity = 0.65;
minimap = memnew(GraphEditMinimap(this));
top_layer->add_child(minimap);
diff --git a/scene/gui/graph_edit.h b/scene/gui/graph_edit.h
index 6b5698ad41..a7be9ac0b3 100644
--- a/scene/gui/graph_edit.h
+++ b/scene/gui/graph_edit.h
@@ -32,15 +32,17 @@
#define GRAPH_EDIT_H
#include "scene/gui/box_container.h"
-#include "scene/gui/button.h"
#include "scene/gui/graph_node.h"
-#include "scene/gui/label.h"
-#include "scene/gui/scroll_bar.h"
-#include "scene/gui/spin_box.h"
+class Button;
class GraphEdit;
class GraphEditArranger;
+class HScrollBar;
+class Label;
+class PanelContainer;
+class SpinBox;
class ViewPanner;
+class VScrollBar;
class GraphEditFilter : public Control {
GDCLASS(GraphEditFilter, Control);
@@ -154,10 +156,9 @@ private:
Button *toggle_snapping_button = nullptr;
SpinBox *snapping_distance_spinbox = nullptr;
- Button *show_grid_button = nullptr;
+ Button *toggle_grid_button = nullptr;
Button *minimap_button = nullptr;
-
- Button *layout_button = nullptr;
+ Button *arrange_button = nullptr;
HScrollBar *h_scrollbar = nullptr;
VScrollBar *v_scrollbar = nullptr;
@@ -165,7 +166,12 @@ private:
Ref<ViewPanner> panner;
bool warped_panning = true;
- bool arrange_nodes_button_hidden = false;
+ bool show_menu = true;
+ bool show_zoom_label = false;
+ bool show_grid_buttons = true;
+ bool show_zoom_buttons = true;
+ bool show_minimap_button = true;
+ bool show_arrange_button = true;
bool snapping_enabled = true;
int snapping_distance = 20;
@@ -216,6 +222,7 @@ private:
float lines_curvature = 0.5f;
bool lines_antialiased = true;
+ PanelContainer *menu_panel = nullptr;
HBoxContainer *menu_hbox = nullptr;
Control *connections_layer = nullptr;
GraphEditFilter *top_layer = nullptr;
@@ -238,6 +245,8 @@ private:
Color selection_fill;
Color selection_stroke;
+ Ref<StyleBox> menu_panel;
+
Ref<Texture2D> zoom_in;
Ref<Texture2D> zoom_out;
Ref<Texture2D> zoom_reset;
@@ -293,6 +302,11 @@ private:
bool _check_clickable_control(Control *p_control, const Vector2 &r_mouse_pos, const Vector2 &p_offset);
+#ifndef DISABLE_DEPRECATED
+ bool _is_arrange_nodes_button_hidden_bind_compat_81582() const;
+ void _set_arrange_nodes_button_hidden_bind_compat_81582(bool p_enable);
+#endif
+
protected:
virtual void _update_theme_item_cache() override;
@@ -301,6 +315,9 @@ protected:
void _notification(int p_what);
static void _bind_methods();
+#ifndef DISABLE_DEPRECATED
+ static void _bind_compatibility_methods();
+#endif
virtual bool is_in_input_hotzone(GraphNode *p_graph_node, int p_port_idx, const Vector2 &p_mouse_pos, const Vector2i &p_port_size);
virtual bool is_in_output_hotzone(GraphNode *p_graph_node, int p_port_idx, const Vector2 &p_mouse_pos, const Vector2i &p_port_size);
@@ -346,9 +363,6 @@ public:
void set_zoom_step(float p_zoom_step);
float get_zoom_step() const;
- void set_show_zoom_label(bool p_enable);
- bool is_showing_zoom_label() const;
-
void set_minimap_size(Vector2 p_size);
Vector2 get_minimap_size() const;
void set_minimap_opacity(float p_opacity);
@@ -357,8 +371,18 @@ public:
void set_minimap_enabled(bool p_enable);
bool is_minimap_enabled() const;
- void set_arrange_nodes_button_hidden(bool p_enable);
- bool is_arrange_nodes_button_hidden() const;
+ void set_show_menu(bool p_hidden);
+ bool is_showing_menu() const;
+ void set_show_zoom_label(bool p_hidden);
+ bool is_showing_zoom_label() const;
+ void set_show_grid_buttons(bool p_hidden);
+ bool is_showing_grid_buttons() const;
+ void set_show_zoom_buttons(bool p_hidden);
+ bool is_showing_zoom_buttons() const;
+ void set_show_minimap_button(bool p_hidden);
+ bool is_showing_minimap_button() const;
+ void set_show_arrange_button(bool p_hidden);
+ bool is_showing_arrange_button() const;
GraphEditFilter *get_top_layer() const { return top_layer; }
GraphEditMinimap *get_minimap() const { return minimap; }
diff --git a/scene/gui/graph_edit_arranger.cpp b/scene/gui/graph_edit_arranger.cpp
index 1dc778254b..3f2007f7e0 100644
--- a/scene/gui/graph_edit_arranger.cpp
+++ b/scene/gui/graph_edit_arranger.cpp
@@ -65,6 +65,9 @@ void GraphEditArranger::arrange_nodes() {
float gap_v = 100.0f;
float gap_h = 100.0f;
+ List<GraphEdit::Connection> connection_list;
+ graph_edit->get_connection_list(&connection_list);
+
for (int i = graph_edit->get_child_count() - 1; i >= 0; i--) {
GraphNode *graph_element = Object::cast_to<GraphNode>(graph_edit->get_child(i));
if (!graph_element) {
@@ -74,8 +77,6 @@ void GraphEditArranger::arrange_nodes() {
if (graph_element->is_selected() || arrange_entire_graph) {
selected_nodes.insert(graph_element->get_name());
HashSet<StringName> s;
- List<GraphEdit::Connection> connection_list;
- graph_edit->get_connection_list(&connection_list);
for (List<GraphEdit::Connection>::Element *E = connection_list.front(); E; E = E->next()) {
GraphNode *p_from = Object::cast_to<GraphNode>(node_names[E->get().from_node]);
if (E->get().to_node == graph_element->get_name() && (p_from->is_selected() || arrange_entire_graph) && E->get().to_node != E->get().from_node) {
@@ -85,12 +86,6 @@ void GraphEditArranger::arrange_nodes() {
String s_connection = String(p_from->get_name()) + " " + String(E->get().to_node);
StringName _connection(s_connection);
Pair<int, int> ports(E->get().from_port, E->get().to_port);
- if (port_info.has(_connection)) {
- Pair<int, int> p_ports = port_info[_connection];
- if (p_ports.first < ports.first) {
- ports = p_ports;
- }
- }
port_info.insert(_connection, ports);
}
}
@@ -216,13 +211,14 @@ int GraphEditArranger::_set_operations(SET_OPERATIONS p_operation, HashSet<Strin
return 1;
} break;
case GraphEditArranger::DIFFERENCE: {
- for (HashSet<StringName>::Iterator E = r_u.begin(); E;) {
- HashSet<StringName>::Iterator N = E;
- ++N;
- if (r_v.has(*E)) {
- r_u.remove(E);
+ Vector<StringName> common;
+ for (const StringName &E : r_u) {
+ if (r_v.has(E)) {
+ common.append(E);
}
- E = N;
+ }
+ for (const StringName &E : common) {
+ r_u.erase(E);
}
return r_u.size();
} break;
@@ -260,9 +256,7 @@ HashMap<int, Vector<StringName>> GraphEditArranger::_layering(const HashSet<Stri
selected = true;
t.append_array(l[current_layer]);
l.insert(current_layer, t);
- HashSet<StringName> V;
- V.insert(E);
- _set_operations(GraphEditArranger::UNION, u, V);
+ u.insert(E);
}
}
if (!selected) {
diff --git a/scene/gui/graph_element.cpp b/scene/gui/graph_element.cpp
index ac2cb8bd5d..7fa5b0ceec 100644
--- a/scene/gui/graph_element.cpp
+++ b/scene/gui/graph_element.cpp
@@ -150,7 +150,7 @@ void GraphElement::gui_input(const Ref<InputEvent> &p_ev) {
Ref<InputEventMouseButton> mb = p_ev;
if (mb.is_valid()) {
- ERR_FAIL_COND_MSG(get_parent_control() == nullptr, "GraphElement must be the child of a GraphEdit node.");
+ ERR_FAIL_NULL_MSG(get_parent_control(), "GraphElement must be the child of a GraphEdit node.");
if (mb->is_pressed() && mb->get_button_index() == MouseButton::LEFT) {
Vector2 mpos = mb->get_position();
@@ -233,13 +233,15 @@ void GraphElement::_bind_methods() {
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "selectable"), "set_selectable", "is_selectable");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "selected"), "set_selected", "is_selected");
- ADD_SIGNAL(MethodInfo("position_offset_changed"));
ADD_SIGNAL(MethodInfo("node_selected"));
ADD_SIGNAL(MethodInfo("node_deselected"));
- ADD_SIGNAL(MethodInfo("dragged", PropertyInfo(Variant::VECTOR2, "from"), PropertyInfo(Variant::VECTOR2, "to")));
+
ADD_SIGNAL(MethodInfo("raise_request"));
- ADD_SIGNAL(MethodInfo("close_request"));
+ ADD_SIGNAL(MethodInfo("delete_request"));
ADD_SIGNAL(MethodInfo("resize_request", PropertyInfo(Variant::VECTOR2, "new_minsize")));
+ ADD_SIGNAL(MethodInfo("dragged", PropertyInfo(Variant::VECTOR2, "from"), PropertyInfo(Variant::VECTOR2, "to")));
+ ADD_SIGNAL(MethodInfo("position_offset_changed"));
+
BIND_THEME_ITEM(Theme::DATA_TYPE_ICON, GraphElement, resizer);
}
diff --git a/scene/gui/graph_node.cpp b/scene/gui/graph_node.cpp
index fdebca3d28..3b1c1a153f 100644
--- a/scene/gui/graph_node.cpp
+++ b/scene/gui/graph_node.cpp
@@ -620,7 +620,7 @@ void GraphNode::_port_pos_update() {
port_cache.pos = Point2i(edgeofs, vertical_ofs + size.height / 2);
port_cache.type = slot_table[i].type_left;
port_cache.color = slot_table[i].color_left;
- port_cache.slot_index = child->get_index(); // Index with internal nodes included.
+ port_cache.slot_index = child->get_index(false);
left_port_cache.push_back(port_cache);
}
if (slot_table[i].enable_right) {
@@ -628,7 +628,7 @@ void GraphNode::_port_pos_update() {
port_cache.pos = Point2i(get_size().width - edgeofs, vertical_ofs + size.height / 2);
port_cache.type = slot_table[i].type_right;
port_cache.color = slot_table[i].color_right;
- port_cache.slot_index = child->get_index(); // Index with internal nodes included.
+ port_cache.slot_index = child->get_index(false);
right_port_cache.push_back(port_cache);
}
}
diff --git a/scene/gui/label.cpp b/scene/gui/label.cpp
index 0d48cb1549..2fbd29b048 100644
--- a/scene/gui/label.cpp
+++ b/scene/gui/label.cpp
@@ -33,6 +33,7 @@
#include "core/config/project_settings.h"
#include "core/string/print_string.h"
#include "core/string/translation.h"
+#include "scene/gui/container.h"
#include "scene/theme/theme_db.h"
#include "servers/text_server.h"
@@ -44,6 +45,7 @@ void Label::set_autowrap_mode(TextServer::AutowrapMode p_mode) {
autowrap_mode = p_mode;
lines_dirty = true;
queue_redraw();
+ update_configuration_warnings();
if (clip || overrun_behavior != TextServer::OVERRUN_NO_TRIMMING) {
update_minimum_size();
@@ -327,6 +329,19 @@ inline void draw_glyph_outline(const Glyph &p_gl, const RID &p_canvas, const Col
PackedStringArray Label::get_configuration_warnings() const {
PackedStringArray warnings = Control::get_configuration_warnings();
+ // FIXME: This is not ideal and the sizing model should be fixed,
+ // but for now we have to warn about this impossible to resolve combination.
+ // See GH-83546.
+ if (is_inside_tree() && get_tree()->get_edited_scene_root() != this) {
+ // If the Label happens to be the root node of the edited scene, we don't need
+ // to check what its parent is. It's going to be some node from the editor tree
+ // and it can be a container, but that makes no difference to the user.
+ Container *parent_container = Object::cast_to<Container>(get_parent_control());
+ if (parent_container && autowrap_mode != TextServer::AUTOWRAP_OFF && get_custom_minimum_size() == Size2()) {
+ warnings.push_back(RTR("Labels with autowrapping enabled must have a custom minimum size configured to work correctly inside a container."));
+ }
+ }
+
// Ensure that the font can render all of the required glyphs.
Ref<Font> font;
if (settings.is_valid()) {
diff --git a/scene/gui/line_edit.cpp b/scene/gui/line_edit.cpp
index 100e0c4548..5ed1a9d5e3 100644
--- a/scene/gui/line_edit.cpp
+++ b/scene/gui/line_edit.cpp
@@ -486,6 +486,7 @@ void LineEdit::gui_input(const Ref<InputEvent> &p_event) {
if (k->is_action("ui_cancel")) {
callable_mp((Control *)this, &Control::release_focus).call_deferred();
+ accept_event();
return;
}
@@ -1913,15 +1914,12 @@ bool LineEdit::is_secret() const {
}
void LineEdit::set_secret_character(const String &p_string) {
- // An empty string as the secret character would crash the engine.
- // It also wouldn't make sense to use multiple characters as the secret character.
- ERR_FAIL_COND_MSG(p_string.length() != 1, "Secret character must be exactly one character long (" + itos(p_string.length()) + " characters given).");
-
if (secret_character == p_string) {
return;
}
secret_character = p_string;
+ update_configuration_warnings();
_shape();
queue_redraw();
}
@@ -2265,6 +2263,13 @@ void LineEdit::_emit_text_change() {
emit_signal(SNAME("text_changed"), text);
text_changed_dirty = false;
}
+PackedStringArray LineEdit::get_configuration_warnings() const {
+ PackedStringArray warnings = Control::get_configuration_warnings();
+ if (secret_character.length() > 1) {
+ warnings.push_back("Secret Character property supports only one character. Extra characters will be ignored.");
+ }
+ return warnings;
+}
void LineEdit::_shape() {
const Ref<Font> &font = theme_cache.font;
@@ -2280,7 +2285,14 @@ void LineEdit::_shape() {
if (text.length() == 0 && ime_text.length() == 0) {
t = placeholder_translated;
} else if (pass) {
- t = secret_character.repeat(text.length() + ime_text.length());
+ // TODO: Integrate with text server to add support for non-latin scripts.
+ // Allow secret_character as empty strings, act like if a space was used as a secret character.
+ String secret = " ";
+ // Allow values longer than 1 character in the property, but trim characters after the first one.
+ if (!secret_character.is_empty()) {
+ secret = secret_character.left(1);
+ }
+ t = secret.repeat(text.length() + ime_text.length());
} else {
if (ime_text.length() > 0) {
t = text.substr(0, caret_column) + ime_text + text.substr(caret_column, text.length());
@@ -2621,8 +2633,6 @@ void LineEdit::_bind_methods() {
ADD_PROPERTY(PropertyInfo(Variant::INT, "alignment", PROPERTY_HINT_ENUM, "Left,Center,Right,Fill"), "set_horizontal_alignment", "get_horizontal_alignment");
ADD_PROPERTY(PropertyInfo(Variant::INT, "max_length", PROPERTY_HINT_RANGE, "0,1000,1,or_greater"), "set_max_length", "get_max_length");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "editable"), "set_editable", "is_editable");
- ADD_PROPERTY(PropertyInfo(Variant::BOOL, "secret"), "set_secret", "is_secret");
- ADD_PROPERTY(PropertyInfo(Variant::STRING, "secret_character"), "set_secret_character", "get_secret_character");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "expand_to_text_length"), "set_expand_to_text_length_enabled", "is_expand_to_text_length_enabled");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "context_menu_enabled"), "set_context_menu_enabled", "is_context_menu_enabled");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "virtual_keyboard_enabled"), "set_virtual_keyboard_enabled", "is_virtual_keyboard_enabled");
@@ -2633,7 +2643,7 @@ void LineEdit::_bind_methods() {
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "selecting_enabled"), "set_selecting_enabled", "is_selecting_enabled");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "deselect_on_focus_loss_enabled"), "set_deselect_on_focus_loss_enabled", "is_deselect_on_focus_loss_enabled");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "drag_and_drop_selection_enabled"), "set_drag_and_drop_selection_enabled", "is_drag_and_drop_selection_enabled");
- ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "right_icon", PROPERTY_HINT_RESOURCE_TYPE, "Texture"), "set_right_icon", "get_right_icon");
+ ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "right_icon", PROPERTY_HINT_RESOURCE_TYPE, "Texture2D"), "set_right_icon", "get_right_icon");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "flat"), "set_flat", "is_flat");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "draw_control_chars"), "set_draw_control_chars", "get_draw_control_chars");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "select_all_on_focus"), "set_select_all_on_focus", "is_select_all_on_focus");
@@ -2645,6 +2655,10 @@ void LineEdit::_bind_methods() {
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "caret_force_displayed"), "set_caret_force_displayed", "is_caret_force_displayed");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "caret_mid_grapheme"), "set_caret_mid_grapheme_enabled", "is_caret_mid_grapheme_enabled");
+ ADD_GROUP("Secret", "");
+ ADD_PROPERTY(PropertyInfo(Variant::BOOL, "secret"), "set_secret", "is_secret");
+ ADD_PROPERTY(PropertyInfo(Variant::STRING, "secret_character"), "set_secret_character", "get_secret_character");
+
ADD_GROUP("BiDi", "");
ADD_PROPERTY(PropertyInfo(Variant::INT, "text_direction", PROPERTY_HINT_ENUM, "Auto,Left-to-Right,Right-to-Left,Inherited"), "set_text_direction", "get_text_direction");
ADD_PROPERTY(PropertyInfo(Variant::STRING, "language", PROPERTY_HINT_LOCALE_ID, ""), "set_language", "get_language");
diff --git a/scene/gui/line_edit.h b/scene/gui/line_edit.h
index 4a81f90166..993bc727e4 100644
--- a/scene/gui/line_edit.h
+++ b/scene/gui/line_edit.h
@@ -385,6 +385,8 @@ public:
virtual bool is_text_field() const override;
+ PackedStringArray get_configuration_warnings() const override;
+
void show_virtual_keyboard();
LineEdit(const String &p_placeholder = String());
diff --git a/scene/gui/link_button.cpp b/scene/gui/link_button.cpp
index 5df0bf21a0..1142ba37f5 100644
--- a/scene/gui/link_button.cpp
+++ b/scene/gui/link_button.cpp
@@ -130,6 +130,10 @@ LinkButton::UnderlineMode LinkButton::get_underline_mode() const {
return underline_mode;
}
+Ref<Font> LinkButton::get_button_font() const {
+ return theme_cache.font;
+}
+
void LinkButton::pressed() {
if (uri.is_empty()) {
return;
diff --git a/scene/gui/link_button.h b/scene/gui/link_button.h
index 6ed47087bf..2faddf2df2 100644
--- a/scene/gui/link_button.h
+++ b/scene/gui/link_button.h
@@ -104,6 +104,8 @@ public:
void set_underline_mode(UnderlineMode p_underline_mode);
UnderlineMode get_underline_mode() const;
+ Ref<Font> get_button_font() const;
+
LinkButton(const String &p_text = String());
};
diff --git a/scene/gui/menu_bar.cpp b/scene/gui/menu_bar.cpp
index 13a42d0407..7418ba7333 100644
--- a/scene/gui/menu_bar.cpp
+++ b/scene/gui/menu_bar.cpp
@@ -202,52 +202,6 @@ void MenuBar::_popup_visibility_changed(bool p_visible) {
}
}
-void MenuBar::_update_submenu(const String &p_menu_name, PopupMenu *p_child) {
- int count = p_child->get_item_count();
- global_menus.insert(p_menu_name);
- for (int i = 0; i < count; i++) {
- if (p_child->is_item_separator(i)) {
- DisplayServer::get_singleton()->global_menu_add_separator(p_menu_name);
- } else if (!p_child->get_item_submenu(i).is_empty()) {
- Node *n = p_child->get_node_or_null(p_child->get_item_submenu(i));
- ERR_FAIL_NULL_MSG(n, "Item subnode does not exist: '" + p_child->get_item_submenu(i) + "'.");
- PopupMenu *pm = Object::cast_to<PopupMenu>(n);
- ERR_FAIL_NULL_MSG(pm, "Item subnode is not a PopupMenu: '" + p_child->get_item_submenu(i) + "'.");
-
- DisplayServer::get_singleton()->global_menu_add_submenu_item(p_menu_name, atr(p_child->get_item_text(i)), p_menu_name + "/" + itos(i));
- _update_submenu(p_menu_name + "/" + itos(i), pm);
- } else {
- int index = DisplayServer::get_singleton()->global_menu_add_item(p_menu_name, atr(p_child->get_item_text(i)), callable_mp(p_child, &PopupMenu::activate_item), Callable(), i);
-
- if (p_child->is_item_checkable(i)) {
- DisplayServer::get_singleton()->global_menu_set_item_checkable(p_menu_name, index, true);
- }
- if (p_child->is_item_radio_checkable(i)) {
- DisplayServer::get_singleton()->global_menu_set_item_radio_checkable(p_menu_name, index, true);
- }
- DisplayServer::get_singleton()->global_menu_set_item_checked(p_menu_name, index, p_child->is_item_checked(i));
- DisplayServer::get_singleton()->global_menu_set_item_disabled(p_menu_name, index, p_child->is_item_disabled(i));
- DisplayServer::get_singleton()->global_menu_set_item_max_states(p_menu_name, index, p_child->get_item_max_states(i));
- DisplayServer::get_singleton()->global_menu_set_item_icon(p_menu_name, index, p_child->get_item_icon(i));
- DisplayServer::get_singleton()->global_menu_set_item_state(p_menu_name, index, p_child->get_item_state(i));
- DisplayServer::get_singleton()->global_menu_set_item_indentation_level(p_menu_name, index, p_child->get_item_indent(i));
- DisplayServer::get_singleton()->global_menu_set_item_tooltip(p_menu_name, index, p_child->get_item_tooltip(i));
- if (!p_child->is_item_shortcut_disabled(i) && p_child->get_item_shortcut(i).is_valid() && p_child->get_item_shortcut(i)->has_valid_event()) {
- Array events = p_child->get_item_shortcut(i)->get_events();
- for (int j = 0; j < events.size(); j++) {
- Ref<InputEventKey> ie = events[j];
- if (ie.is_valid()) {
- DisplayServer::get_singleton()->global_menu_set_item_accelerator(p_menu_name, index, ie->get_keycode_with_modifiers());
- break;
- }
- }
- } else if (p_child->get_item_accelerator(i) != Key::NONE) {
- DisplayServer::get_singleton()->global_menu_set_item_accelerator(p_menu_name, i, p_child->get_item_accelerator(i));
- }
- }
- }
-}
-
bool MenuBar::is_native_menu() const {
#ifdef TOOLS_ENABLED
if (is_part_of_edited_scene()) {
@@ -258,52 +212,67 @@ bool MenuBar::is_native_menu() const {
return (DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_GLOBAL_MENU) && is_native);
}
-void MenuBar::_clear_menu() {
+String MenuBar::bind_global_menu() {
+#ifdef TOOLS_ENABLED
+ if (is_part_of_edited_scene()) {
+ return String();
+ }
+#endif
if (!DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_GLOBAL_MENU)) {
- return;
+ return String();
+ }
+
+ if (!global_menu_name.is_empty()) {
+ return global_menu_name; // Already bound.
}
- // Remove root menu items.
- int count = DisplayServer::get_singleton()->global_menu_get_item_count("_main");
- for (int i = count - 1; i >= 0; i--) {
- if (global_menus.has(DisplayServer::get_singleton()->global_menu_get_item_submenu("_main", i))) {
- DisplayServer::get_singleton()->global_menu_remove_item("_main", i);
+ DisplayServer *ds = DisplayServer::get_singleton();
+ global_menu_name = "__MenuBar#" + itos(get_instance_id());
+
+ int global_start_idx = -1;
+ int count = ds->global_menu_get_item_count("_main");
+ String prev_tag;
+ for (int i = 0; i < count; i++) {
+ String tag = ds->global_menu_get_item_tag("_main", i).operator String().get_slice("#", 1);
+ if (!tag.is_empty() && tag != prev_tag) {
+ if (i >= start_index) {
+ global_start_idx = i;
+ break;
+ }
}
+ prev_tag = tag;
}
- // Erase submenu contents.
- for (const String &E : global_menus) {
- DisplayServer::get_singleton()->global_menu_clear(E);
+ if (global_start_idx == -1) {
+ global_start_idx = count;
}
- global_menus.clear();
-}
-void MenuBar::_update_menu() {
- _clear_menu();
+ Vector<PopupMenu *> popups = _get_popups();
+ for (int i = 0; i < menu_cache.size(); i++) {
+ String submenu_name = popups[i]->bind_global_menu();
+ int index = ds->global_menu_add_submenu_item("_main", menu_cache[i].name, submenu_name, global_start_idx + i);
+ ds->global_menu_set_item_tag("_main", index, global_menu_name + "#" + itos(i));
+ ds->global_menu_set_item_hidden("_main", index, menu_cache[i].hidden);
+ ds->global_menu_set_item_disabled("_main", index, menu_cache[i].disabled);
+ ds->global_menu_set_item_tooltip("_main", index, menu_cache[i].tooltip);
+ }
+
+ return global_menu_name;
+}
- if (!is_visible_in_tree()) {
+void MenuBar::unbind_global_menu() {
+ if (global_menu_name.is_empty()) {
return;
}
- int index = start_index;
- if (is_native_menu()) {
- Vector<PopupMenu *> popups = _get_popups();
- String root_name = "MenuBar<" + String::num_int64((uint64_t)this, 16) + ">";
- for (int i = 0; i < popups.size(); i++) {
- if (menu_cache[i].hidden) {
- continue;
- }
- String menu_name = atr(String(popups[i]->get_meta("_menu_name", popups[i]->get_name())));
-
- index = DisplayServer::get_singleton()->global_menu_add_submenu_item("_main", menu_name, root_name + "/" + itos(i), index);
- if (menu_cache[i].disabled) {
- DisplayServer::get_singleton()->global_menu_set_item_disabled("_main", index, true);
- }
- _update_submenu(root_name + "/" + itos(i), popups[i]);
- index++;
- }
+ DisplayServer *ds = DisplayServer::get_singleton();
+ int global_start = _find_global_start_index();
+ Vector<PopupMenu *> popups = _get_popups();
+ for (int i = menu_cache.size() - 1; i >= 0; i--) {
+ popups[i]->unbind_global_menu();
+ ds->global_menu_remove_item("_main", global_start + i);
}
- update_minimum_size();
- queue_redraw();
+
+ global_menu_name = String();
}
void MenuBar::_notification(int p_what) {
@@ -312,25 +281,43 @@ void MenuBar::_notification(int p_what) {
if (get_menu_count() > 0) {
_refresh_menu_names();
}
+ if (is_native_menu()) {
+ bind_global_menu();
+ }
} break;
case NOTIFICATION_EXIT_TREE: {
- _clear_menu();
+ unbind_global_menu();
} break;
case NOTIFICATION_MOUSE_EXIT: {
focused_menu = -1;
selected_menu = -1;
queue_redraw();
} break;
- case NOTIFICATION_TRANSLATION_CHANGED:
+ case NOTIFICATION_TRANSLATION_CHANGED: {
+ DisplayServer *ds = DisplayServer::get_singleton();
+ bool is_global = !global_menu_name.is_empty();
+ int global_start = _find_global_start_index();
+ for (int i = 0; i < menu_cache.size(); i++) {
+ shape(menu_cache.write[i]);
+ if (is_global) {
+ ds->global_menu_set_item_text("_main", global_start + i, atr(menu_cache[i].name));
+ }
+ }
+ } break;
case NOTIFICATION_LAYOUT_DIRECTION_CHANGED:
case NOTIFICATION_THEME_CHANGED: {
for (int i = 0; i < menu_cache.size(); i++) {
shape(menu_cache.write[i]);
}
- _update_menu();
} break;
case NOTIFICATION_VISIBILITY_CHANGED: {
- _update_menu();
+ if (is_native_menu()) {
+ if (is_visible_in_tree()) {
+ bind_global_menu();
+ } else {
+ unbind_global_menu();
+ }
+ }
} break;
case NOTIFICATION_DRAW: {
if (is_native_menu()) {
@@ -512,14 +499,20 @@ void MenuBar::shape(Menu &p_menu) {
}
void MenuBar::_refresh_menu_names() {
+ DisplayServer *ds = DisplayServer::get_singleton();
+ bool is_global = !global_menu_name.is_empty();
+ int global_start = _find_global_start_index();
+
Vector<PopupMenu *> popups = _get_popups();
for (int i = 0; i < popups.size(); i++) {
if (!popups[i]->has_meta("_menu_name") && String(popups[i]->get_name()) != get_menu_title(i)) {
menu_cache.write[i].name = popups[i]->get_name();
shape(menu_cache.write[i]);
+ if (is_global) {
+ ds->global_menu_set_item_text("_main", global_start + i, atr(menu_cache[i].name));
+ }
}
}
- _update_menu();
}
Vector<PopupMenu *> MenuBar::_get_popups() const {
@@ -560,11 +553,15 @@ void MenuBar::add_child_notify(Node *p_child) {
menu_cache.push_back(menu);
p_child->connect("renamed", callable_mp(this, &MenuBar::_refresh_menu_names));
- p_child->connect("menu_changed", callable_mp(this, &MenuBar::_update_menu));
p_child->connect("about_to_popup", callable_mp(this, &MenuBar::_popup_visibility_changed).bind(true));
p_child->connect("popup_hide", callable_mp(this, &MenuBar::_popup_visibility_changed).bind(false));
- _update_menu();
+ if (!global_menu_name.is_empty()) {
+ String submenu_name = pm->bind_global_menu();
+ int index = DisplayServer::get_singleton()->global_menu_add_submenu_item("_main", atr(menu.name), submenu_name, _find_global_start_index() + menu_cache.size() - 1);
+ DisplayServer::get_singleton()->global_menu_set_item_tag("_main", index, global_menu_name + "#" + itos(menu_cache.size() - 1));
+ }
+ update_minimum_size();
}
void MenuBar::move_child_notify(Node *p_child) {
@@ -586,9 +583,20 @@ void MenuBar::move_child_notify(Node *p_child) {
}
Menu menu = menu_cache[old_idx];
menu_cache.remove_at(old_idx);
- menu_cache.insert(get_menu_idx_from_control(pm), menu);
+ int new_idx = get_menu_idx_from_control(pm);
+ menu_cache.insert(new_idx, menu);
- _update_menu();
+ if (!global_menu_name.is_empty()) {
+ int global_start = _find_global_start_index();
+ if (old_idx != -1) {
+ DisplayServer::get_singleton()->global_menu_remove_item("_main", global_start + old_idx);
+ }
+ if (new_idx != -1) {
+ String submenu_name = pm->bind_global_menu();
+ int index = DisplayServer::get_singleton()->global_menu_add_submenu_item("_main", atr(menu.name), submenu_name, global_start + new_idx);
+ DisplayServer::get_singleton()->global_menu_set_item_tag("_main", index, global_menu_name + "#" + itos(new_idx));
+ }
+ }
}
void MenuBar::remove_child_notify(Node *p_child) {
@@ -603,15 +611,19 @@ void MenuBar::remove_child_notify(Node *p_child) {
menu_cache.remove_at(idx);
+ if (!global_menu_name.is_empty()) {
+ pm->unbind_global_menu();
+ DisplayServer::get_singleton()->global_menu_remove_item("_main", _find_global_start_index() + idx);
+ }
+
p_child->remove_meta("_menu_name");
p_child->remove_meta("_menu_tooltip");
p_child->disconnect("renamed", callable_mp(this, &MenuBar::_refresh_menu_names));
- p_child->disconnect("menu_changed", callable_mp(this, &MenuBar::_update_menu));
p_child->disconnect("about_to_popup", callable_mp(this, &MenuBar::_popup_visibility_changed));
p_child->disconnect("popup_hide", callable_mp(this, &MenuBar::_popup_visibility_changed));
- _update_menu();
+ update_minimum_size();
}
void MenuBar::_bind_methods() {
@@ -699,7 +711,8 @@ void MenuBar::set_text_direction(Control::TextDirection p_text_direction) {
ERR_FAIL_COND((int)p_text_direction < -1 || (int)p_text_direction > 3);
if (text_direction != p_text_direction) {
text_direction = p_text_direction;
- _update_menu();
+ update_minimum_size();
+ queue_redraw();
}
}
@@ -710,7 +723,8 @@ Control::TextDirection MenuBar::get_text_direction() const {
void MenuBar::set_language(const String &p_language) {
if (language != p_language) {
language = p_language;
- _update_menu();
+ update_minimum_size();
+ queue_redraw();
}
}
@@ -732,7 +746,10 @@ bool MenuBar::is_flat() const {
void MenuBar::set_start_index(int p_index) {
if (start_index != p_index) {
start_index = p_index;
- _update_menu();
+ if (!global_menu_name.is_empty()) {
+ unbind_global_menu();
+ bind_global_menu();
+ }
}
}
@@ -742,11 +759,12 @@ int MenuBar::get_start_index() const {
void MenuBar::set_prefer_global_menu(bool p_enabled) {
if (is_native != p_enabled) {
+ is_native = p_enabled;
if (is_native) {
- _clear_menu();
+ bind_global_menu();
+ } else {
+ unbind_global_menu();
}
- is_native = p_enabled;
- _update_menu();
}
}
@@ -790,7 +808,10 @@ void MenuBar::set_menu_title(int p_menu, const String &p_title) {
}
menu_cache.write[p_menu].name = p_title;
shape(menu_cache.write[p_menu]);
- _update_menu();
+ if (!global_menu_name.is_empty()) {
+ DisplayServer::get_singleton()->global_menu_set_item_text("_main", _find_global_start_index() + p_menu, atr(menu_cache[p_menu].name));
+ }
+ update_minimum_size();
}
String MenuBar::get_menu_title(int p_menu) const {
@@ -802,7 +823,10 @@ void MenuBar::set_menu_tooltip(int p_menu, const String &p_tooltip) {
ERR_FAIL_INDEX(p_menu, menu_cache.size());
PopupMenu *pm = get_menu_popup(p_menu);
pm->set_meta("_menu_tooltip", p_tooltip);
- menu_cache.write[p_menu].name = p_tooltip;
+ menu_cache.write[p_menu].tooltip = p_tooltip;
+ if (!global_menu_name.is_empty()) {
+ DisplayServer::get_singleton()->global_menu_set_item_tooltip("_main", _find_global_start_index() + p_menu, p_tooltip);
+ }
}
String MenuBar::get_menu_tooltip(int p_menu) const {
@@ -813,7 +837,9 @@ String MenuBar::get_menu_tooltip(int p_menu) const {
void MenuBar::set_menu_disabled(int p_menu, bool p_disabled) {
ERR_FAIL_INDEX(p_menu, menu_cache.size());
menu_cache.write[p_menu].disabled = p_disabled;
- _update_menu();
+ if (!global_menu_name.is_empty()) {
+ DisplayServer::get_singleton()->global_menu_set_item_disabled("_main", _find_global_start_index() + p_menu, p_disabled);
+ }
}
bool MenuBar::is_menu_disabled(int p_menu) const {
@@ -824,7 +850,10 @@ bool MenuBar::is_menu_disabled(int p_menu) const {
void MenuBar::set_menu_hidden(int p_menu, bool p_hidden) {
ERR_FAIL_INDEX(p_menu, menu_cache.size());
menu_cache.write[p_menu].hidden = p_hidden;
- _update_menu();
+ if (!global_menu_name.is_empty()) {
+ DisplayServer::get_singleton()->global_menu_set_item_hidden("_main", _find_global_start_index() + p_menu, p_hidden);
+ }
+ update_minimum_size();
}
bool MenuBar::is_menu_hidden(int p_menu) const {
diff --git a/scene/gui/menu_bar.h b/scene/gui/menu_bar.h
index 4d6e76d9b6..ba4df5f229 100644
--- a/scene/gui/menu_bar.h
+++ b/scene/gui/menu_bar.h
@@ -66,7 +66,6 @@ class MenuBar : public Control {
}
};
Vector<Menu> menu_cache;
- HashSet<String> global_menus;
int focused_menu = -1;
int selected_menu = -1;
@@ -114,9 +113,23 @@ class MenuBar : public Control {
void _open_popup(int p_index, bool p_focus_item = false);
void _popup_visibility_changed(bool p_visible);
- void _update_submenu(const String &p_menu_name, PopupMenu *p_child);
- void _clear_menu();
- void _update_menu();
+
+ String global_menu_name;
+
+ int _find_global_start_index() {
+ if (global_menu_name.is_empty()) {
+ return -1;
+ }
+
+ DisplayServer *ds = DisplayServer::get_singleton();
+ int count = ds->global_menu_get_item_count("_main");
+ for (int i = 0; i < count; i++) {
+ if (ds->global_menu_get_item_tag("_main", i).operator String().begins_with(global_menu_name)) {
+ return i;
+ }
+ }
+ return -1;
+ }
protected:
virtual void shortcut_input(const Ref<InputEvent> &p_event) override;
@@ -130,6 +143,9 @@ protected:
public:
virtual void gui_input(const Ref<InputEvent> &p_event) override;
+ String bind_global_menu();
+ void unbind_global_menu();
+
void set_switch_on_hover(bool p_enabled);
bool is_switch_on_hover();
void set_disable_shortcuts(bool p_disabled);
diff --git a/scene/gui/popup_menu.cpp b/scene/gui/popup_menu.cpp
index 54fd8b8745..d6b8dd0202 100644
--- a/scene/gui/popup_menu.cpp
+++ b/scene/gui/popup_menu.cpp
@@ -40,6 +40,86 @@
#include "scene/gui/menu_bar.h"
#include "scene/theme/theme_db.h"
+String PopupMenu::bind_global_menu() {
+#ifdef TOOLS_ENABLED
+ if (is_part_of_edited_scene()) {
+ return String();
+ }
+#endif
+ if (!DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_GLOBAL_MENU)) {
+ return String();
+ }
+
+ if (!global_menu_name.is_empty()) {
+ return global_menu_name; // Already bound;
+ }
+
+ DisplayServer *ds = DisplayServer::get_singleton();
+ global_menu_name = "__PopupMenu#" + itos(get_instance_id());
+ ds->global_menu_set_popup_callbacks(global_menu_name, callable_mp(this, &PopupMenu::_about_to_popup), callable_mp(this, &PopupMenu::_about_to_close));
+ for (int i = 0; i < items.size(); i++) {
+ Item &item = items.write[i];
+ if (item.separator) {
+ ds->global_menu_add_separator(global_menu_name);
+ } else {
+ int index = ds->global_menu_add_item(global_menu_name, item.xl_text, callable_mp(this, &PopupMenu::activate_item), Callable(), i);
+ if (!item.submenu.is_empty()) {
+ PopupMenu *pm = Object::cast_to<PopupMenu>(get_node_or_null(item.submenu));
+ if (pm) {
+ String submenu_name = pm->bind_global_menu();
+ ds->global_menu_set_item_submenu(global_menu_name, index, submenu_name);
+ item.submenu_bound = true;
+ }
+ }
+ if (item.checkable_type == Item::CHECKABLE_TYPE_CHECK_BOX) {
+ ds->global_menu_set_item_checkable(global_menu_name, index, true);
+ } else if (item.checkable_type == Item::CHECKABLE_TYPE_RADIO_BUTTON) {
+ ds->global_menu_set_item_radio_checkable(global_menu_name, index, true);
+ }
+ ds->global_menu_set_item_checked(global_menu_name, index, item.checked);
+ ds->global_menu_set_item_disabled(global_menu_name, index, item.disabled);
+ ds->global_menu_set_item_max_states(global_menu_name, index, item.max_states);
+ ds->global_menu_set_item_icon(global_menu_name, index, item.icon);
+ ds->global_menu_set_item_state(global_menu_name, index, item.state);
+ ds->global_menu_set_item_indentation_level(global_menu_name, index, item.indent);
+ ds->global_menu_set_item_tooltip(global_menu_name, index, item.tooltip);
+ if (!item.shortcut_is_disabled && item.shortcut.is_valid() && item.shortcut->has_valid_event()) {
+ Array events = item.shortcut->get_events();
+ for (int j = 0; j < events.size(); j++) {
+ Ref<InputEventKey> ie = events[j];
+ if (ie.is_valid()) {
+ ds->global_menu_set_item_accelerator(global_menu_name, index, ie->get_keycode_with_modifiers());
+ break;
+ }
+ }
+ } else if (item.accel != Key::NONE) {
+ ds->global_menu_set_item_accelerator(global_menu_name, index, item.accel);
+ }
+ }
+ }
+ return global_menu_name;
+}
+
+void PopupMenu::unbind_global_menu() {
+ if (global_menu_name.is_empty()) {
+ return;
+ }
+
+ for (int i = 0; i < items.size(); i++) {
+ Item &item = items.write[i];
+ if (!item.submenu.is_empty()) {
+ PopupMenu *pm = Object::cast_to<PopupMenu>(get_node_or_null(item.submenu));
+ if (pm) {
+ pm->unbind_global_menu();
+ }
+ item.submenu_bound = false;
+ }
+ }
+ DisplayServer::get_singleton()->global_menu_clear(global_menu_name);
+
+ global_menu_name = String();
+}
+
String PopupMenu::_get_accel_text(const Item &p_item) const {
if (p_item.shortcut.is_valid()) {
return p_item.shortcut->get_as_text();
@@ -631,9 +711,9 @@ void PopupMenu::_draw_items() {
// Separator
item_ofs.x += items[i].indent * theme_cache.indent;
if (items[i].separator) {
- if (!text.is_empty() || !items[i].icon.is_null()) {
+ if (!text.is_empty() || items[i].icon.is_valid()) {
int content_size = items[i].text_buf->get_size().width + theme_cache.h_separation * 2;
- if (!items[i].icon.is_null()) {
+ if (items[i].icon.is_valid()) {
content_size += icon_size.width + theme_cache.h_separation;
}
@@ -662,7 +742,9 @@ void PopupMenu::_draw_items() {
icon_color *= items[i].icon_modulate;
// For non-separator items, add some padding for the content.
- item_ofs.x += theme_cache.item_start_padding;
+ if (!items[i].separator) {
+ item_ofs.x += theme_cache.item_start_padding;
+ }
// Checkboxes
if (items[i].checkable_type && !items[i].separator) {
@@ -678,7 +760,7 @@ 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 (items[i].icon.is_valid()) {
const Point2 icon_offset = Point2(0, Math::floor((h - icon_size.height) / 2.0));
Point2 icon_pos;
@@ -689,6 +771,7 @@ void PopupMenu::_draw_items() {
icon_pos = Size2(control->get_size().width - item_ofs.x - separator_ofs - icon_size.width, item_ofs.y);
} else {
icon_pos = item_ofs + Size2(separator_ofs, 0);
+ separator_ofs += icon_size.width + theme_cache.h_separation;
}
} else {
if (rtl) {
@@ -714,9 +797,6 @@ void PopupMenu::_draw_items() {
if (items[i].separator) {
if (!text.is_empty()) {
Vector2 text_pos = Point2(separator_ofs, item_ofs.y + Math::floor((h - items[i].text_buf->get_size().y) / 2.0));
- if (!rtl && !items[i].icon.is_null()) {
- text_pos.x += icon_size.width + theme_cache.h_separation;
- }
if (theme_cache.font_separator_outline_size > 0 && theme_cache.font_separator_outline_color.a > 0) {
items[i].text_buf->draw_outline(ci, text_pos, theme_cache.font_separator_outline_size, theme_cache.font_separator_outline_color);
@@ -821,11 +901,17 @@ void PopupMenu::_menu_changed() {
void PopupMenu::add_child_notify(Node *p_child) {
Window::add_child_notify(p_child);
- PopupMenu *pm = Object::cast_to<PopupMenu>(p_child);
- if (!pm) {
- return;
+ if (Object::cast_to<PopupMenu>(p_child) && !global_menu_name.is_empty()) {
+ String node_name = p_child->get_name();
+ PopupMenu *pm = Object::cast_to<PopupMenu>(get_node_or_null(node_name));
+ for (int i = 0; i < items.size(); i++) {
+ if (items[i].submenu == node_name) {
+ String submenu_name = pm->bind_global_menu();
+ DisplayServer::get_singleton()->global_menu_set_item_submenu(global_menu_name, i, submenu_name);
+ items.write[i].submenu_bound = true;
+ }
+ }
}
- p_child->connect("menu_changed", callable_mp(this, &PopupMenu::_menu_changed));
_menu_changed();
}
@@ -836,7 +922,16 @@ void PopupMenu::remove_child_notify(Node *p_child) {
if (!pm) {
return;
}
- p_child->disconnect("menu_changed", callable_mp(this, &PopupMenu::_menu_changed));
+ if (Object::cast_to<PopupMenu>(p_child) && !global_menu_name.is_empty()) {
+ String node_name = p_child->get_name();
+ for (int i = 0; i < items.size(); i++) {
+ if (items[i].submenu == node_name) {
+ DisplayServer::get_singleton()->global_menu_set_item_submenu(global_menu_name, i, String());
+ items.write[i].submenu_bound = false;
+ }
+ }
+ pm->unbind_global_menu();
+ }
_menu_changed();
}
@@ -857,9 +952,15 @@ void PopupMenu::_notification(int p_what) {
case NOTIFICATION_THEME_CHANGED:
case Control::NOTIFICATION_LAYOUT_DIRECTION_CHANGED:
case NOTIFICATION_TRANSLATION_CHANGED: {
+ DisplayServer *ds = DisplayServer::get_singleton();
+ bool is_global = !global_menu_name.is_empty();
for (int i = 0; i < items.size(); i++) {
- items.write[i].xl_text = atr(items[i].text);
- items.write[i].dirty = true;
+ Item &item = items.write[i];
+ item.xl_text = atr(item.text);
+ item.dirty = true;
+ if (is_global) {
+ ds->global_menu_set_item_text(global_menu_name, i, item.xl_text);
+ }
_shape_item(i);
}
@@ -1007,10 +1108,12 @@ void PopupMenu::_notification(int p_what) {
}
// Set margin on the margin container
+ margin_container->begin_bulk_theme_override();
margin_container->add_theme_constant_override("margin_left", theme_cache.panel_style->get_margin(Side::SIDE_LEFT));
margin_container->add_theme_constant_override("margin_top", theme_cache.panel_style->get_margin(Side::SIDE_TOP));
margin_container->add_theme_constant_override("margin_right", theme_cache.panel_style->get_margin(Side::SIDE_RIGHT));
margin_container->add_theme_constant_override("margin_bottom", theme_cache.panel_style->get_margin(Side::SIDE_BOTTOM));
+ margin_container->end_bulk_theme_override();
}
} break;
}
@@ -1031,6 +1134,14 @@ void PopupMenu::add_item(const String &p_label, int p_id, Key p_accel) {
ITEM_SETUP_WITH_ACCEL(p_label, p_id, p_accel);
items.push_back(item);
+ if (!global_menu_name.is_empty()) {
+ DisplayServer *ds = DisplayServer::get_singleton();
+ int index = ds->global_menu_add_item(global_menu_name, item.xl_text, callable_mp(this, &PopupMenu::activate_item), Callable(), items.size() - 1);
+ if (item.accel != Key::NONE) {
+ ds->global_menu_set_item_accelerator(global_menu_name, index, item.accel);
+ }
+ }
+
_shape_item(items.size() - 1);
control->queue_redraw();
@@ -1045,6 +1156,15 @@ void PopupMenu::add_icon_item(const Ref<Texture2D> &p_icon, const String &p_labe
item.icon = p_icon;
items.push_back(item);
+ if (!global_menu_name.is_empty()) {
+ DisplayServer *ds = DisplayServer::get_singleton();
+ int index = ds->global_menu_add_item(global_menu_name, item.xl_text, callable_mp(this, &PopupMenu::activate_item), Callable(), items.size() - 1);
+ if (item.accel != Key::NONE) {
+ ds->global_menu_set_item_accelerator(global_menu_name, index, item.accel);
+ }
+ ds->global_menu_set_item_icon(global_menu_name, index, item.icon);
+ }
+
_shape_item(items.size() - 1);
control->queue_redraw();
@@ -1059,10 +1179,20 @@ void PopupMenu::add_check_item(const String &p_label, int p_id, Key p_accel) {
item.checkable_type = Item::CHECKABLE_TYPE_CHECK_BOX;
items.push_back(item);
+ if (!global_menu_name.is_empty()) {
+ DisplayServer *ds = DisplayServer::get_singleton();
+ int index = ds->global_menu_add_item(global_menu_name, item.xl_text, callable_mp(this, &PopupMenu::activate_item), Callable(), items.size() - 1);
+ if (item.accel != Key::NONE) {
+ ds->global_menu_set_item_accelerator(global_menu_name, index, item.accel);
+ }
+ ds->global_menu_set_item_checkable(global_menu_name, index, true);
+ }
+
_shape_item(items.size() - 1);
control->queue_redraw();
child_controls_changed();
+ notify_property_list_changed();
_menu_changed();
}
@@ -1073,10 +1203,22 @@ void PopupMenu::add_icon_check_item(const Ref<Texture2D> &p_icon, const String &
item.checkable_type = Item::CHECKABLE_TYPE_CHECK_BOX;
items.push_back(item);
+ if (!global_menu_name.is_empty()) {
+ DisplayServer *ds = DisplayServer::get_singleton();
+ int index = ds->global_menu_add_item(global_menu_name, item.xl_text, callable_mp(this, &PopupMenu::activate_item), Callable(), items.size() - 1);
+ if (item.accel != Key::NONE) {
+ ds->global_menu_set_item_accelerator(global_menu_name, index, item.accel);
+ }
+ ds->global_menu_set_item_icon(global_menu_name, index, item.icon);
+ ds->global_menu_set_item_checkable(global_menu_name, index, true);
+ }
+
_shape_item(items.size() - 1);
control->queue_redraw();
child_controls_changed();
+ notify_property_list_changed();
+ _menu_changed();
}
void PopupMenu::add_radio_check_item(const String &p_label, int p_id, Key p_accel) {
@@ -1085,10 +1227,20 @@ void PopupMenu::add_radio_check_item(const String &p_label, int p_id, Key p_acce
item.checkable_type = Item::CHECKABLE_TYPE_RADIO_BUTTON;
items.push_back(item);
+ if (!global_menu_name.is_empty()) {
+ DisplayServer *ds = DisplayServer::get_singleton();
+ int index = ds->global_menu_add_item(global_menu_name, item.xl_text, callable_mp(this, &PopupMenu::activate_item), Callable(), items.size() - 1);
+ if (item.accel != Key::NONE) {
+ ds->global_menu_set_item_accelerator(global_menu_name, index, item.accel);
+ }
+ ds->global_menu_set_item_radio_checkable(global_menu_name, index, true);
+ }
+
_shape_item(items.size() - 1);
control->queue_redraw();
child_controls_changed();
+ notify_property_list_changed();
_menu_changed();
}
@@ -1099,10 +1251,21 @@ void PopupMenu::add_icon_radio_check_item(const Ref<Texture2D> &p_icon, const St
item.checkable_type = Item::CHECKABLE_TYPE_RADIO_BUTTON;
items.push_back(item);
+ if (!global_menu_name.is_empty()) {
+ DisplayServer *ds = DisplayServer::get_singleton();
+ int index = ds->global_menu_add_item(global_menu_name, item.xl_text, callable_mp(this, &PopupMenu::activate_item), Callable(), items.size() - 1);
+ if (item.accel != Key::NONE) {
+ ds->global_menu_set_item_accelerator(global_menu_name, index, item.accel);
+ }
+ ds->global_menu_set_item_icon(global_menu_name, index, item.icon);
+ ds->global_menu_set_item_radio_checkable(global_menu_name, index, true);
+ }
+
_shape_item(items.size() - 1);
control->queue_redraw();
child_controls_changed();
+ notify_property_list_changed();
_menu_changed();
}
@@ -1113,11 +1276,22 @@ void PopupMenu::add_multistate_item(const String &p_label, int p_max_states, int
item.state = p_default_state;
items.push_back(item);
+ if (!global_menu_name.is_empty()) {
+ DisplayServer *ds = DisplayServer::get_singleton();
+ int index = ds->global_menu_add_item(global_menu_name, item.xl_text, callable_mp(this, &PopupMenu::activate_item), Callable(), items.size() - 1);
+ if (item.accel != Key::NONE) {
+ ds->global_menu_set_item_accelerator(global_menu_name, index, item.accel);
+ }
+ ds->global_menu_set_item_max_states(global_menu_name, index, item.max_states);
+ ds->global_menu_set_item_state(global_menu_name, index, item.state);
+ }
+
_shape_item(items.size() - 1);
control->queue_redraw();
child_controls_changed();
_menu_changed();
+ notify_property_list_changed();
}
#define ITEM_SETUP_WITH_SHORTCUT(p_shortcut, p_id, p_global, p_allow_echo) \
@@ -1135,10 +1309,26 @@ void PopupMenu::add_shortcut(const Ref<Shortcut> &p_shortcut, int p_id, bool p_g
ITEM_SETUP_WITH_SHORTCUT(p_shortcut, p_id, p_global, p_allow_echo);
items.push_back(item);
+ if (!global_menu_name.is_empty()) {
+ DisplayServer *ds = DisplayServer::get_singleton();
+ int index = ds->global_menu_add_item(global_menu_name, item.xl_text, callable_mp(this, &PopupMenu::activate_item), Callable(), items.size() - 1);
+ if (!item.shortcut_is_disabled && item.shortcut.is_valid() && item.shortcut->has_valid_event()) {
+ Array events = item.shortcut->get_events();
+ for (int j = 0; j < events.size(); j++) {
+ Ref<InputEventKey> ie = events[j];
+ if (ie.is_valid()) {
+ ds->global_menu_set_item_accelerator(global_menu_name, index, ie->get_keycode_with_modifiers());
+ break;
+ }
+ }
+ }
+ }
+
_shape_item(items.size() - 1);
control->queue_redraw();
child_controls_changed();
+ notify_property_list_changed();
_menu_changed();
}
@@ -1148,10 +1338,27 @@ void PopupMenu::add_icon_shortcut(const Ref<Texture2D> &p_icon, const Ref<Shortc
item.icon = p_icon;
items.push_back(item);
+ if (!global_menu_name.is_empty()) {
+ DisplayServer *ds = DisplayServer::get_singleton();
+ int index = ds->global_menu_add_item(global_menu_name, item.xl_text, callable_mp(this, &PopupMenu::activate_item), Callable(), items.size() - 1);
+ if (!item.shortcut_is_disabled && item.shortcut.is_valid() && item.shortcut->has_valid_event()) {
+ Array events = item.shortcut->get_events();
+ for (int j = 0; j < events.size(); j++) {
+ Ref<InputEventKey> ie = events[j];
+ if (ie.is_valid()) {
+ ds->global_menu_set_item_accelerator(global_menu_name, index, ie->get_keycode_with_modifiers());
+ break;
+ }
+ }
+ }
+ ds->global_menu_set_item_icon(global_menu_name, index, item.icon);
+ }
+
_shape_item(items.size() - 1);
control->queue_redraw();
child_controls_changed();
+ notify_property_list_changed();
_menu_changed();
}
@@ -1161,10 +1368,27 @@ void PopupMenu::add_check_shortcut(const Ref<Shortcut> &p_shortcut, int p_id, bo
item.checkable_type = Item::CHECKABLE_TYPE_CHECK_BOX;
items.push_back(item);
+ if (!global_menu_name.is_empty()) {
+ DisplayServer *ds = DisplayServer::get_singleton();
+ int index = ds->global_menu_add_item(global_menu_name, item.xl_text, callable_mp(this, &PopupMenu::activate_item), Callable(), items.size() - 1);
+ if (!item.shortcut_is_disabled && item.shortcut.is_valid() && item.shortcut->has_valid_event()) {
+ Array events = item.shortcut->get_events();
+ for (int j = 0; j < events.size(); j++) {
+ Ref<InputEventKey> ie = events[j];
+ if (ie.is_valid()) {
+ ds->global_menu_set_item_accelerator(global_menu_name, index, ie->get_keycode_with_modifiers());
+ break;
+ }
+ }
+ }
+ ds->global_menu_set_item_checkable(global_menu_name, index, true);
+ }
+
_shape_item(items.size() - 1);
control->queue_redraw();
child_controls_changed();
+ notify_property_list_changed();
_menu_changed();
}
@@ -1175,10 +1399,28 @@ void PopupMenu::add_icon_check_shortcut(const Ref<Texture2D> &p_icon, const Ref<
item.checkable_type = Item::CHECKABLE_TYPE_CHECK_BOX;
items.push_back(item);
+ if (!global_menu_name.is_empty()) {
+ DisplayServer *ds = DisplayServer::get_singleton();
+ int index = ds->global_menu_add_item(global_menu_name, item.xl_text, callable_mp(this, &PopupMenu::activate_item), Callable(), items.size() - 1);
+ if (!item.shortcut_is_disabled && item.shortcut.is_valid() && item.shortcut->has_valid_event()) {
+ Array events = item.shortcut->get_events();
+ for (int j = 0; j < events.size(); j++) {
+ Ref<InputEventKey> ie = events[j];
+ if (ie.is_valid()) {
+ ds->global_menu_set_item_accelerator(global_menu_name, index, ie->get_keycode_with_modifiers());
+ break;
+ }
+ }
+ }
+ ds->global_menu_set_item_icon(global_menu_name, index, item.icon);
+ ds->global_menu_set_item_checkable(global_menu_name, index, true);
+ }
+
_shape_item(items.size() - 1);
control->queue_redraw();
child_controls_changed();
+ notify_property_list_changed();
_menu_changed();
}
@@ -1188,10 +1430,27 @@ void PopupMenu::add_radio_check_shortcut(const Ref<Shortcut> &p_shortcut, int p_
item.checkable_type = Item::CHECKABLE_TYPE_RADIO_BUTTON;
items.push_back(item);
+ if (!global_menu_name.is_empty()) {
+ DisplayServer *ds = DisplayServer::get_singleton();
+ int index = ds->global_menu_add_item(global_menu_name, item.xl_text, callable_mp(this, &PopupMenu::activate_item), Callable(), items.size() - 1);
+ if (!item.shortcut_is_disabled && item.shortcut.is_valid() && item.shortcut->has_valid_event()) {
+ Array events = item.shortcut->get_events();
+ for (int j = 0; j < events.size(); j++) {
+ Ref<InputEventKey> ie = events[j];
+ if (ie.is_valid()) {
+ ds->global_menu_set_item_accelerator(global_menu_name, index, ie->get_keycode_with_modifiers());
+ break;
+ }
+ }
+ }
+ ds->global_menu_set_item_radio_checkable(global_menu_name, index, true);
+ }
+
_shape_item(items.size() - 1);
control->queue_redraw();
child_controls_changed();
+ notify_property_list_changed();
_menu_changed();
}
@@ -1202,14 +1461,37 @@ void PopupMenu::add_icon_radio_check_shortcut(const Ref<Texture2D> &p_icon, cons
item.checkable_type = Item::CHECKABLE_TYPE_RADIO_BUTTON;
items.push_back(item);
+ if (!global_menu_name.is_empty()) {
+ DisplayServer *ds = DisplayServer::get_singleton();
+ int index = ds->global_menu_add_item(global_menu_name, item.xl_text, callable_mp(this, &PopupMenu::activate_item), Callable(), items.size() - 1);
+ if (!item.shortcut_is_disabled && item.shortcut.is_valid() && item.shortcut->has_valid_event()) {
+ Array events = item.shortcut->get_events();
+ for (int j = 0; j < events.size(); j++) {
+ Ref<InputEventKey> ie = events[j];
+ if (ie.is_valid()) {
+ ds->global_menu_set_item_accelerator(global_menu_name, index, ie->get_keycode_with_modifiers());
+ break;
+ }
+ }
+ }
+ ds->global_menu_set_item_icon(global_menu_name, index, item.icon);
+ ds->global_menu_set_item_radio_checkable(global_menu_name, index, true);
+ }
+
_shape_item(items.size() - 1);
control->queue_redraw();
child_controls_changed();
+ notify_property_list_changed();
_menu_changed();
}
void PopupMenu::add_submenu_item(const String &p_label, const String &p_submenu, int p_id) {
+ String submenu_name_safe = p_submenu.replace("@", "_"); // Allow special characters for auto-generated names.
+ if (submenu_name_safe.validate_node_name() != submenu_name_safe) {
+ ERR_FAIL_MSG(vformat("Invalid node name '%s' for a submenu, the following characters are not allowed:\n%s", p_submenu, String::get_invalid_node_name_characters(true)));
+ }
+
Item item;
item.text = p_label;
item.xl_text = atr(p_label);
@@ -1217,10 +1499,22 @@ void PopupMenu::add_submenu_item(const String &p_label, const String &p_submenu,
item.submenu = p_submenu;
items.push_back(item);
+ if (!global_menu_name.is_empty()) {
+ DisplayServer *ds = DisplayServer::get_singleton();
+ int index = ds->global_menu_add_item(global_menu_name, item.xl_text, callable_mp(this, &PopupMenu::activate_item), Callable(), items.size() - 1);
+ PopupMenu *pm = Object::cast_to<PopupMenu>(get_node_or_null(item.submenu)); // Find first menu with this name.
+ if (pm) {
+ String submenu_name = pm->bind_global_menu();
+ ds->global_menu_set_item_submenu(global_menu_name, index, submenu_name);
+ items.write[index].submenu_bound = true;
+ }
+ }
+
_shape_item(items.size() - 1);
control->queue_redraw();
child_controls_changed();
+ notify_property_list_changed();
_menu_changed();
}
@@ -1240,6 +1534,10 @@ void PopupMenu::set_item_text(int p_idx, const String &p_text) {
items.write[p_idx].text = p_text;
items.write[p_idx].xl_text = atr(p_text);
items.write[p_idx].dirty = true;
+
+ if (!global_menu_name.is_empty()) {
+ DisplayServer::get_singleton()->global_menu_set_item_text(global_menu_name, p_idx, items[p_idx].xl_text);
+ }
_shape_item(p_idx);
control->queue_redraw();
@@ -1284,6 +1582,10 @@ void PopupMenu::set_item_icon(int p_idx, const Ref<Texture2D> &p_icon) {
items.write[p_idx].icon = p_icon;
+ if (!global_menu_name.is_empty()) {
+ DisplayServer::get_singleton()->global_menu_set_item_icon(global_menu_name, p_idx, items[p_idx].icon);
+ }
+
control->queue_redraw();
child_controls_changed();
_menu_changed();
@@ -1332,6 +1634,10 @@ void PopupMenu::set_item_checked(int p_idx, bool p_checked) {
items.write[p_idx].checked = p_checked;
+ if (!global_menu_name.is_empty()) {
+ DisplayServer::get_singleton()->global_menu_set_item_checked(global_menu_name, p_idx, p_checked);
+ }
+
control->queue_redraw();
child_controls_changed();
_menu_changed();
@@ -1349,6 +1655,10 @@ void PopupMenu::set_item_id(int p_idx, int p_id) {
items.write[p_idx].id = p_id;
+ if (!global_menu_name.is_empty()) {
+ DisplayServer::get_singleton()->global_menu_set_item_tag(global_menu_name, p_idx, p_id);
+ }
+
control->queue_redraw();
child_controls_changed();
_menu_changed();
@@ -1367,6 +1677,10 @@ void PopupMenu::set_item_accelerator(int p_idx, Key p_accel) {
items.write[p_idx].accel = p_accel;
items.write[p_idx].dirty = true;
+ if (!global_menu_name.is_empty()) {
+ DisplayServer::get_singleton()->global_menu_set_item_accelerator(global_menu_name, p_idx, p_accel);
+ }
+
control->queue_redraw();
child_controls_changed();
_menu_changed();
@@ -1383,7 +1697,6 @@ void PopupMenu::set_item_metadata(int p_idx, const Variant &p_meta) {
}
items.write[p_idx].metadata = p_meta;
- control->queue_redraw();
child_controls_changed();
_menu_changed();
}
@@ -1399,6 +1712,11 @@ void PopupMenu::set_item_disabled(int p_idx, bool p_disabled) {
}
items.write[p_idx].disabled = p_disabled;
+
+ if (!global_menu_name.is_empty()) {
+ DisplayServer::get_singleton()->global_menu_set_item_disabled(global_menu_name, p_idx, p_disabled);
+ }
+
control->queue_redraw();
child_controls_changed();
_menu_changed();
@@ -1414,7 +1732,30 @@ void PopupMenu::set_item_submenu(int p_idx, const String &p_submenu) {
return;
}
+ if (!global_menu_name.is_empty()) {
+ if (items[p_idx].submenu_bound) {
+ PopupMenu *pm = Object::cast_to<PopupMenu>(get_node_or_null(items[p_idx].submenu));
+ if (pm) {
+ DisplayServer::get_singleton()->global_menu_set_item_submenu(global_menu_name, p_idx, String());
+ pm->unbind_global_menu();
+ }
+ items.write[p_idx].submenu_bound = false;
+ }
+ }
+
items.write[p_idx].submenu = p_submenu;
+
+ if (!global_menu_name.is_empty()) {
+ if (!items[p_idx].submenu.is_empty()) {
+ PopupMenu *pm = Object::cast_to<PopupMenu>(get_node_or_null(items[p_idx].submenu));
+ if (pm) {
+ String submenu_name = pm->bind_global_menu();
+ DisplayServer::get_singleton()->global_menu_set_item_submenu(global_menu_name, p_idx, submenu_name);
+ items.write[p_idx].submenu_bound = true;
+ }
+ }
+ }
+
control->queue_redraw();
child_controls_changed();
_menu_changed();
@@ -1423,6 +1764,11 @@ void PopupMenu::set_item_submenu(int p_idx, const String &p_submenu) {
void PopupMenu::toggle_item_checked(int p_idx) {
ERR_FAIL_INDEX(p_idx, items.size());
items.write[p_idx].checked = !items[p_idx].checked;
+
+ if (!global_menu_name.is_empty()) {
+ DisplayServer::get_singleton()->global_menu_set_item_checked(global_menu_name, p_idx, items[p_idx].checked);
+ }
+
control->queue_redraw();
child_controls_changed();
_menu_changed();
@@ -1569,6 +1915,11 @@ void PopupMenu::set_item_as_checkable(int p_idx, bool p_checkable) {
}
items.write[p_idx].checkable_type = p_checkable ? Item::CHECKABLE_TYPE_CHECK_BOX : Item::CHECKABLE_TYPE_NONE;
+
+ if (!global_menu_name.is_empty()) {
+ DisplayServer::get_singleton()->global_menu_set_item_checkable(global_menu_name, p_idx, p_checkable);
+ }
+
control->queue_redraw();
_menu_changed();
}
@@ -1585,6 +1936,11 @@ void PopupMenu::set_item_as_radio_checkable(int p_idx, bool p_radio_checkable) {
}
items.write[p_idx].checkable_type = p_radio_checkable ? Item::CHECKABLE_TYPE_RADIO_BUTTON : Item::CHECKABLE_TYPE_NONE;
+
+ if (!global_menu_name.is_empty()) {
+ DisplayServer::get_singleton()->global_menu_set_item_radio_checkable(global_menu_name, p_idx, p_radio_checkable);
+ }
+
control->queue_redraw();
_menu_changed();
}
@@ -1600,6 +1956,11 @@ void PopupMenu::set_item_tooltip(int p_idx, const String &p_tooltip) {
}
items.write[p_idx].tooltip = p_tooltip;
+
+ if (!global_menu_name.is_empty()) {
+ DisplayServer::get_singleton()->global_menu_set_item_tooltip(global_menu_name, p_idx, p_tooltip);
+ }
+
control->queue_redraw();
_menu_changed();
}
@@ -1625,6 +1986,21 @@ void PopupMenu::set_item_shortcut(int p_idx, const Ref<Shortcut> &p_shortcut, bo
_ref_shortcut(items[p_idx].shortcut);
}
+ if (!global_menu_name.is_empty()) {
+ DisplayServer *ds = DisplayServer::get_singleton();
+ ds->global_menu_set_item_accelerator(global_menu_name, p_idx, Key::NONE);
+ if (!items[p_idx].shortcut_is_disabled && items[p_idx].shortcut.is_valid() && items[p_idx].shortcut->has_valid_event()) {
+ Array events = items[p_idx].shortcut->get_events();
+ for (int j = 0; j < events.size(); j++) {
+ Ref<InputEventKey> ie = events[j];
+ if (ie.is_valid()) {
+ ds->global_menu_set_item_accelerator(global_menu_name, p_idx, ie->get_keycode_with_modifiers());
+ break;
+ }
+ }
+ }
+ }
+
control->queue_redraw();
_menu_changed();
}
@@ -1640,6 +2016,10 @@ void PopupMenu::set_item_indent(int p_idx, int p_indent) {
}
items.write[p_idx].indent = p_indent;
+ if (!global_menu_name.is_empty()) {
+ DisplayServer::get_singleton()->global_menu_set_item_indentation_level(global_menu_name, p_idx, p_indent);
+ }
+
control->queue_redraw();
child_controls_changed();
_menu_changed();
@@ -1656,6 +2036,11 @@ void PopupMenu::set_item_multistate(int p_idx, int p_state) {
}
items.write[p_idx].state = p_state;
+
+ if (!global_menu_name.is_empty()) {
+ DisplayServer::get_singleton()->global_menu_set_item_state(global_menu_name, p_idx, p_state);
+ }
+
control->queue_redraw();
_menu_changed();
}
@@ -1671,6 +2056,22 @@ void PopupMenu::set_item_shortcut_disabled(int p_idx, bool p_disabled) {
}
items.write[p_idx].shortcut_is_disabled = p_disabled;
+
+ if (!global_menu_name.is_empty()) {
+ DisplayServer *ds = DisplayServer::get_singleton();
+ ds->global_menu_set_item_accelerator(global_menu_name, p_idx, Key::NONE);
+ if (!items[p_idx].shortcut_is_disabled && items[p_idx].shortcut.is_valid() && items[p_idx].shortcut->has_valid_event()) {
+ Array events = items[p_idx].shortcut->get_events();
+ for (int j = 0; j < events.size(); j++) {
+ Ref<InputEventKey> ie = events[j];
+ if (ie.is_valid()) {
+ ds->global_menu_set_item_accelerator(global_menu_name, p_idx, ie->get_keycode_with_modifiers());
+ break;
+ }
+ }
+ }
+ }
+
control->queue_redraw();
_menu_changed();
}
@@ -1686,6 +2087,10 @@ void PopupMenu::toggle_item_multistate(int p_idx) {
items.write[p_idx].state = 0;
}
+ if (!global_menu_name.is_empty()) {
+ DisplayServer::get_singleton()->global_menu_set_item_state(global_menu_name, p_idx, items[p_idx].state);
+ }
+
control->queue_redraw();
_menu_changed();
}
@@ -1739,11 +2144,23 @@ void PopupMenu::set_item_count(int p_count) {
return;
}
+ DisplayServer *ds = DisplayServer::get_singleton();
+ bool is_global = !global_menu_name.is_empty();
+
+ if (is_global && prev_size > p_count) {
+ for (int i = prev_size - 1; i >= p_count; i--) {
+ ds->global_menu_remove_item(global_menu_name, i);
+ }
+ }
+
items.resize(p_count);
if (prev_size < p_count) {
for (int i = prev_size; i < p_count; i++) {
items.write[i].id = i;
+ if (is_global) {
+ ds->global_menu_add_item(global_menu_name, String(), callable_mp(this, &PopupMenu::activate_item), Callable(), i);
+ }
}
}
@@ -1760,18 +2177,21 @@ int PopupMenu::get_item_count() const {
void PopupMenu::scroll_to_item(int p_idx) {
ERR_FAIL_INDEX(p_idx, items.size());
- // Scroll item into view (upwards).
- if (items[p_idx]._ofs_cache - scroll_container->get_v_scroll() < -control->get_position().y) {
- scroll_container->set_v_scroll(items[p_idx]._ofs_cache + control->get_position().y);
- }
+ // Calculate the position of the item relative to the visible area.
+ int item_y = items[p_idx]._ofs_cache;
+ int visible_height = scroll_container->get_size().height;
+ int relative_y = item_y - scroll_container->get_v_scroll();
- // Scroll item into view (downwards).
- if (items[p_idx]._ofs_cache + items[p_idx]._height_cache - scroll_container->get_v_scroll() > -control->get_position().y + scroll_container->get_size().height) {
- scroll_container->set_v_scroll(items[p_idx]._ofs_cache + items[p_idx]._height_cache + control->get_position().y);
+ // If item is not fully visible, adjust scroll.
+ if (relative_y < 0) {
+ scroll_container->set_v_scroll(item_y);
+ } else if (relative_y + items[p_idx]._height_cache > visible_height) {
+ scroll_container->set_v_scroll(item_y + items[p_idx]._height_cache - visible_height);
}
}
bool PopupMenu::activate_item_by_event(const Ref<InputEvent> &p_event, bool p_for_global_only) {
+ ERR_FAIL_COND_V(p_event.is_null(), false);
Key code = Key::NONE;
Ref<InputEventKey> k = p_event;
@@ -1828,6 +2248,16 @@ bool PopupMenu::activate_item_by_event(const Ref<InputEvent> &p_event, bool p_fo
return false;
}
+void PopupMenu::_about_to_popup() {
+ ERR_MAIN_THREAD_GUARD;
+ emit_signal(SNAME("about_to_popup"));
+}
+
+void PopupMenu::_about_to_close() {
+ ERR_MAIN_THREAD_GUARD;
+ emit_signal(SNAME("popup_hide"));
+}
+
void PopupMenu::activate_item(int p_idx) {
ERR_FAIL_INDEX(p_idx, items.size());
ERR_FAIL_COND(items[p_idx].separator);
@@ -1890,6 +2320,11 @@ void PopupMenu::remove_item(int p_idx) {
}
items.remove_at(p_idx);
+
+ if (!global_menu_name.is_empty()) {
+ DisplayServer::get_singleton()->global_menu_remove_item(global_menu_name, p_idx);
+ }
+
control->queue_redraw();
child_controls_changed();
_menu_changed();
@@ -1904,6 +2339,11 @@ void PopupMenu::add_separator(const String &p_text, int p_id) {
sep.xl_text = atr(p_text);
}
items.push_back(sep);
+
+ if (!global_menu_name.is_empty()) {
+ DisplayServer::get_singleton()->global_menu_add_separator(global_menu_name);
+ }
+
control->queue_redraw();
_menu_changed();
}
@@ -1922,7 +2362,22 @@ void PopupMenu::clear(bool p_free_submenus) {
}
}
}
+
+ if (!global_menu_name.is_empty()) {
+ for (int i = 0; i < items.size(); i++) {
+ Item &item = items.write[i];
+ if (!item.submenu.is_empty()) {
+ PopupMenu *pm = Object::cast_to<PopupMenu>(get_node_or_null(item.submenu));
+ if (pm) {
+ pm->unbind_global_menu();
+ }
+ item.submenu_bound = false;
+ }
+ }
+ DisplayServer::get_singleton()->global_menu_clear(global_menu_name);
+ }
items.clear();
+
mouse_over = -1;
control->queue_redraw();
child_controls_changed();
diff --git a/scene/gui/popup_menu.h b/scene/gui/popup_menu.h
index f123d08400..5d5f4a8322 100644
--- a/scene/gui/popup_menu.h
+++ b/scene/gui/popup_menu.h
@@ -75,6 +75,7 @@ class PopupMenu : public Popup {
bool shortcut_is_global = false;
bool shortcut_is_disabled = false;
bool allow_echo = false;
+ bool submenu_bound = false;
// Returns (0,0) if icon is null.
Size2 get_icon_size() const {
@@ -88,6 +89,8 @@ class PopupMenu : public Popup {
}
};
+ String global_menu_name;
+
bool close_allowed = false;
bool activated_by_keyboard = false;
@@ -213,6 +216,9 @@ public:
virtual void _parent_focused() override;
+ String bind_global_menu();
+ void unbind_global_menu();
+
void add_item(const String &p_label, int p_id = -1, Key p_accel = Key::NONE);
void add_icon_item(const Ref<Texture2D> &p_icon, const String &p_label, int p_id = -1, Key p_accel = Key::NONE);
void add_check_item(const String &p_label, int p_id = -1, Key p_accel = Key::NONE);
@@ -293,6 +299,9 @@ public:
bool activate_item_by_event(const Ref<InputEvent> &p_event, bool p_for_global_only = false);
void activate_item(int p_idx);
+ void _about_to_popup();
+ void _about_to_close();
+
void remove_item(int p_idx);
void add_separator(const String &p_text = String(), int p_id = -1);
diff --git a/scene/gui/range.h b/scene/gui/range.h
index 9b4f0707e6..b1c2446ded 100644
--- a/scene/gui/range.h
+++ b/scene/gui/range.h
@@ -64,6 +64,7 @@ class Range : public Control {
protected:
virtual void _value_changed(double p_value);
+ void _notify_shared_value_changed() { shared->emit_value_changed(); };
static void _bind_methods();
diff --git a/scene/gui/rich_text_label.cpp b/scene/gui/rich_text_label.cpp
index b4cd201e6a..30a468dfc5 100644
--- a/scene/gui/rich_text_label.cpp
+++ b/scene/gui/rich_text_label.cpp
@@ -307,7 +307,7 @@ float RichTextLabel::_resize_line(ItemFrame *p_frame, int p_line, const Ref<Font
Size2 img_size = img->size;
if (img->size_in_percent) {
img_size = _get_image_size(img->image, p_width * img->rq_size.width / 100.f, p_width * img->rq_size.height / 100.f, img->region);
- l.text_buf->resize_object((uint64_t)it, img_size, img->inline_align, 1);
+ l.text_buf->resize_object((uint64_t)it, img_size, img->inline_align);
}
} break;
case ITEM_TABLE: {
@@ -830,37 +830,6 @@ int RichTextLabel::_draw_line(ItemFrame *p_frame, int p_line, const Vector2 &p_o
prefix = segment + prefix;
}
}
- if (!prefix.is_empty()) {
- Ref<Font> font = theme_cache.normal_font;
- int font_size = theme_cache.normal_font_size;
-
- ItemFont *font_it = _find_font(l.from);
- if (font_it) {
- if (font_it->font.is_valid()) {
- font = font_it->font;
- }
- if (font_it->font_size > 0) {
- font_size = font_it->font_size;
- }
- }
- ItemFontSize *font_size_it = _find_font_size(l.from);
- if (font_size_it && font_size_it->font_size > 0) {
- font_size = font_size_it->font_size;
- }
- if (rtl) {
- float offx = 0.0f;
- if (!lrtl && p_frame == main) { // Skip Scrollbar.
- offx -= scroll_w;
- }
- font->draw_string(ci, p_ofs + Vector2(p_width - l.offset.x + offx, l.text_buf->get_line_ascent(0)), " " + prefix, HORIZONTAL_ALIGNMENT_LEFT, l.offset.x, font_size, _find_color(l.from, p_base_color));
- } else {
- float offx = 0.0f;
- if (lrtl && p_frame == main) { // Skip Scrollbar.
- offx += scroll_w;
- }
- font->draw_string(ci, p_ofs + Vector2(offx, l.text_buf->get_line_ascent(0)), prefix + " ", HORIZONTAL_ALIGNMENT_RIGHT, l.offset.x, font_size, _find_color(l.from, p_base_color));
- }
- }
// Draw dropcap.
int dc_lines = l.text_buf->get_dropcap_lines();
@@ -924,6 +893,30 @@ int RichTextLabel::_draw_line(ItemFrame *p_frame, int p_line, const Vector2 &p_o
} break;
}
+ if (!prefix.is_empty() && line == 0) {
+ Ref<Font> font = theme_cache.normal_font;
+ int font_size = theme_cache.normal_font_size;
+
+ ItemFont *font_it = _find_font(l.from);
+ if (font_it) {
+ if (font_it->font.is_valid()) {
+ font = font_it->font;
+ }
+ if (font_it->font_size > 0) {
+ font_size = font_it->font_size;
+ }
+ }
+ ItemFontSize *font_size_it = _find_font_size(l.from);
+ if (font_size_it && font_size_it->font_size > 0) {
+ font_size = font_size_it->font_size;
+ }
+ if (rtl) {
+ font->draw_string(ci, p_ofs + Vector2(off.x + length, l.text_buf->get_line_ascent(0)), " " + prefix, HORIZONTAL_ALIGNMENT_LEFT, l.offset.x, font_size, _find_color(l.from, p_base_color));
+ } else {
+ font->draw_string(ci, p_ofs + Vector2(off.x - l.offset.x, l.text_buf->get_line_ascent(0)), prefix + " ", HORIZONTAL_ALIGNMENT_RIGHT, l.offset.x, font_size, _find_color(l.from, p_base_color));
+ }
+ }
+
if (line <= dc_lines) {
if (rtl) {
off.x -= h_off;
@@ -1143,8 +1136,7 @@ int RichTextLabel::_draw_line(ItemFrame *p_frame, int p_line, const Vector2 &p_o
if (is_inside_tree() && get_viewport()->is_snap_2d_transforms_to_pixel_enabled()) {
fx_offset = fx_offset.round();
}
-
- Vector2i char_off = char_xform.get_origin();
+ Vector2 char_off = char_xform.get_origin();
// Draw glyph outlines.
const Color modulated_outline_color = font_outline_color * Color(1, 1, 1, font_color.a);
@@ -1220,7 +1212,14 @@ int RichTextLabel::_draw_line(ItemFrame *p_frame, int p_line, const Vector2 &p_o
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, nullptr) && underline_meta)) {
- if (!ul_started) {
+ if (ul_started && font_color != ul_color) {
+ float y_off = TS->shaped_text_get_underline_position(rid);
+ float underline_width = MAX(1.0, TS->shaped_text_get_underline_thickness(rid) * theme_cache.base_scale);
+ draw_line(ul_start + Vector2(0, y_off), p_ofs + Vector2(off.x, off.y + y_off), ul_color, underline_width);
+ ul_start = p_ofs + Vector2(off.x, off.y);
+ ul_color = font_color;
+ ul_color.a *= 0.5;
+ } else if (!ul_started) {
ul_started = true;
ul_start = p_ofs + Vector2(off.x, off.y);
ul_color = font_color;
@@ -1233,7 +1232,14 @@ int RichTextLabel::_draw_line(ItemFrame *p_frame, int p_line, const Vector2 &p_o
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) {
+ if (dot_ul_started && font_color != dot_ul_color) {
+ float y_off = TS->shaped_text_get_underline_position(rid);
+ float underline_width = MAX(1.0, TS->shaped_text_get_underline_thickness(rid) * theme_cache.base_scale);
+ draw_dashed_line(dot_ul_start + Vector2(0, y_off), p_ofs + Vector2(off.x, off.y + y_off), dot_ul_color, underline_width, MAX(2.0, underline_width * 2));
+ 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 = true;
dot_ul_start = p_ofs + Vector2(off.x, off.y);
dot_ul_color = font_color;
@@ -1246,7 +1252,14 @@ int RichTextLabel::_draw_line(ItemFrame *p_frame, int p_line, const Vector2 &p_o
draw_dashed_line(dot_ul_start + Vector2(0, y_off), p_ofs + Vector2(off.x, off.y + y_off), dot_ul_color, underline_width, MAX(2.0, underline_width * 2));
}
if (_find_strikethrough(it)) {
- if (!st_started) {
+ if (st_started && font_color != st_color) {
+ float y_off = -TS->shaped_text_get_ascent(rid) + TS->shaped_text_get_size(rid).y / 2;
+ float underline_width = MAX(1.0, TS->shaped_text_get_underline_thickness(rid) * theme_cache.base_scale);
+ draw_line(st_start + Vector2(0, y_off), p_ofs + Vector2(off.x, off.y + y_off), st_color, underline_width);
+ st_start = p_ofs + Vector2(off.x, off.y);
+ st_color = font_color;
+ st_color.a *= 0.5;
+ } else if (!st_started) {
st_started = true;
st_start = p_ofs + Vector2(off.x, off.y);
st_color = font_color;
@@ -1389,8 +1402,8 @@ int RichTextLabel::_draw_line(ItemFrame *p_frame, int p_line, const Vector2 &p_o
if (is_inside_tree() && get_viewport()->is_snap_2d_transforms_to_pixel_enabled()) {
fx_offset = fx_offset.round();
}
+ Vector2 char_off = char_xform.get_origin();
- Vector2i char_off = char_xform.get_origin();
Transform2D char_reverse_xform;
char_reverse_xform.set_origin(-char_off);
char_xform = char_xform * char_reverse_xform;
diff --git a/scene/gui/scroll_bar.cpp b/scene/gui/scroll_bar.cpp
index 2931b5be91..1310cac2c7 100644
--- a/scene/gui/scroll_bar.cpp
+++ b/scene/gui/scroll_bar.cpp
@@ -468,22 +468,6 @@ double ScrollBar::get_area_size() const {
}
}
-double ScrollBar::get_area_offset() const {
- double ofs = 0.0;
-
- if (orientation == VERTICAL) {
- ofs += theme_cache.scroll_offset_style->get_margin(SIDE_TOP);
- ofs += theme_cache.decrement_icon->get_height();
- }
-
- if (orientation == HORIZONTAL) {
- ofs += theme_cache.scroll_offset_style->get_margin(SIDE_LEFT);
- ofs += theme_cache.decrement_icon->get_width();
- }
-
- return ofs;
-}
-
double ScrollBar::get_grabber_offset() const {
return (get_area_size()) * get_as_ratio();
}
@@ -639,7 +623,6 @@ void ScrollBar::_bind_methods() {
BIND_THEME_ITEM_CUSTOM(Theme::DATA_TYPE_STYLEBOX, ScrollBar, scroll_style, "scroll");
BIND_THEME_ITEM_CUSTOM(Theme::DATA_TYPE_STYLEBOX, ScrollBar, scroll_focus_style, "scroll_focus");
- BIND_THEME_ITEM_CUSTOM(Theme::DATA_TYPE_STYLEBOX, ScrollBar, scroll_offset_style, "hscroll");
BIND_THEME_ITEM_CUSTOM(Theme::DATA_TYPE_STYLEBOX, ScrollBar, grabber_style, "grabber");
BIND_THEME_ITEM_CUSTOM(Theme::DATA_TYPE_STYLEBOX, ScrollBar, grabber_hl_style, "grabber_highlight");
BIND_THEME_ITEM_CUSTOM(Theme::DATA_TYPE_STYLEBOX, ScrollBar, grabber_pressed_style, "grabber_pressed");
diff --git a/scene/gui/scroll_bar.h b/scene/gui/scroll_bar.h
index aacf2060b8..deadbb53d6 100644
--- a/scene/gui/scroll_bar.h
+++ b/scene/gui/scroll_bar.h
@@ -63,7 +63,6 @@ class ScrollBar : public Range {
double get_grabber_size() const;
double get_grabber_min_size() const;
double get_area_size() const;
- double get_area_offset() const;
double get_grabber_offset() const;
static void set_can_focus_by_default(bool p_can_focus);
diff --git a/scene/gui/slider.cpp b/scene/gui/slider.cpp
index b1a2f8017e..8bb8eb1d30 100644
--- a/scene/gui/slider.cpp
+++ b/scene/gui/slider.cpp
@@ -68,15 +68,18 @@ void Slider::gui_input(const Ref<InputEvent> &p_event) {
double grab_width = (double)grabber->get_width();
double grab_height = (double)grabber->get_height();
double max = orientation == VERTICAL ? get_size().height - grab_height : get_size().width - grab_width;
+ set_block_signals(true);
if (orientation == VERTICAL) {
set_as_ratio(1 - (((double)grab.pos - (grab_height / 2.0)) / max));
} else {
set_as_ratio(((double)grab.pos - (grab_width / 2.0)) / max);
}
+ set_block_signals(false);
grab.active = true;
grab.uvalue = get_as_ratio();
emit_signal(SNAME("drag_started"));
+ _notify_shared_value_changed();
} else {
grab.active = false;
diff --git a/scene/gui/spin_box.cpp b/scene/gui/spin_box.cpp
index 26dbe1cb0c..bd549a6e4a 100644
--- a/scene/gui/spin_box.cpp
+++ b/scene/gui/spin_box.cpp
@@ -40,7 +40,7 @@ Size2 SpinBox::get_minimum_size() const {
return ms;
}
-void SpinBox::_update_text() {
+void SpinBox::_update_text(bool p_keep_line_edit) {
String value = String::num(get_value(), Math::range_step_decimals(get_step()));
if (is_localizing_numeral_system()) {
value = TS->format_number(value);
@@ -55,7 +55,12 @@ void SpinBox::_update_text() {
}
}
+ if (p_keep_line_edit && value == last_updated_text && value != line_edit->get_text()) {
+ return;
+ }
+
line_edit->set_text_with_selection(value);
+ last_updated_text = value;
}
void SpinBox::_text_submitted(const String &p_string) {
@@ -245,7 +250,7 @@ inline void SpinBox::_adjust_width_for_icon(const Ref<Texture2D> &icon) {
void SpinBox::_notification(int p_what) {
switch (p_what) {
case NOTIFICATION_DRAW: {
- _update_text();
+ _update_text(true);
_adjust_width_for_icon(theme_cache.updown_icon);
RID ci = get_canvas_item();
diff --git a/scene/gui/spin_box.h b/scene/gui/spin_box.h
index bbb1db637a..4d49626d71 100644
--- a/scene/gui/spin_box.h
+++ b/scene/gui/spin_box.h
@@ -46,12 +46,13 @@ class SpinBox : public Range {
void _range_click_timeout();
void _release_mouse();
- void _update_text();
+ void _update_text(bool p_keep_line_edit = false);
void _text_submitted(const String &p_string);
void _text_changed(const String &p_string);
String prefix;
String suffix;
+ String last_updated_text;
double custom_arrow_step = 0.0;
void _line_edit_input(const Ref<InputEvent> &p_event);
diff --git a/scene/gui/subviewport_container.cpp b/scene/gui/subviewport_container.cpp
index 851a94b32f..0d33774e20 100644
--- a/scene/gui/subviewport_container.cpp
+++ b/scene/gui/subviewport_container.cpp
@@ -190,6 +190,13 @@ void SubViewportContainer::_propagate_nonpositional_event(const Ref<InputEvent>
return;
}
+ bool send;
+ if (GDVIRTUAL_CALL(_propagate_input_event, p_event, send)) {
+ if (!send) {
+ return;
+ }
+ }
+
_send_event_to_viewports(p_event);
}
@@ -204,6 +211,13 @@ void SubViewportContainer::gui_input(const Ref<InputEvent> &p_event) {
return;
}
+ bool send;
+ if (GDVIRTUAL_CALL(_propagate_input_event, p_event, send)) {
+ if (!send) {
+ return;
+ }
+ }
+
if (stretch && shrink > 1) {
Transform2D xform;
xform.scale(Vector2(1, 1) / shrink);
@@ -275,6 +289,8 @@ void SubViewportContainer::_bind_methods() {
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "stretch"), "set_stretch", "is_stretch_enabled");
ADD_PROPERTY(PropertyInfo(Variant::INT, "stretch_shrink"), "set_stretch_shrink", "get_stretch_shrink");
+
+ GDVIRTUAL_BIND(_propagate_input_event, "event");
}
SubViewportContainer::SubViewportContainer() {
diff --git a/scene/gui/subviewport_container.h b/scene/gui/subviewport_container.h
index 3c6cd09d66..06420de730 100644
--- a/scene/gui/subviewport_container.h
+++ b/scene/gui/subviewport_container.h
@@ -50,6 +50,8 @@ protected:
virtual void add_child_notify(Node *p_child) override;
virtual void remove_child_notify(Node *p_child) override;
+ GDVIRTUAL1RC(bool, _propagate_input_event, Ref<InputEvent>);
+
public:
void set_stretch(bool p_enable);
bool is_stretch_enabled() const;
diff --git a/scene/gui/tab_bar.cpp b/scene/gui/tab_bar.cpp
index 9a915939c2..777ca96cc4 100644
--- a/scene/gui/tab_bar.cpp
+++ b/scene/gui/tab_bar.cpp
@@ -321,7 +321,6 @@ void TabBar::gui_input(const Ref<InputEvent> &p_event) {
}
void TabBar::_shape(int p_tab) {
- tabs.write[p_tab].xl_text = atr(tabs[p_tab].text);
tabs.write[p_tab].text_buf->clear();
tabs.write[p_tab].text_buf->set_width(-1);
if (tabs[p_tab].text_direction == Control::TEXT_DIRECTION_INHERITED) {
@@ -330,11 +329,17 @@ void TabBar::_shape(int p_tab) {
tabs.write[p_tab].text_buf->set_direction((TextServer::Direction)tabs[p_tab].text_direction);
}
- tabs.write[p_tab].text_buf->add_string(tabs[p_tab].xl_text, theme_cache.font, theme_cache.font_size, tabs[p_tab].language);
+ tabs.write[p_tab].text_buf->add_string(atr(tabs[p_tab].text), theme_cache.font, theme_cache.font_size, tabs[p_tab].language);
}
void TabBar::_notification(int p_what) {
switch (p_what) {
+ case NOTIFICATION_ENTER_TREE: {
+ if (scroll_to_selected) {
+ ensure_tab_visible(current);
+ }
+ } break;
+
case NOTIFICATION_INTERNAL_PROCESS: {
Input *input = Input::get_singleton();
@@ -567,6 +572,8 @@ void TabBar::_draw_tab(Ref<StyleBox> &p_tab_style, Color &p_font_color, int p_in
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;
+ } else {
+ tabs.write[p_index].rb_rect = Rect2();
}
// Draw and calculate rect of the close button.
@@ -668,7 +675,7 @@ bool TabBar::select_previous_available() {
if (target_tab < 0) {
target_tab += get_tab_count();
}
- if (!is_tab_disabled(target_tab)) {
+ if (!is_tab_disabled(target_tab) && !is_tab_hidden(target_tab)) {
set_current_tab(target_tab);
return true;
}
@@ -680,7 +687,7 @@ bool TabBar::select_next_available() {
const int offset_end = (get_tab_count() - get_current_tab());
for (int i = 1; i < offset_end; i++) {
int target_tab = (get_current_tab() + i) % get_tab_count();
- if (!is_tab_disabled(target_tab)) {
+ if (!is_tab_disabled(target_tab) && !is_tab_hidden(target_tab)) {
set_current_tab(target_tab);
return true;
}
@@ -851,11 +858,6 @@ bool TabBar::is_tab_hidden(int p_tab) const {
void TabBar::set_tab_metadata(int p_tab, const Variant &p_metadata) {
ERR_FAIL_INDEX(p_tab, tabs.size());
-
- if (tabs[p_tab].metadata == p_metadata) {
- return;
- }
-
tabs.write[p_tab].metadata = p_metadata;
}
@@ -1098,6 +1100,23 @@ void TabBar::remove_tab(int p_idx) {
max_drawn_tab = 0;
previous = 0;
} else {
+ // Try to change to a valid tab if possible (without firing the `tab_selected` signal).
+ for (int i = current; i < tabs.size(); i++) {
+ if (!is_tab_disabled(i) && !is_tab_hidden(i)) {
+ current = i;
+ break;
+ }
+ }
+ // If nothing, try backwards.
+ if (is_tab_disabled(current) || is_tab_hidden(current)) {
+ for (int i = current - 1; i >= 0; i--) {
+ if (!is_tab_disabled(i) && !is_tab_hidden(i)) {
+ current = i;
+ break;
+ }
+ }
+ }
+
offset = MIN(offset, tabs.size() - 1);
max_drawn_tab = MIN(max_drawn_tab, tabs.size() - 1);
@@ -1121,7 +1140,26 @@ Variant TabBar::get_drag_data(const Point2 &p_point) {
if (!drag_to_rearrange_enabled) {
return Control::get_drag_data(p_point); // Allow stuff like TabContainer to override it.
}
+ return _handle_get_drag_data("tab_bar_tab", p_point);
+}
+
+bool TabBar::can_drop_data(const Point2 &p_point, const Variant &p_data) const {
+ if (!drag_to_rearrange_enabled) {
+ return Control::can_drop_data(p_point, p_data); // Allow stuff like TabContainer to override it.
+ }
+ return _handle_can_drop_data("tab_bar_tab", p_point, p_data);
+}
+
+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;
+ }
+ _handle_drop_data("tab_bar_tab", p_point, p_data, callable_mp(this, &TabBar::move_tab), callable_mp(this, &TabBar::_move_tab_from));
+}
+
+Variant TabBar::_handle_get_drag_data(const String &p_type, const Point2 &p_point) {
int tab_over = get_tab_idx_at_point(p_point);
if (tab_over < 0) {
return Variant();
@@ -1141,30 +1179,26 @@ Variant TabBar::get_drag_data(const Point2 &p_point) {
drag_preview->add_child(tf);
}
- Label *label = memnew(Label(tabs[tab_over].xl_text));
+ Label *label = memnew(Label(get_tab_title(tab_over)));
drag_preview->add_child(label);
set_drag_preview(drag_preview);
Dictionary drag_data;
- drag_data["type"] = "tab_element";
- drag_data["tab_element"] = tab_over;
+ drag_data["type"] = p_type;
+ drag_data["tab_index"] = 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 Control::can_drop_data(p_point, p_data); // Allow stuff like TabContainer to override it.
- }
-
+bool TabBar::_handle_can_drop_data(const String &p_type, const Point2 &p_point, const Variant &p_data) const {
Dictionary d = p_data;
if (!d.has("type")) {
return false;
}
- if (String(d["type"]) == "tab_element") {
+ if (String(d["type"]) == p_type) {
NodePath from_path = d["from_path"];
NodePath to_path = get_path();
if (from_path == to_path) {
@@ -1182,19 +1216,14 @@ 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;
- }
-
+void TabBar::_handle_drop_data(const String &p_type, const Point2 &p_point, const Variant &p_data, const Callable &p_move_tab_callback, const Callable &p_move_tab_from_other_callback) {
Dictionary d = p_data;
if (!d.has("type")) {
return;
}
- if (String(d["type"]) == "tab_element") {
- int tab_from_id = d["tab_element"];
+ if (String(d["type"]) == p_type) {
+ int tab_from_id = d["tab_index"];
int hover_now = get_tab_idx_at_point(p_point);
NodePath from_path = d["from_path"];
NodePath to_path = get_path();
@@ -1219,7 +1248,7 @@ void TabBar::drop_data(const Point2 &p_point, const Variant &p_data) {
hover_now = is_layout_rtl() ^ (p_point.x < x) ? 0 : get_tab_count() - 1;
}
- move_tab(tab_from_id, hover_now);
+ p_move_tab_callback.call(tab_from_id, hover_now);
if (!is_tab_disabled(hover_now)) {
emit_signal(SNAME("active_tab_rearranged"), hover_now);
set_current_tab(hover_now);
@@ -1245,35 +1274,42 @@ void TabBar::drop_data(const Point2 &p_point, const Variant &p_data) {
hover_now = tabs.is_empty() || (is_layout_rtl() ^ (p_point.x < get_tab_rect(0).position.x)) ? 0 : get_tab_count();
}
- Tab moving_tab = from_tabs->tabs[tab_from_id];
- from_tabs->remove_tab(tab_from_id);
- tabs.insert(hover_now, moving_tab);
-
- if (tabs.size() > 1) {
- if (current >= hover_now) {
- current++;
- }
- if (previous >= hover_now) {
- previous++;
- }
- }
+ p_move_tab_from_other_callback.call(from_tabs, tab_from_id, hover_now);
+ }
+ }
+ }
+}
- if (!is_tab_disabled(hover_now)) {
- set_current_tab(hover_now);
- } else {
- _update_cache();
- queue_redraw();
- }
+void TabBar::_move_tab_from(TabBar *p_from_tabbar, int p_from_index, int p_to_index) {
+ Tab moving_tab = p_from_tabbar->tabs[p_from_index];
+ p_from_tabbar->remove_tab(p_from_index);
+ tabs.insert(p_to_index, moving_tab);
- update_minimum_size();
+ if (tabs.size() > 1) {
+ if (current >= p_to_index) {
+ current++;
+ }
+ if (previous >= p_to_index) {
+ previous++;
+ }
+ }
- if (tabs.size() == 1) {
- emit_signal(SNAME("tab_selected"), 0);
- emit_signal(SNAME("tab_changed"), 0);
- }
- }
+ if (!is_tab_disabled(p_to_index)) {
+ set_current_tab(p_to_index);
+ if (tabs.size() == 1) {
+ _update_cache();
+ queue_redraw();
+ emit_signal(SNAME("tab_changed"), 0);
+ }
+ } else {
+ _update_cache();
+ queue_redraw();
+ if (tabs.size() == 1) {
+ emit_signal(SNAME("tab_changed"), 0);
}
}
+
+ update_minimum_size();
}
int TabBar::get_tab_idx_at_point(const Point2 &p_point) const {
@@ -1732,7 +1768,10 @@ void TabBar::_bind_methods() {
ADD_SIGNAL(MethodInfo("tab_hovered", PropertyInfo(Variant::INT, "tab")));
ADD_SIGNAL(MethodInfo("active_tab_rearranged", PropertyInfo(Variant::INT, "idx_to")));
- ADD_PROPERTY(PropertyInfo(Variant::INT, "current_tab", PROPERTY_HINT_RANGE, "-1,4096,1", PROPERTY_USAGE_EDITOR), "set_current_tab", "get_current_tab");
+ // "current_tab" property must come after "tab_count", otherwise the property isn't loaded correctly.
+ ADD_ARRAY_COUNT("Tabs", "tab_count", "set_tab_count", "get_tab_count", "tab_");
+
+ ADD_PROPERTY(PropertyInfo(Variant::INT, "current_tab", PROPERTY_HINT_RANGE, "-1,4096,1"), "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");
@@ -1743,8 +1782,6 @@ void TabBar::_bind_methods() {
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);
BIND_ENUM_CONSTANT(ALIGNMENT_RIGHT);
diff --git a/scene/gui/tab_bar.h b/scene/gui/tab_bar.h
index 4bce30ea52..28e3411f3d 100644
--- a/scene/gui/tab_bar.h
+++ b/scene/gui/tab_bar.h
@@ -55,7 +55,6 @@ public:
private:
struct Tab {
String text;
- String xl_text;
String language;
Control::TextDirection text_direction = Control::TEXT_DIRECTION_INHERITED;
@@ -167,8 +166,13 @@ protected:
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;
+ void _move_tab_from(TabBar *p_from_tabbar, int p_from_index, int p_to_index);
public:
+ Variant _handle_get_drag_data(const String &p_type, const Point2 &p_point);
+ bool _handle_can_drop_data(const String &p_type, const Point2 &p_point, const Variant &p_data) const;
+ void _handle_drop_data(const String &p_type, const Point2 &p_point, const Variant &p_data, const Callable &p_move_tab_callback, const Callable &p_move_tab_from_other_callback);
+
void add_tab(const String &p_str = "", const Ref<Texture2D> &p_icon = Ref<Texture2D>());
void set_tab_title(int p_tab, const String &p_title);
diff --git a/scene/gui/tab_container.cpp b/scene/gui/tab_container.cpp
index b757b516d1..aa9400847f 100644
--- a/scene/gui/tab_container.cpp
+++ b/scene/gui/tab_container.cpp
@@ -143,6 +143,13 @@ void TabContainer::_notification(int p_what) {
}
} break;
+ case NOTIFICATION_POST_ENTER_TREE: {
+ if (setup_current_tab >= 0) {
+ set_current_tab(setup_current_tab);
+ setup_current_tab = -1;
+ }
+ } break;
+
case NOTIFICATION_READY:
case NOTIFICATION_RESIZED: {
_update_margins();
@@ -191,6 +198,8 @@ void TabContainer::_on_theme_changed() {
return;
}
+ tab_bar->begin_bulk_theme_override();
+
tab_bar->add_theme_style_override(SNAME("tab_unselected"), theme_cache.tab_unselected_style);
tab_bar->add_theme_style_override(SNAME("tab_hovered"), theme_cache.tab_hovered_style);
tab_bar->add_theme_style_override(SNAME("tab_selected"), theme_cache.tab_selected_style);
@@ -217,6 +226,8 @@ void TabContainer::_on_theme_changed() {
tab_bar->add_theme_constant_override(SNAME("icon_max_width"), theme_cache.icon_max_width);
tab_bar->add_theme_constant_override(SNAME("outline_size"), theme_cache.outline_size);
+ tab_bar->end_bulk_theme_override();
+
_update_margins();
if (get_tab_count() > 0) {
_repaint();
@@ -329,140 +340,65 @@ Variant TabContainer::_get_drag_data_fw(const Point2 &p_point, Control *p_from_c
if (!drag_to_rearrange_enabled) {
return Variant();
}
-
- int tab_over = get_tab_idx_at_point(p_point);
- if (tab_over < 0) {
- return Variant();
- }
-
- HBoxContainer *drag_preview = memnew(HBoxContainer);
-
- Ref<Texture2D> icon = get_tab_icon(tab_over);
- if (!icon.is_null()) {
- TextureRect *tf = memnew(TextureRect);
- tf->set_texture(icon);
- drag_preview->add_child(tf);
- }
-
- Label *label = memnew(Label(get_tab_title(tab_over)));
- 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;
+ return tab_bar->_handle_get_drag_data("tab_container_tab", p_point);
}
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;
}
-
- Dictionary d = p_data;
- if (!d.has("type")) {
- return false;
- }
-
- if (String(d["type"]) == "tabc_element") {
- NodePath from_path = d["from_path"];
- NodePath to_path = get_path();
- if (from_path == to_path) {
- return true;
- } else if (get_tabs_rearrange_group() != -1) {
- // 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()) {
- return true;
- }
- }
- }
-
- return false;
+ return tab_bar->_handle_can_drop_data("tab_container_tab", p_point, 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;
}
+ return tab_bar->_handle_drop_data("tab_container_tab", p_point, p_data, callable_mp(this, &TabContainer::_drag_move_tab), callable_mp(this, &TabContainer::_drag_move_tab_from));
+}
+
+void TabContainer::_drag_move_tab(int p_from_index, int p_to_index) {
+ move_child(get_tab_control(p_from_index), get_tab_control(p_to_index)->get_index(false));
+}
- Dictionary d = p_data;
- if (!d.has("type")) {
+void TabContainer::_drag_move_tab_from(TabBar *p_from_tabbar, int p_from_index, int p_to_index) {
+ Node *parent = p_from_tabbar->get_parent();
+ if (!parent) {
return;
}
+ TabContainer *from_tab_container = Object::cast_to<TabContainer>(parent);
+ if (!from_tab_container) {
+ return;
+ }
+ move_tab_from_tab_container(from_tab_container, p_from_index, p_to_index);
+}
- 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 (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 = 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)) {
- emit_signal(SNAME("active_tab_rearranged"), hover_now);
- set_current_tab(hover_now);
- }
-
- } else if (get_tabs_rearrange_group() != -1) {
- // 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);
- Ref<Texture2D> tab_icon = from_tabc->get_tab_icon(tab_from_id);
- bool tab_disabled = from_tabc->is_tab_disabled(tab_from_id);
- Variant tab_metadata = from_tabc->get_tab_metadata(tab_from_id);
+void TabContainer::move_tab_from_tab_container(TabContainer *p_from, int p_from_index, int p_to_index) {
+ ERR_FAIL_NULL(p_from);
+ ERR_FAIL_INDEX(p_from_index, p_from->get_tab_count());
+ ERR_FAIL_INDEX(p_to_index, get_tab_count() + 1);
- // 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();
- }
+ // Get the tab properties before they get erased by the child removal.
+ String tab_title = p_from->get_tab_title(p_from_index);
+ Ref<Texture2D> tab_icon = p_from->get_tab_icon(p_from_index);
+ bool tab_disabled = p_from->is_tab_disabled(p_from_index);
+ Variant tab_metadata = p_from->get_tab_metadata(p_from_index);
- Control *moving_tabc = from_tabc->get_tab_control(tab_from_id);
- from_tabc->remove_child(moving_tabc);
- add_child(moving_tabc, true);
+ Control *moving_tabc = p_from->get_tab_control(p_from_index);
+ p_from->remove_child(moving_tabc);
+ add_child(moving_tabc, true);
- set_tab_title(get_tab_count() - 1, tab_title);
- set_tab_icon(get_tab_count() - 1, tab_icon);
- set_tab_disabled(get_tab_count() - 1, tab_disabled);
- set_tab_metadata(get_tab_count() - 1, tab_metadata);
+ set_tab_title(get_tab_count() - 1, tab_title);
+ set_tab_icon(get_tab_count() - 1, tab_icon);
+ set_tab_disabled(get_tab_count() - 1, tab_disabled);
+ set_tab_metadata(get_tab_count() - 1, tab_metadata);
- move_child(moving_tabc, get_tab_control(hover_now)->get_index(false));
- if (!is_tab_disabled(hover_now)) {
- set_current_tab(hover_now);
- }
- }
- }
+ if (p_to_index < 0 || p_to_index > get_tab_count() - 1) {
+ p_to_index = get_tab_count() - 1;
+ }
+ move_child(moving_tabc, get_tab_control(p_to_index)->get_index(false));
+ if (!is_tab_disabled(p_to_index)) {
+ set_current_tab(p_to_index);
}
}
@@ -492,6 +428,10 @@ void TabContainer::_on_tab_button_pressed(int p_tab) {
emit_signal(SNAME("tab_button_pressed"), p_tab);
}
+void TabContainer::_on_active_tab_rearranged(int p_tab) {
+ emit_signal(SNAME("active_tab_rearranged"), p_tab);
+}
+
void TabContainer::_refresh_tab_names() {
Vector<Control *> controls = _get_tab_controls();
for (int i = 0; i < controls.size(); i++) {
@@ -595,6 +535,10 @@ int TabContainer::get_tab_count() const {
}
void TabContainer::set_current_tab(int p_current) {
+ if (!is_inside_tree()) {
+ setup_current_tab = p_current;
+ return;
+ }
tab_bar->set_current_tab(p_current);
}
@@ -979,7 +923,7 @@ void TabContainer::_bind_methods() {
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::INT, "current_tab", PROPERTY_HINT_RANGE, "-1,4096,1"), "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");
@@ -1034,6 +978,7 @@ TabContainer::TabContainer() {
tab_bar->connect("tab_hovered", callable_mp(this, &TabContainer::_on_tab_hovered));
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));
+ tab_bar->connect("active_tab_rearranged", callable_mp(this, &TabContainer::_on_active_tab_rearranged));
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 a831416612..450143cd0c 100644
--- a/scene/gui/tab_container.h
+++ b/scene/gui/tab_container.h
@@ -43,10 +43,11 @@ class TabContainer : public Container {
bool all_tabs_in_front = false;
bool menu_hovered = false;
mutable ObjectID popup_obj_id;
- bool drag_to_rearrange_enabled = false;
bool use_hidden_tabs_for_min_size = false;
bool theme_changing = false;
Vector<Control *> children_removing;
+ bool drag_to_rearrange_enabled = false;
+ int setup_current_tab = -1;
struct ThemeCache {
int side_margin = 0;
@@ -97,10 +98,13 @@ class TabContainer : public Container {
void _on_tab_hovered(int p_tab);
void _on_tab_selected(int p_tab);
void _on_tab_button_pressed(int p_tab);
+ void _on_active_tab_rearranged(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);
+ void _drag_move_tab(int p_from_index, int p_to_index);
+ void _drag_move_tab_from(TabBar *p_from_tabbar, int p_from_index, int p_to_index);
protected:
virtual void gui_input(const Ref<InputEvent> &p_event) override;
@@ -166,6 +170,8 @@ public:
void set_popup(Node *p_popup);
Popup *get_popup() const;
+ void move_tab_from_tab_container(TabContainer *p_from, int p_from_index, int p_to_index = -1);
+
void set_drag_to_rearrange_enabled(bool p_enabled);
bool get_drag_to_rearrange_enabled() const;
void set_tabs_rearrange_group(int p_group_id);
diff --git a/scene/gui/text_edit.cpp b/scene/gui/text_edit.cpp
index 9e159cd303..86e726d9da 100644
--- a/scene/gui/text_edit.cpp
+++ b/scene/gui/text_edit.cpp
@@ -212,9 +212,6 @@ void TextEdit::Text::invalidate_cache(int p_line, int p_column, bool p_text_chan
for (int i = 0; i < spans; i++) {
TS->shaped_set_span_update_font(r, i, font->get_rids(), font_size, font->get_opentype_features());
}
- for (int i = 0; i < TextServer::SPACING_MAX; i++) {
- TS->shaped_text_set_spacing(r, TextServer::SpacingType(i), font->get_spacing(TextServer::SpacingType(i)));
- }
}
// Apply tab align.
@@ -1072,12 +1069,7 @@ void TextEdit::_notification(int p_what) {
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.callp(argp, 3, ret, ce);
+ gutter.custom_draw_callback.call(line, g, Rect2(gutter_rect));
}
} break;
}
@@ -1693,10 +1685,10 @@ void TextEdit::gui_input(const Ref<InputEvent> &p_gui_input) {
h_scroll->set_value(h_scroll->get_value() - (100 * mb->get_factor()));
} else if (mb->is_alt_pressed()) {
// Scroll 5 times as fast as normal (like in Visual Studio Code).
- _scroll_up(15 * mb->get_factor());
+ _scroll_up(15 * mb->get_factor(), true);
} else if (v_scroll->is_visible()) {
// Scroll 3 lines.
- _scroll_up(3 * mb->get_factor());
+ _scroll_up(3 * mb->get_factor(), true);
}
}
if (mb->get_button_index() == MouseButton::WHEEL_DOWN && !mb->is_command_or_control_pressed()) {
@@ -1704,10 +1696,10 @@ void TextEdit::gui_input(const Ref<InputEvent> &p_gui_input) {
h_scroll->set_value(h_scroll->get_value() + (100 * mb->get_factor()));
} else if (mb->is_alt_pressed()) {
// Scroll 5 times as fast as normal (like in Visual Studio Code).
- _scroll_down(15 * mb->get_factor());
+ _scroll_down(15 * mb->get_factor(), true);
} else if (v_scroll->is_visible()) {
// Scroll 3 lines.
- _scroll_down(3 * mb->get_factor());
+ _scroll_down(3 * mb->get_factor(), true);
}
}
if (mb->get_button_index() == MouseButton::WHEEL_LEFT) {
@@ -1941,9 +1933,9 @@ void TextEdit::gui_input(const Ref<InputEvent> &p_gui_input) {
if (pan_gesture.is_valid()) {
const real_t delta = pan_gesture->get_delta().y;
if (delta < 0) {
- _scroll_up(-delta);
+ _scroll_up(-delta, false);
} else {
- _scroll_down(delta);
+ _scroll_down(delta, false);
}
h_scroll->set_value(h_scroll->get_value() + pan_gesture->get_delta().x * 100);
if (v_scroll->get_value() != prev_v_scroll || h_scroll->get_value() != prev_h_scroll) {
@@ -2956,6 +2948,8 @@ void TextEdit::_update_placeholder() {
return; // Not in tree?
}
+ const String placeholder_translated = atr(placeholder_text);
+
// 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());
@@ -2966,9 +2960,9 @@ void TextEdit::_update_placeholder() {
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, theme_cache.font, theme_cache.font_size, language);
+ placeholder_data_buf->add_string(placeholder_translated, theme_cache.font, theme_cache.font_size, language);
- placeholder_bidi_override = structured_text_parser(st_parser, st_args, placeholder_text);
+ placeholder_bidi_override = structured_text_parser(st_parser, st_args, placeholder_translated);
if (placeholder_bidi_override.is_empty()) {
TS->shaped_text_set_bidi_override(placeholder_data_buf->get_rid(), placeholder_bidi_override);
}
@@ -2993,7 +2987,7 @@ void TextEdit::_update_placeholder() {
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));
+ placeholder_wraped_rows.push_back(placeholder_translated.substr(line_range.x, line_range.y - line_range.x));
}
}
@@ -4112,6 +4106,9 @@ Point2i TextEdit::search(const String &p_key, uint32_t p_search_flags, int p_fro
int line = p_from_line;
int pos = -1;
+ bool key_start_is_symbol = is_symbol(p_key[0]);
+ bool key_end_is_symbol = is_symbol(p_key[p_key.length() - 1]);
+
for (int i = 0; i < text.size() + 1; i++) {
if (line < 0) {
line = text.size() - 1;
@@ -4175,9 +4172,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_symbol(text_line[pos - 1])) {
+ if (!key_start_is_symbol && pos > 0 && !is_symbol(text_line[pos - 1])) {
is_match = false;
- } else if (pos + p_key.length() < text_line.length() && !is_symbol(text_line[pos + p_key.length()])) {
+ } else if (!key_end_is_symbol && pos + p_key.length() < text_line.length() && !is_symbol(text_line[pos + p_key.length()])) {
is_match = false;
}
}
@@ -5473,7 +5470,7 @@ void TextEdit::set_line_as_last_visible(int p_line, int p_wrap_index) {
set_v_scroll(0);
return;
}
- set_v_scroll(get_scroll_pos_for_line(first_line, next_line.y) + _get_visible_lines_offset());
+ set_v_scroll(Math::round(get_scroll_pos_for_line(first_line, next_line.y) + _get_visible_lines_offset()));
}
int TextEdit::get_last_full_visible_line() const {
@@ -6433,14 +6430,6 @@ void TextEdit::_bind_methods() {
ADD_PROPERTY(PropertyInfo(Variant::INT, "wrap_mode", PROPERTY_HINT_ENUM, "None,Boundary"), "set_line_wrapping_mode", "get_line_wrapping_mode");
ADD_PROPERTY(PropertyInfo(Variant::INT, "autowrap_mode", PROPERTY_HINT_ENUM, "Arbitrary:1,Word:2,Word (Smart):3"), "set_autowrap_mode", "get_autowrap_mode");
- ADD_PROPERTY(PropertyInfo(Variant::BOOL, "highlight_all_occurrences"), "set_highlight_all_occurrences", "is_highlight_all_occurrences_enabled");
- ADD_PROPERTY(PropertyInfo(Variant::BOOL, "highlight_current_line"), "set_highlight_current_line", "is_highlight_current_line_enabled");
- ADD_PROPERTY(PropertyInfo(Variant::BOOL, "draw_control_chars"), "set_draw_control_chars", "get_draw_control_chars");
- ADD_PROPERTY(PropertyInfo(Variant::BOOL, "draw_tabs"), "set_draw_tabs", "is_drawing_tabs");
- ADD_PROPERTY(PropertyInfo(Variant::BOOL, "draw_spaces"), "set_draw_spaces", "is_drawing_spaces");
-
- ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "syntax_highlighter", PROPERTY_HINT_RESOURCE_TYPE, "SyntaxHighlighter", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_ALWAYS_DUPLICATE), "set_syntax_highlighter", "get_syntax_highlighter");
-
ADD_GROUP("Scroll", "scroll_");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "scroll_smooth"), "set_smooth_scroll_enabled", "is_smooth_scroll_enabled");
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "scroll_v_scroll_speed", PROPERTY_HINT_NONE, "suffix:px/s"), "set_v_scroll_speed", "get_v_scroll_speed");
@@ -6462,6 +6451,16 @@ void TextEdit::_bind_methods() {
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "caret_mid_grapheme"), "set_caret_mid_grapheme_enabled", "is_caret_mid_grapheme_enabled");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "caret_multiple"), "set_multiple_carets_enabled", "is_multiple_carets_enabled");
+ ADD_GROUP("Highlighting", "");
+ ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "syntax_highlighter", PROPERTY_HINT_RESOURCE_TYPE, "SyntaxHighlighter", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_ALWAYS_DUPLICATE), "set_syntax_highlighter", "get_syntax_highlighter");
+ ADD_PROPERTY(PropertyInfo(Variant::BOOL, "highlight_all_occurrences"), "set_highlight_all_occurrences", "is_highlight_all_occurrences_enabled");
+ ADD_PROPERTY(PropertyInfo(Variant::BOOL, "highlight_current_line"), "set_highlight_current_line", "is_highlight_current_line_enabled");
+
+ ADD_GROUP("Visual Whitespace", "draw_");
+ ADD_PROPERTY(PropertyInfo(Variant::BOOL, "draw_control_chars"), "set_draw_control_chars", "get_draw_control_chars");
+ ADD_PROPERTY(PropertyInfo(Variant::BOOL, "draw_tabs"), "set_draw_tabs", "is_drawing_tabs");
+ ADD_PROPERTY(PropertyInfo(Variant::BOOL, "draw_spaces"), "set_draw_spaces", "is_drawing_spaces");
+
ADD_GROUP("BiDi", "");
ADD_PROPERTY(PropertyInfo(Variant::INT, "text_direction", PROPERTY_HINT_ENUM, "Auto,Left-to-Right,Right-to-Left,Inherited"), "set_text_direction", "get_text_direction");
ADD_PROPERTY(PropertyInfo(Variant::STRING, "language", PROPERTY_HINT_LOCALE_ID, ""), "set_language", "get_language");
@@ -7020,6 +7019,9 @@ int TextEdit::_get_column_pos_of_word(const String &p_key, const String &p_searc
p_from_column = 0;
}
+ bool key_start_is_symbol = is_symbol(p_key[0]);
+ bool key_end_is_symbol = is_symbol(p_key[p_key.length() - 1]);
+
while (col == -1 && p_from_column <= p_search.length()) {
if (p_search_flags & SEARCH_MATCH_CASE) {
col = p_search.find(p_key, p_from_column);
@@ -7036,9 +7038,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_symbol(p_search[col - 1])) {
+ if (!key_start_is_symbol && col > 0 && !is_symbol(p_search[col - 1])) {
col = -1;
- } else if ((col + p_key.length()) < p_search.length() && !is_symbol(p_search[col + p_key.length()])) {
+ } else if (!key_end_is_symbol && (col + p_key.length()) < p_search.length() && !is_symbol(p_search[col + p_key.length()])) {
col = -1;
}
}
@@ -7340,7 +7342,7 @@ void TextEdit::_update_scrollbars() {
}
int visible_width = size.width - theme_cache.style_normal->get_minimum_size().width;
- int total_width = (draw_placeholder ? placeholder_max_width : text.get_max_width()) + vmin.x + gutters_width + gutter_padding;
+ int total_width = (draw_placeholder ? placeholder_max_width : text.get_max_width()) + gutters_width + gutter_padding;
if (draw_minimap) {
total_width += minimap_width;
@@ -7357,11 +7359,6 @@ void TextEdit::_update_scrollbars() {
v_scroll->show();
v_scroll->set_max(total_rows + _get_visible_lines_offset());
v_scroll->set_page(visible_rows + _get_visible_lines_offset());
- if (smooth_scroll_enabled) {
- v_scroll->set_step(0.25);
- } else {
- v_scroll->set_step(1);
- }
set_v_scroll(get_v_scroll());
} else {
@@ -7455,7 +7452,7 @@ double TextEdit::_get_v_scroll_offset() const {
return CLAMP(val, 0, 1);
}
-void TextEdit::_scroll_up(real_t p_delta) {
+void TextEdit::_scroll_up(real_t p_delta, bool p_animate) {
if (scrolling && smooth_scroll_enabled && SIGN(target_v_scroll - v_scroll->get_value()) != SIGN(-p_delta)) {
scrolling = false;
minimap_clicked = false;
@@ -7471,7 +7468,7 @@ void TextEdit::_scroll_up(real_t p_delta) {
if (target_v_scroll <= 0) {
target_v_scroll = 0;
}
- if (Math::abs(target_v_scroll - v_scroll->get_value()) < 1.0) {
+ if (!p_animate || Math::abs(target_v_scroll - v_scroll->get_value()) < 1.0) {
v_scroll->set_value(target_v_scroll);
} else {
scrolling = true;
@@ -7482,7 +7479,7 @@ void TextEdit::_scroll_up(real_t p_delta) {
}
}
-void TextEdit::_scroll_down(real_t p_delta) {
+void TextEdit::_scroll_down(real_t p_delta, bool p_animate) {
if (scrolling && smooth_scroll_enabled && SIGN(target_v_scroll - v_scroll->get_value()) != SIGN(p_delta)) {
scrolling = false;
minimap_clicked = false;
@@ -7499,7 +7496,7 @@ void TextEdit::_scroll_down(real_t p_delta) {
if (target_v_scroll > max_v_scroll) {
target_v_scroll = max_v_scroll;
}
- if (Math::abs(target_v_scroll - v_scroll->get_value()) < 1.0) {
+ if (!p_animate || Math::abs(target_v_scroll - v_scroll->get_value()) < 1.0) {
v_scroll->set_value(target_v_scroll);
} else {
scrolling = true;
@@ -7605,9 +7602,9 @@ void TextEdit::_update_minimap_click() {
int first_line = row - next_line.x + 1;
double delta = get_scroll_pos_for_line(first_line, next_line.y) - get_v_scroll();
if (delta < 0) {
- _scroll_up(-delta);
+ _scroll_up(-delta, true);
} else {
- _scroll_down(delta);
+ _scroll_down(delta, true);
}
}
diff --git a/scene/gui/text_edit.h b/scene/gui/text_edit.h
index 56d5b67e0b..8a541b623b 100644
--- a/scene/gui/text_edit.h
+++ b/scene/gui/text_edit.h
@@ -500,8 +500,8 @@ private:
double _get_visible_lines_offset() const;
double _get_v_scroll_offset() const;
- void _scroll_up(real_t p_delta);
- void _scroll_down(real_t p_delta);
+ void _scroll_up(real_t p_delta, bool p_animate);
+ void _scroll_down(real_t p_delta, bool p_animate);
void _scroll_lines_up();
void _scroll_lines_down();
diff --git a/scene/gui/tree.cpp b/scene/gui/tree.cpp
index e2b16cdd66..a4c239cf53 100644
--- a/scene/gui/tree.cpp
+++ b/scene/gui/tree.cpp
@@ -1476,7 +1476,7 @@ Size2 TreeItem::get_minimum_size(int p_column) {
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;
+ r_error.expected = 1;
return;
}
@@ -2050,7 +2050,7 @@ int Tree::draw_item(const Point2i &p_pos, const Point2 &p_draw_ofs, const Size2
int text_width = item_width - theme_cache.inner_item_margin_left - theme_cache.inner_item_margin_right;
if (p_item->cells[i].icon.is_valid()) {
- text_width -= p_item->cells[i].get_icon_size().x + theme_cache.h_separation;
+ text_width -= _get_cell_icon_size(p_item->cells[i]).x + theme_cache.h_separation;
}
p_item->cells.write[i].text_buf->set_width(text_width);
@@ -2770,21 +2770,9 @@ int Tree::propagate_mouse_event(const Point2i &p_pos, int x_ofs, int y_ofs, int
x -= theme_cache.h_separation;
}
- if (!p_item->disable_folding && !hide_folding && !p_item->cells[col].editable && !p_item->cells[col].selectable && p_item->get_first_child()) {
- if (enable_recursive_folding && p_mod->is_shift_pressed()) {
- p_item->set_collapsed_recursive(!p_item->is_collapsed());
- } else {
- p_item->set_collapsed(!p_item->is_collapsed());
- }
- return -1; //collapse/uncollapse because nothing can be done with item
- }
-
const TreeItem::Cell &c = p_item->cells[col];
- bool already_selected = c.selected;
- bool already_cursor = (p_item == selected_item) && col == selected_col;
-
- if (!cache.rtl && p_item->cells[col].buttons.size()) {
+ if (!cache.rtl && !p_item->cells[col].buttons.is_empty()) {
int button_w = 0;
for (int j = p_item->cells[col].buttons.size() - 1; j >= 0; j--) {
Ref<Texture2D> b = p_item->cells[col].buttons[j].texture;
@@ -2827,6 +2815,18 @@ int Tree::propagate_mouse_event(const Point2i &p_pos, int x_ofs, int y_ofs, int
col_width -= w + theme_cache.button_margin;
}
+ if (!p_item->disable_folding && !hide_folding && !p_item->cells[col].editable && !p_item->cells[col].selectable && p_item->get_first_child()) {
+ if (enable_recursive_folding && p_mod->is_shift_pressed()) {
+ p_item->set_collapsed_recursive(!p_item->is_collapsed());
+ } else {
+ p_item->set_collapsed(!p_item->is_collapsed());
+ }
+ return -1; // Collapse/uncollapse, because nothing can be done with the item.
+ }
+
+ bool already_selected = c.selected;
+ bool already_cursor = (p_item == selected_item) && col == selected_col;
+
if (p_button == MouseButton::LEFT || (p_button == MouseButton::RIGHT && allow_rmb_select)) {
/* process selection */
@@ -3142,7 +3142,7 @@ void Tree::value_editor_changed(double p_value) {
TreeItem::Cell &c = popup_edited_item->cells.write[popup_edited_item_col];
c.val = p_value;
- text_editor->set_text(String::num(c.val, Math::range_step_decimals(c.step)));
+ line_editor->set_text(String::num(c.val, Math::range_step_decimals(c.step)));
item_edited(popup_edited_item_col, popup_edited_item);
queue_redraw();
diff --git a/scene/gui/view_panner.cpp b/scene/gui/view_panner.cpp
index 6d1905f111..fc03f2d887 100644
--- a/scene/gui/view_panner.cpp
+++ b/scene/gui/view_panner.cpp
@@ -47,7 +47,7 @@ bool ViewPanner::gui_input(const Ref<InputEvent> &p_event, Rect2 p_canvas_rect)
float zoom_factor = mb->get_factor() <= 0 ? 1.0 : mb->get_factor();
zoom_factor = ((scroll_zoom_factor - 1.0) * zoom_factor) + 1.0;
float zoom = (scroll_vec.x + scroll_vec.y) > 0 ? 1.0 / scroll_zoom_factor : scroll_zoom_factor;
- callback_helper(zoom_callback, varray(zoom, mb->get_position(), p_event));
+ zoom_callback.call(zoom, mb->get_position(), p_event);
return true;
} else {
Vector2 panning = scroll_vec * mb->get_factor();
@@ -58,7 +58,7 @@ bool ViewPanner::gui_input(const Ref<InputEvent> &p_event, Rect2 p_canvas_rect)
} else if (mb->is_shift_pressed()) {
panning = Vector2(panning.y, panning.x);
}
- callback_helper(pan_callback, varray(-panning * scroll_speed, p_event));
+ pan_callback.call(-panning * scroll_speed, p_event);
return true;
}
} else {
@@ -71,14 +71,14 @@ bool ViewPanner::gui_input(const Ref<InputEvent> &p_event, Rect2 p_canvas_rect)
} else if (mb->is_shift_pressed()) {
panning = Vector2(panning.y, panning.x);
}
- callback_helper(pan_callback, varray(-panning * scroll_speed, p_event));
+ pan_callback.call(-panning * scroll_speed, p_event);
return true;
} else if (!mb->is_shift_pressed()) {
// Compute the zoom factor.
float zoom_factor = mb->get_factor() <= 0 ? 1.0 : mb->get_factor();
zoom_factor = ((scroll_zoom_factor - 1.0) * zoom_factor) + 1.0;
float zoom = (scroll_vec.x + scroll_vec.y) > 0 ? 1.0 / scroll_zoom_factor : scroll_zoom_factor;
- callback_helper(zoom_callback, varray(zoom, mb->get_position(), p_event));
+ zoom_callback.call(zoom, mb->get_position(), p_event);
return true;
}
}
@@ -108,9 +108,9 @@ bool ViewPanner::gui_input(const Ref<InputEvent> &p_event, Rect2 p_canvas_rect)
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), p_event));
+ pan_callback.call(Input::get_singleton()->warp_mouse_motion(mm, p_canvas_rect), p_event);
} else {
- callback_helper(pan_callback, varray(mm->get_relative(), p_event));
+ pan_callback.call(mm->get_relative(), p_event);
}
return true;
}
@@ -119,13 +119,13 @@ bool ViewPanner::gui_input(const Ref<InputEvent> &p_event, Rect2 p_canvas_rect)
Ref<InputEventMagnifyGesture> magnify_gesture = p_event;
if (magnify_gesture.is_valid()) {
// Zoom gesture
- callback_helper(zoom_callback, varray(magnify_gesture->get_factor(), magnify_gesture->get_position(), p_event));
+ zoom_callback.call(magnify_gesture->get_factor(), magnify_gesture->get_position(), p_event);
return true;
}
Ref<InputEventPanGesture> pan_gesture = p_event;
if (pan_gesture.is_valid()) {
- callback_helper(pan_callback, varray(-pan_gesture->get_delta() * scroll_speed, p_event));
+ pan_callback.call(-pan_gesture->get_delta() * scroll_speed, p_event);
}
Ref<InputEventScreenDrag> screen_drag = p_event;
@@ -134,7 +134,7 @@ bool ViewPanner::gui_input(const Ref<InputEvent> &p_event, Rect2 p_canvas_rect)
// This set of events also generates/is generated by
// InputEventMouseButton/InputEventMouseMotion events which will be processed instead.
} else {
- callback_helper(pan_callback, varray(screen_drag->get_relative(), p_event));
+ pan_callback.call(screen_drag->get_relative(), p_event);
}
}
@@ -157,17 +157,6 @@ void ViewPanner::release_pan_key() {
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.callp(argptr, p_args.size(), result, ce);
-}
-
void ViewPanner::set_callbacks(Callable p_pan_callback, Callable p_zoom_callback) {
pan_callback = p_pan_callback;
zoom_callback = p_zoom_callback;
diff --git a/scene/gui/view_panner.h b/scene/gui/view_panner.h
index 60d36ca04c..5aec2d4f6b 100644
--- a/scene/gui/view_panner.h
+++ b/scene/gui/view_panner.h
@@ -68,7 +68,6 @@ private:
Callable pan_callback;
Callable zoom_callback;
- void callback_helper(Callable p_callback, Vector<Variant> p_args);
ControlScheme control_scheme = SCROLL_ZOOMS;
public: