summaryrefslogtreecommitdiff
path: root/scene/gui
diff options
context:
space:
mode:
Diffstat (limited to 'scene/gui')
-rw-r--r--scene/gui/aspect_ratio_container.cpp2
-rw-r--r--scene/gui/base_button.cpp4
-rw-r--r--scene/gui/base_button.h2
-rw-r--r--scene/gui/button.cpp18
-rw-r--r--scene/gui/check_box.cpp4
-rw-r--r--scene/gui/check_button.cpp4
-rw-r--r--scene/gui/code_edit.cpp37
-rw-r--r--scene/gui/code_edit.h8
-rw-r--r--scene/gui/color_picker.cpp4
-rw-r--r--scene/gui/container.cpp2
-rw-r--r--scene/gui/control.cpp109
-rw-r--r--scene/gui/control.h16
-rw-r--r--scene/gui/dialogs.cpp18
-rw-r--r--scene/gui/dialogs.h14
-rw-r--r--scene/gui/file_dialog.cpp14
-rw-r--r--scene/gui/file_dialog.h52
-rw-r--r--scene/gui/flow_container.cpp4
-rw-r--r--scene/gui/gradient_edit.h4
-rw-r--r--scene/gui/graph_edit.cpp28
-rw-r--r--scene/gui/graph_edit.h32
-rw-r--r--scene/gui/graph_node.cpp5
-rw-r--r--scene/gui/grid_container.cpp11
-rw-r--r--scene/gui/item_list.cpp73
-rw-r--r--scene/gui/item_list.h2
-rw-r--r--scene/gui/label.cpp8
-rw-r--r--scene/gui/label.h6
-rw-r--r--scene/gui/line_edit.cpp26
-rw-r--r--scene/gui/line_edit.h7
-rw-r--r--scene/gui/link_button.cpp4
-rw-r--r--scene/gui/link_button.h6
-rw-r--r--scene/gui/menu_button.cpp4
-rw-r--r--scene/gui/menu_button.h4
-rw-r--r--scene/gui/option_button.cpp49
-rw-r--r--scene/gui/option_button.h8
-rw-r--r--scene/gui/popup.h2
-rw-r--r--scene/gui/popup_menu.cpp64
-rw-r--r--scene/gui/popup_menu.h11
-rw-r--r--scene/gui/range.cpp4
-rw-r--r--scene/gui/range.h2
-rw-r--r--scene/gui/rich_text_label.cpp378
-rw-r--r--scene/gui/rich_text_label.h41
-rw-r--r--scene/gui/scroll_container.cpp2
-rw-r--r--scene/gui/scroll_container.h4
-rw-r--r--scene/gui/spin_box.h4
-rw-r--r--scene/gui/subviewport_container.cpp21
-rw-r--r--scene/gui/subviewport_container.h2
-rw-r--r--scene/gui/tab_bar.cpp106
-rw-r--r--scene/gui/tab_bar.h1
-rw-r--r--scene/gui/tab_container.cpp78
-rw-r--r--scene/gui/tab_container.h6
-rw-r--r--scene/gui/text_edit.cpp248
-rw-r--r--scene/gui/text_edit.h17
-rw-r--r--scene/gui/texture_progress_bar.cpp18
-rw-r--r--scene/gui/tree.cpp174
-rw-r--r--scene/gui/tree.h27
-rw-r--r--scene/gui/video_stream_player.cpp4
-rw-r--r--scene/gui/view_panner.cpp2
57 files changed, 1174 insertions, 631 deletions
diff --git a/scene/gui/aspect_ratio_container.cpp b/scene/gui/aspect_ratio_container.cpp
index b59eda465e..75f19ac452 100644
--- a/scene/gui/aspect_ratio_container.cpp
+++ b/scene/gui/aspect_ratio_container.cpp
@@ -172,7 +172,7 @@ void AspectRatioContainer::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_alignment_vertical", "alignment_vertical"), &AspectRatioContainer::set_alignment_vertical);
ClassDB::bind_method(D_METHOD("get_alignment_vertical"), &AspectRatioContainer::get_alignment_vertical);
- ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "ratio"), "set_ratio", "get_ratio");
+ ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "ratio", PROPERTY_HINT_RANGE, "0.001,10.0,0.0001,or_greater"), "set_ratio", "get_ratio");
ADD_PROPERTY(PropertyInfo(Variant::INT, "stretch_mode", PROPERTY_HINT_ENUM, "Width Controls Height,Height Controls Width,Fit,Cover"), "set_stretch_mode", "get_stretch_mode");
ADD_GROUP("Alignment", "alignment_");
diff --git a/scene/gui/base_button.cpp b/scene/gui/base_button.cpp
index ab86face7e..789c01adf3 100644
--- a/scene/gui/base_button.cpp
+++ b/scene/gui/base_button.cpp
@@ -339,14 +339,14 @@ bool BaseButton::is_keep_pressed_outside() const {
void BaseButton::set_shortcut(const Ref<Shortcut> &p_shortcut) {
shortcut = p_shortcut;
- set_process_unhandled_key_input(shortcut.is_valid());
+ set_process_shortcut_input(shortcut.is_valid());
}
Ref<Shortcut> BaseButton::get_shortcut() const {
return shortcut;
}
-void BaseButton::unhandled_key_input(const Ref<InputEvent> &p_event) {
+void BaseButton::shortcut_input(const Ref<InputEvent> &p_event) {
ERR_FAIL_COND(p_event.is_null());
if (!_is_focus_owner_in_shortcut_context()) {
diff --git a/scene/gui/base_button.h b/scene/gui/base_button.h
index a2b6ee0845..f4f9b88868 100644
--- a/scene/gui/base_button.h
+++ b/scene/gui/base_button.h
@@ -77,7 +77,7 @@ protected:
virtual void toggled(bool p_pressed);
static void _bind_methods();
virtual void gui_input(const Ref<InputEvent> &p_event) override;
- virtual void unhandled_key_input(const Ref<InputEvent> &p_event) override;
+ virtual void shortcut_input(const Ref<InputEvent> &p_event) override;
void _notification(int p_what);
bool _is_focus_owner_in_shortcut_context() const;
diff --git a/scene/gui/button.cpp b/scene/gui/button.cpp
index 29a0681f9c..b7c1e674dd 100644
--- a/scene/gui/button.cpp
+++ b/scene/gui/button.cpp
@@ -53,7 +53,7 @@ Size2 Button::get_minimum_size() const {
if (icon_alignment != HORIZONTAL_ALIGNMENT_CENTER) {
minsize.width += _icon->get_width();
if (!xl_text.is_empty()) {
- minsize.width += get_theme_constant(SNAME("hseparation"));
+ minsize.width += get_theme_constant(SNAME("h_separation"));
}
} else {
minsize.width = MAX(minsize.width, _icon->get_width());
@@ -244,21 +244,21 @@ void Button::_notification(int p_what) {
if (icon_align_rtl_checked == HORIZONTAL_ALIGNMENT_LEFT) {
style_offset.x = style->get_margin(SIDE_LEFT);
if (_internal_margin[SIDE_LEFT] > 0) {
- icon_ofs_region = _internal_margin[SIDE_LEFT] + get_theme_constant(SNAME("hseparation"));
+ icon_ofs_region = _internal_margin[SIDE_LEFT] + get_theme_constant(SNAME("h_separation"));
}
} else if (icon_align_rtl_checked == HORIZONTAL_ALIGNMENT_CENTER) {
style_offset.x = 0.0;
} else if (icon_align_rtl_checked == HORIZONTAL_ALIGNMENT_RIGHT) {
style_offset.x = -style->get_margin(SIDE_RIGHT);
if (_internal_margin[SIDE_RIGHT] > 0) {
- icon_ofs_region = -_internal_margin[SIDE_RIGHT] - get_theme_constant(SNAME("hseparation"));
+ icon_ofs_region = -_internal_margin[SIDE_RIGHT] - get_theme_constant(SNAME("h_separation"));
}
}
style_offset.y = style->get_margin(SIDE_TOP);
if (expand_icon) {
Size2 _size = get_size() - style->get_offset() * 2;
- _size.width -= get_theme_constant(SNAME("hseparation")) + icon_ofs_region;
+ _size.width -= get_theme_constant(SNAME("h_separation")) + icon_ofs_region;
if (!clip_text && icon_align_rtl_checked != HORIZONTAL_ALIGNMENT_CENTER) {
_size.width -= text_buf->get_size().width;
}
@@ -286,7 +286,7 @@ void Button::_notification(int p_what) {
}
}
- Point2 icon_ofs = !_icon.is_null() ? Point2(icon_region.size.width + get_theme_constant(SNAME("hseparation")), 0) : Point2();
+ Point2 icon_ofs = !_icon.is_null() ? Point2(icon_region.size.width + get_theme_constant(SNAME("h_separation")), 0) : Point2();
if (align_rtl_checked == HORIZONTAL_ALIGNMENT_CENTER && icon_align_rtl_checked == HORIZONTAL_ALIGNMENT_CENTER) {
icon_ofs.x = 0.0;
}
@@ -296,10 +296,10 @@ void Button::_notification(int p_what) {
int text_width = MAX(1, clip_text ? MIN(text_clip, text_buf->get_size().x) : text_buf->get_size().x);
if (_internal_margin[SIDE_LEFT] > 0) {
- text_clip -= _internal_margin[SIDE_LEFT] + get_theme_constant(SNAME("hseparation"));
+ text_clip -= _internal_margin[SIDE_LEFT] + get_theme_constant(SNAME("h_separation"));
}
if (_internal_margin[SIDE_RIGHT] > 0) {
- text_clip -= _internal_margin[SIDE_RIGHT] + get_theme_constant(SNAME("hseparation"));
+ text_clip -= _internal_margin[SIDE_RIGHT] + get_theme_constant(SNAME("h_separation"));
}
Point2 text_ofs = (size - style->get_minimum_size() - icon_ofs - text_buf->get_size() - Point2(_internal_margin[SIDE_RIGHT] - _internal_margin[SIDE_LEFT], 0)) / 2.0;
@@ -313,7 +313,7 @@ void Button::_notification(int p_what) {
icon_ofs.x = 0.0;
}
if (_internal_margin[SIDE_LEFT] > 0) {
- text_ofs.x = style->get_margin(SIDE_LEFT) + icon_ofs.x + _internal_margin[SIDE_LEFT] + get_theme_constant(SNAME("hseparation"));
+ text_ofs.x = style->get_margin(SIDE_LEFT) + icon_ofs.x + _internal_margin[SIDE_LEFT] + get_theme_constant(SNAME("h_separation"));
} else {
text_ofs.x = style->get_margin(SIDE_LEFT) + icon_ofs.x;
}
@@ -330,7 +330,7 @@ void Button::_notification(int p_what) {
} break;
case HORIZONTAL_ALIGNMENT_RIGHT: {
if (_internal_margin[SIDE_RIGHT] > 0) {
- text_ofs.x = size.x - style->get_margin(SIDE_RIGHT) - text_width - _internal_margin[SIDE_RIGHT] - get_theme_constant(SNAME("hseparation"));
+ text_ofs.x = size.x - style->get_margin(SIDE_RIGHT) - text_width - _internal_margin[SIDE_RIGHT] - get_theme_constant(SNAME("h_separation"));
} else {
text_ofs.x = size.x - style->get_margin(SIDE_RIGHT) - text_width;
}
diff --git a/scene/gui/check_box.cpp b/scene/gui/check_box.cpp
index 063a154bb2..cb80f5b5ef 100644
--- a/scene/gui/check_box.cpp
+++ b/scene/gui/check_box.cpp
@@ -75,7 +75,7 @@ Size2 CheckBox::get_minimum_size() const {
Size2 tex_size = get_icon_size();
minsize.width += tex_size.width;
if (get_text().length() > 0) {
- minsize.width += get_theme_constant(SNAME("hseparation"));
+ minsize.width += get_theme_constant(SNAME("h_separation"));
}
Ref<StyleBox> sb = get_theme_stylebox(SNAME("normal"));
minsize.height = MAX(minsize.height, tex_size.height + sb->get_margin(SIDE_TOP) + sb->get_margin(SIDE_BOTTOM));
@@ -110,7 +110,7 @@ void CheckBox::_notification(int p_what) {
} else {
ofs.x = sb->get_margin(SIDE_LEFT);
}
- ofs.y = int((get_size().height - get_icon_size().height) / 2) + get_theme_constant(SNAME("check_vadjust"));
+ ofs.y = int((get_size().height - get_icon_size().height) / 2) + get_theme_constant(SNAME("check_v_adjust"));
if (is_pressed()) {
on->draw(ci, ofs);
diff --git a/scene/gui/check_button.cpp b/scene/gui/check_button.cpp
index 527b0061ac..a09873ea4f 100644
--- a/scene/gui/check_button.cpp
+++ b/scene/gui/check_button.cpp
@@ -52,7 +52,7 @@ Size2 CheckButton::get_minimum_size() const {
Size2 tex_size = get_icon_size();
minsize.width += tex_size.width;
if (get_text().length() > 0) {
- minsize.width += get_theme_constant(SNAME("hseparation"));
+ minsize.width += get_theme_constant(SNAME("h_separation"));
}
Ref<StyleBox> sb = get_theme_stylebox(SNAME("normal"));
minsize.height = MAX(minsize.height, tex_size.height + sb->get_margin(SIDE_TOP) + sb->get_margin(SIDE_BOTTOM));
@@ -100,7 +100,7 @@ void CheckButton::_notification(int p_what) {
} else {
ofs.x = get_size().width - (tex_size.width + sb->get_margin(SIDE_RIGHT));
}
- ofs.y = (get_size().height - tex_size.height) / 2 + get_theme_constant(SNAME("check_vadjust"));
+ ofs.y = (get_size().height - tex_size.height) / 2 + get_theme_constant(SNAME("check_v_adjust"));
if (is_pressed()) {
on->draw(ci, ofs);
diff --git a/scene/gui/code_edit.cpp b/scene/gui/code_edit.cpp
index 3fa0cec302..fdcd7116f3 100644
--- a/scene/gui/code_edit.cpp
+++ b/scene/gui/code_edit.cpp
@@ -106,7 +106,7 @@ void CodeEdit::_notification(int p_what) {
const int code_completion_options_count = code_completion_options.size();
const int lines = MIN(code_completion_options_count, code_completion_max_lines);
- const int icon_hsep = get_theme_constant(SNAME("hseparation"), SNAME("ItemList"));
+ const int icon_hsep = get_theme_constant(SNAME("h_separation"), SNAME("ItemList"));
const Size2 icon_area_size(row_height, row_height);
code_completion_rect.size.width = code_completion_longest_line + icon_hsep + icon_area_size.width + 2;
@@ -994,7 +994,8 @@ void CodeEdit::_new_line(bool p_split_current_line, bool p_above) {
}
/* Make sure this is the last char, trailing whitespace or comments are okay. */
- if (should_indent && (!is_whitespace(c) && is_in_comment(cl, cc) == -1)) {
+ /* Increment column for comments because the delimiter (#) should be ignored. */
+ if (should_indent && (!is_whitespace(c) && is_in_comment(cl, line_col + 1) == -1)) {
should_indent = false;
}
}
@@ -1782,10 +1783,10 @@ void CodeEdit::request_code_completion(bool p_force) {
/* Don't re-query if all existing options are quoted types, eg path, signal. */
bool ignored = code_completion_active && !code_completion_options.is_empty();
if (ignored) {
- ScriptCodeCompletionOption::Kind kind = ScriptCodeCompletionOption::KIND_PLAIN_TEXT;
- const ScriptCodeCompletionOption *previous_option = nullptr;
+ ScriptLanguage::CodeCompletionKind kind = ScriptLanguage::CODE_COMPLETION_KIND_PLAIN_TEXT;
+ const ScriptLanguage::CodeCompletionOption *previous_option = nullptr;
for (int i = 0; i < code_completion_options.size(); i++) {
- const ScriptCodeCompletionOption &current_option = code_completion_options[i];
+ const ScriptLanguage::CodeCompletionOption &current_option = code_completion_options[i];
if (!previous_option) {
previous_option = &current_option;
kind = current_option.kind;
@@ -1795,7 +1796,7 @@ void CodeEdit::request_code_completion(bool p_force) {
break;
}
}
- ignored = ignored && (kind == ScriptCodeCompletionOption::KIND_FILE_PATH || kind == ScriptCodeCompletionOption::KIND_NODE_PATH || kind == ScriptCodeCompletionOption::KIND_SIGNAL);
+ ignored = ignored && (kind == ScriptLanguage::CODE_COMPLETION_KIND_FILE_PATH || kind == ScriptLanguage::CODE_COMPLETION_KIND_NODE_PATH || kind == ScriptLanguage::CODE_COMPLETION_KIND_SIGNAL);
}
if (ignored) {
@@ -1818,8 +1819,8 @@ void CodeEdit::request_code_completion(bool p_force) {
}
void CodeEdit::add_code_completion_option(CodeCompletionKind p_type, const String &p_display_text, const String &p_insert_text, const Color &p_text_color, const RES &p_icon, const Variant &p_value) {
- ScriptCodeCompletionOption completion_option;
- completion_option.kind = (ScriptCodeCompletionOption::Kind)p_type;
+ ScriptLanguage::CodeCompletionOption completion_option;
+ completion_option.kind = (ScriptLanguage::CodeCompletionKind)p_type;
completion_option.display = p_display_text;
completion_option.insert_text = p_insert_text;
completion_option.font_color = p_text_color;
@@ -2261,7 +2262,7 @@ void CodeEdit::_bind_methods() {
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "indent_automatic"), "set_auto_indent_enabled", "is_auto_indent_enabled");
ADD_PROPERTY(PropertyInfo(Variant::PACKED_STRING_ARRAY, "indent_automatic_prefixes"), "set_auto_indent_prefixes", "get_auto_indent_prefixes");
- ADD_GROUP("Auto brace completion", "auto_brace_completion_");
+ ADD_GROUP("Auto Brace Completion", "auto_brace_completion_");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "auto_brace_completion_enabled"), "set_auto_brace_completion_enabled", "is_auto_brace_completion_enabled");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "auto_brace_completion_highlight_matching"), "set_highlight_matching_braces_enabled", "is_highlight_matching_braces_enabled");
ADD_PROPERTY(PropertyInfo(Variant::DICTIONARY, "auto_brace_completion_pairs"), "set_auto_brace_completion_pairs", "get_auto_brace_completion_pairs");
@@ -2702,7 +2703,7 @@ void CodeEdit::_filter_code_completion_candidates_impl() {
TypedArray<Dictionary> completion_options_sources;
completion_options_sources.resize(code_completion_option_sources.size());
int i = 0;
- for (const ScriptCodeCompletionOption &E : code_completion_option_sources) {
+ for (const ScriptLanguage::CodeCompletionOption &E : code_completion_option_sources) {
Dictionary option;
option["kind"] = E.kind;
option["display_text"] = E.display;
@@ -2727,8 +2728,8 @@ void CodeEdit::_filter_code_completion_candidates_impl() {
/* Convert back into options. */
int max_width = 0;
for (i = 0; i < completion_options.size(); i++) {
- ScriptCodeCompletionOption option;
- option.kind = (ScriptCodeCompletionOption::Kind)(int)completion_options[i].get("kind");
+ ScriptLanguage::CodeCompletionOption option;
+ option.kind = (ScriptLanguage::CodeCompletionKind)(int)completion_options[i].get("kind");
option.display = completion_options[i].get("display_text");
option.insert_text = completion_options[i].get("insert_text");
option.font_color = completion_options[i].get("font_color");
@@ -2821,15 +2822,15 @@ void CodeEdit::_filter_code_completion_candidates_impl() {
code_completion_options.clear();
code_completion_base = string_to_complete;
- Vector<ScriptCodeCompletionOption> completion_options_casei;
- Vector<ScriptCodeCompletionOption> completion_options_substr;
- Vector<ScriptCodeCompletionOption> completion_options_substr_casei;
- Vector<ScriptCodeCompletionOption> completion_options_subseq;
- Vector<ScriptCodeCompletionOption> completion_options_subseq_casei;
+ Vector<ScriptLanguage::CodeCompletionOption> completion_options_casei;
+ Vector<ScriptLanguage::CodeCompletionOption> completion_options_substr;
+ Vector<ScriptLanguage::CodeCompletionOption> completion_options_substr_casei;
+ Vector<ScriptLanguage::CodeCompletionOption> completion_options_subseq;
+ Vector<ScriptLanguage::CodeCompletionOption> completion_options_subseq_casei;
int max_width = 0;
String string_to_complete_lower = string_to_complete.to_lower();
- for (ScriptCodeCompletionOption &option : code_completion_option_sources) {
+ for (ScriptLanguage::CodeCompletionOption &option : code_completion_option_sources) {
if (single_quote && option.display.is_quoted()) {
option.display = option.display.unquote().quote("'");
}
diff --git a/scene/gui/code_edit.h b/scene/gui/code_edit.h
index cb1309ced3..596a065f12 100644
--- a/scene/gui/code_edit.h
+++ b/scene/gui/code_edit.h
@@ -38,7 +38,7 @@ class CodeEdit : public TextEdit {
public:
/* Keep enum in sync with: */
- /* /core/object/script_language.h - ScriptCodeCompletionOption::Kind */
+ /* /core/object/script_language.h - ScriptLanguage::CodeCompletionKind */
enum CodeCompletionKind {
KIND_CLASS,
KIND_FUNCTION,
@@ -208,15 +208,15 @@ private:
Color code_completion_existing_color = Color(0, 0, 0, 0);
bool code_completion_active = false;
- Vector<ScriptCodeCompletionOption> code_completion_options;
+ Vector<ScriptLanguage::CodeCompletionOption> code_completion_options;
int code_completion_line_ofs = 0;
int code_completion_current_selected = 0;
int code_completion_longest_line = 0;
Rect2i code_completion_rect;
Set<char32_t> code_completion_prefixes;
- List<ScriptCodeCompletionOption> code_completion_option_submitted;
- List<ScriptCodeCompletionOption> code_completion_option_sources;
+ List<ScriptLanguage::CodeCompletionOption> code_completion_option_submitted;
+ List<ScriptLanguage::CodeCompletionOption> code_completion_option_sources;
String code_completion_base;
void _filter_code_completion_candidates_impl();
diff --git a/scene/gui/color_picker.cpp b/scene/gui/color_picker.cpp
index 9f32ac223c..6f7ad94139 100644
--- a/scene/gui/color_picker.cpp
+++ b/scene/gui/color_picker.cpp
@@ -436,7 +436,7 @@ ColorPicker::PickerShapeType ColorPicker::get_picker_shape() const {
}
inline int ColorPicker::_get_preset_size() {
- return (int(get_minimum_size().width) - (preset_container->get_theme_constant(SNAME("hseparation")) * (preset_column_count - 1))) / preset_column_count;
+ return (int(get_minimum_size().width) - (preset_container->get_theme_constant(SNAME("h_separation")) * (preset_column_count - 1))) / preset_column_count;
}
void ColorPicker::_add_preset_button(int p_size, const Color &p_color) {
@@ -1221,7 +1221,7 @@ ColorPicker::ColorPicker() :
hhb->add_child(text_type);
text_type->set_text("#");
- text_type->set_tooltip(TTR("Switch between hexadecimal and code values."));
+ text_type->set_tooltip(RTR("Switch between hexadecimal and code values."));
if (Engine::get_singleton()->is_editor_hint()) {
text_type->connect("pressed", callable_mp(this, &ColorPicker::_text_type_toggled));
} else {
diff --git a/scene/gui/container.cpp b/scene/gui/container.cpp
index 1dd88371ea..5512c0f1fd 100644
--- a/scene/gui/container.cpp
+++ b/scene/gui/container.cpp
@@ -196,7 +196,7 @@ TypedArray<String> Container::get_configuration_warnings() const {
TypedArray<String> warnings = Control::get_configuration_warnings();
if (get_class() == "Container" && get_script().is_null()) {
- warnings.push_back(TTR("Container by itself serves no purpose unless a script configures its children placement behavior.\nIf you don't intend to add a script, use a plain Control node instead."));
+ warnings.push_back(RTR("Container by itself serves no purpose unless a script configures its children placement behavior.\nIf you don't intend to add a script, use a plain Control node instead."));
}
return warnings;
diff --git a/scene/gui/control.cpp b/scene/gui/control.cpp
index d2d1b5e9b7..35d1cf1f3e 100644
--- a/scene/gui/control.cpp
+++ b/scene/gui/control.cpp
@@ -367,7 +367,7 @@ bool Control::_get(const StringName &p_name, Variant &r_ret) const {
void Control::_get_property_list(List<PropertyInfo> *p_list) const {
Ref<Theme> theme = Theme::get_default();
- p_list->push_back(PropertyInfo(Variant::NIL, "Theme Overrides", PROPERTY_HINT_NONE, "theme_override_", PROPERTY_USAGE_GROUP));
+ p_list->push_back(PropertyInfo(Variant::NIL, TTRC("Theme Overrides"), PROPERTY_HINT_NONE, "theme_override_", PROPERTY_USAGE_GROUP));
{
List<StringName> names;
@@ -2937,99 +2937,26 @@ Control::MouseFilter Control::get_mouse_filter() const {
return data.mouse_filter;
}
-void Control::warp_mouse(const Point2 &p_to_pos) {
+void Control::warp_mouse(const Point2 &p_position) {
ERR_FAIL_COND(!is_inside_tree());
- get_viewport()->warp_mouse(get_global_transform().xform(p_to_pos));
+ get_viewport()->warp_mouse(get_global_transform_with_canvas().xform(p_position));
}
bool Control::is_text_field() const {
return false;
}
-Array Control::structured_text_parser(StructuredTextParser p_theme_type, const Array &p_args, const String &p_text) const {
- Array ret;
- switch (p_theme_type) {
- case STRUCTURED_TEXT_URI: {
- int prev = 0;
- for (int i = 0; i < p_text.length(); i++) {
- if ((p_text[i] == '\\') || (p_text[i] == '/') || (p_text[i] == '.') || (p_text[i] == ':') || (p_text[i] == '&') || (p_text[i] == '=') || (p_text[i] == '@') || (p_text[i] == '?') || (p_text[i] == '#')) {
- if (prev != i) {
- ret.push_back(Vector2i(prev, i));
- }
- ret.push_back(Vector2i(i, i + 1));
- prev = i + 1;
- }
- }
- if (prev != p_text.length()) {
- ret.push_back(Vector2i(prev, p_text.length()));
- }
- } break;
- case STRUCTURED_TEXT_FILE: {
- int prev = 0;
- for (int i = 0; i < p_text.length(); i++) {
- if ((p_text[i] == '\\') || (p_text[i] == '/') || (p_text[i] == ':')) {
- if (prev != i) {
- ret.push_back(Vector2i(prev, i));
- }
- ret.push_back(Vector2i(i, i + 1));
- prev = i + 1;
- }
- }
- if (prev != p_text.length()) {
- ret.push_back(Vector2i(prev, p_text.length()));
- }
- } break;
- case STRUCTURED_TEXT_EMAIL: {
- bool local = true;
- int prev = 0;
- for (int i = 0; i < p_text.length(); i++) {
- if ((p_text[i] == '@') && local) { // Add full "local" as single context.
- local = false;
- ret.push_back(Vector2i(prev, i));
- ret.push_back(Vector2i(i, i + 1));
- prev = i + 1;
- } else if (!local & (p_text[i] == '.')) { // Add each dot separated "domain" part as context.
- if (prev != i) {
- ret.push_back(Vector2i(prev, i));
- }
- ret.push_back(Vector2i(i, i + 1));
- prev = i + 1;
- }
- }
- if (prev != p_text.length()) {
- ret.push_back(Vector2i(prev, p_text.length()));
- }
- } break;
- case STRUCTURED_TEXT_LIST: {
- if (p_args.size() == 1 && p_args[0].get_type() == Variant::STRING) {
- Vector<String> tags = p_text.split(String(p_args[0]));
- int prev = 0;
- for (int i = 0; i < tags.size(); i++) {
- if (prev != i) {
- ret.push_back(Vector2i(prev, prev + tags[i].length()));
- }
- ret.push_back(Vector2i(prev + tags[i].length(), prev + tags[i].length() + 1));
- prev = prev + tags[i].length() + 1;
- }
- }
- } break;
- case STRUCTURED_TEXT_CUSTOM: {
- Array r;
- if (GDVIRTUAL_CALL(_structured_text_parser, p_args, p_text, r)) {
- for (int i = 0; i < r.size(); i++) {
- if (r[i].get_type() == Variant::VECTOR2I) {
- ret.push_back(Vector2i(r[i]));
- }
- }
- }
- } break;
- case STRUCTURED_TEXT_NONE:
- case STRUCTURED_TEXT_DEFAULT:
- default: {
- ret.push_back(Vector2i(0, p_text.length()));
+Array Control::structured_text_parser(TextServer::StructuredTextParser p_parser_type, const Array &p_args, const String &p_text) const {
+ if (p_parser_type == TextServer::STRUCTURED_TEXT_CUSTOM) {
+ Array ret;
+ if (GDVIRTUAL_CALL(_structured_text_parser, p_args, p_text, ret)) {
+ return ret;
+ } else {
+ return Array();
}
+ } else {
+ return TS->parse_structured_text(p_parser_type, p_args, p_text);
}
- return ret;
}
void Control::set_rotation(real_t p_radians) {
@@ -3141,7 +3068,7 @@ TypedArray<String> Control::get_configuration_warnings() const {
TypedArray<String> warnings = Node::get_configuration_warnings();
if (data.mouse_filter == MOUSE_FILTER_IGNORE && !data.tooltip.is_empty()) {
- warnings.push_back(TTR("The Hint Tooltip won't be displayed as the control's Mouse Filter is set to \"Ignore\". To solve this, set the Mouse Filter to \"Stop\" or \"Pass\"."));
+ warnings.push_back(RTR("The Hint Tooltip won't be displayed as the control's Mouse Filter is set to \"Ignore\". To solve this, set the Mouse Filter to \"Stop\" or \"Pass\"."));
}
return warnings;
@@ -3331,7 +3258,7 @@ void Control::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_drag_preview", "control"), &Control::set_drag_preview);
ClassDB::bind_method(D_METHOD("is_drag_successful"), &Control::is_drag_successful);
- ClassDB::bind_method(D_METHOD("warp_mouse", "to_position"), &Control::warp_mouse);
+ ClassDB::bind_method(D_METHOD("warp_mouse", "position"), &Control::warp_mouse);
ClassDB::bind_method(D_METHOD("update_minimum_size"), &Control::update_minimum_size);
@@ -3491,14 +3418,6 @@ void Control::_bind_methods() {
BIND_ENUM_CONSTANT(TEXT_DIRECTION_LTR);
BIND_ENUM_CONSTANT(TEXT_DIRECTION_RTL);
- BIND_ENUM_CONSTANT(STRUCTURED_TEXT_DEFAULT);
- BIND_ENUM_CONSTANT(STRUCTURED_TEXT_URI);
- BIND_ENUM_CONSTANT(STRUCTURED_TEXT_FILE);
- BIND_ENUM_CONSTANT(STRUCTURED_TEXT_EMAIL);
- BIND_ENUM_CONSTANT(STRUCTURED_TEXT_LIST);
- BIND_ENUM_CONSTANT(STRUCTURED_TEXT_NONE);
- BIND_ENUM_CONSTANT(STRUCTURED_TEXT_CUSTOM);
-
ADD_SIGNAL(MethodInfo("resized"));
ADD_SIGNAL(MethodInfo("gui_input", PropertyInfo(Variant::OBJECT, "event", PROPERTY_HINT_RESOURCE_TYPE, "InputEvent")));
ADD_SIGNAL(MethodInfo("mouse_entered"));
diff --git a/scene/gui/control.h b/scene/gui/control.h
index becb50a118..65b71d74f8 100644
--- a/scene/gui/control.h
+++ b/scene/gui/control.h
@@ -44,7 +44,6 @@ class Panel;
class Control : public CanvasItem {
GDCLASS(Control, CanvasItem);
- OBJ_CATEGORY("GUI Nodes");
public:
enum Anchor {
@@ -148,16 +147,6 @@ public:
TEXT_DIRECTION_INHERITED,
};
- enum StructuredTextParser {
- STRUCTURED_TEXT_DEFAULT,
- STRUCTURED_TEXT_URI,
- STRUCTURED_TEXT_FILE,
- STRUCTURED_TEXT_EMAIL,
- STRUCTURED_TEXT_LIST,
- STRUCTURED_TEXT_NONE,
- STRUCTURED_TEXT_CUSTOM
- };
-
private:
struct CComparator {
bool operator()(const Control *p_a, const Control *p_b) const {
@@ -291,7 +280,7 @@ protected:
//virtual void _window_gui_input(InputEvent p_event);
- virtual Array structured_text_parser(StructuredTextParser p_theme_type, const Array &p_args, const String &p_text) const;
+ virtual Array structured_text_parser(TextServer::StructuredTextParser p_parser_type, const Array &p_args, const String &p_text) const;
bool _set(const StringName &p_name, const Variant &p_value);
bool _get(const StringName &p_name, Variant &r_ret) const;
@@ -548,7 +537,7 @@ public:
void grab_click_focus();
- void warp_mouse(const Point2 &p_to_pos);
+ void warp_mouse(const Point2 &p_position);
virtual bool is_text_field() const;
@@ -580,6 +569,5 @@ VARIANT_ENUM_CAST(Control::Anchor);
VARIANT_ENUM_CAST(Control::LayoutMode);
VARIANT_ENUM_CAST(Control::LayoutDirection);
VARIANT_ENUM_CAST(Control::TextDirection);
-VARIANT_ENUM_CAST(Control::StructuredTextParser);
#endif
diff --git a/scene/gui/dialogs.cpp b/scene/gui/dialogs.cpp
index e3744eedca..0bb96a18a5 100644
--- a/scene/gui/dialogs.cpp
+++ b/scene/gui/dialogs.cpp
@@ -39,13 +39,13 @@
void AcceptDialog::_input_from_window(const Ref<InputEvent> &p_event) {
Ref<InputEventKey> key = p_event;
- if (key.is_valid() && key->is_pressed() && key->get_keycode() == Key::ESCAPE) {
+ if (close_on_escape && key.is_valid() && key->is_pressed() && key->get_keycode() == Key::ESCAPE) {
_cancel_pressed();
}
}
void AcceptDialog::_parent_focused() {
- if (!is_exclusive()) {
+ if (close_on_escape && !is_exclusive()) {
_cancel_pressed();
}
}
@@ -93,6 +93,9 @@ void AcceptDialog::_notification(int p_what) {
}
void AcceptDialog::_text_submitted(const String &p_text) {
+ if (get_ok_button() && get_ok_button()->is_disabled()) {
+ return; // Do not allow submission if OK button is disabled.
+ }
_ok_pressed();
}
@@ -142,6 +145,14 @@ bool AcceptDialog::get_hide_on_ok() const {
return hide_on_ok;
}
+void AcceptDialog::set_close_on_escape(bool p_hide) {
+ close_on_escape = p_hide;
+}
+
+bool AcceptDialog::get_close_on_escape() const {
+ return close_on_escape;
+}
+
void AcceptDialog::set_autowrap(bool p_autowrap) {
label->set_autowrap_mode(p_autowrap ? Label::AUTOWRAP_WORD : Label::AUTOWRAP_OFF);
}
@@ -285,6 +296,8 @@ void AcceptDialog::_bind_methods() {
ClassDB::bind_method(D_METHOD("get_label"), &AcceptDialog::get_label);
ClassDB::bind_method(D_METHOD("set_hide_on_ok", "enabled"), &AcceptDialog::set_hide_on_ok);
ClassDB::bind_method(D_METHOD("get_hide_on_ok"), &AcceptDialog::get_hide_on_ok);
+ ClassDB::bind_method(D_METHOD("set_close_on_escape", "enabled"), &AcceptDialog::set_close_on_escape);
+ ClassDB::bind_method(D_METHOD("get_close_on_escape"), &AcceptDialog::get_close_on_escape);
ClassDB::bind_method(D_METHOD("add_button", "text", "right", "action"), &AcceptDialog::add_button, DEFVAL(false), DEFVAL(""));
ClassDB::bind_method(D_METHOD("add_cancel_button", "name"), &AcceptDialog::add_cancel_button);
ClassDB::bind_method(D_METHOD("remove_button", "button"), &AcceptDialog::remove_button);
@@ -301,6 +314,7 @@ void AcceptDialog::_bind_methods() {
ADD_GROUP("Dialog", "dialog");
ADD_PROPERTY(PropertyInfo(Variant::STRING, "dialog_text", PROPERTY_HINT_MULTILINE_TEXT, "", PROPERTY_USAGE_DEFAULT_INTL), "set_text", "get_text");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "dialog_hide_on_ok"), "set_hide_on_ok", "get_hide_on_ok");
+ ADD_PROPERTY(PropertyInfo(Variant::BOOL, "dialog_close_on_escape"), "set_close_on_escape", "get_close_on_escape");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "dialog_autowrap"), "set_autowrap", "has_autowrap");
}
diff --git a/scene/gui/dialogs.h b/scene/gui/dialogs.h
index 1365b1df24..41fd9c0a10 100644
--- a/scene/gui/dialogs.h
+++ b/scene/gui/dialogs.h
@@ -45,11 +45,12 @@ class AcceptDialog : public Window {
GDCLASS(AcceptDialog, Window);
Window *parent_visible = nullptr;
- Panel *bg;
- HBoxContainer *hbc;
- Label *label;
- Button *ok;
+ Panel *bg = nullptr;
+ HBoxContainer *hbc = nullptr;
+ Label *label = nullptr;
+ Button *ok = nullptr;
bool hide_on_ok = true;
+ bool close_on_escape = true;
void _custom_action(const String &p_action);
void _update_child_rects();
@@ -87,6 +88,9 @@ public:
void set_hide_on_ok(bool p_hide);
bool get_hide_on_ok() const;
+ void set_close_on_escape(bool p_enable);
+ bool get_close_on_escape() const;
+
void set_text(String p_text);
String get_text() const;
@@ -99,7 +103,7 @@ public:
class ConfirmationDialog : public AcceptDialog {
GDCLASS(ConfirmationDialog, AcceptDialog);
- Button *cancel;
+ Button *cancel = nullptr;
protected:
static void _bind_methods();
diff --git a/scene/gui/file_dialog.cpp b/scene/gui/file_dialog.cpp
index e71ab64535..6da5340ca4 100644
--- a/scene/gui/file_dialog.cpp
+++ b/scene/gui/file_dialog.cpp
@@ -95,7 +95,7 @@ void FileDialog::_notification(int p_what) {
switch (p_what) {
case NOTIFICATION_VISIBILITY_CHANGED: {
if (!is_visible()) {
- set_process_unhandled_input(false);
+ set_process_shortcut_input(false);
}
} break;
@@ -119,7 +119,7 @@ void FileDialog::_notification(int p_what) {
}
}
-void FileDialog::unhandled_input(const Ref<InputEvent> &p_event) {
+void FileDialog::shortcut_input(const Ref<InputEvent> &p_event) {
ERR_FAIL_COND(p_event.is_null());
Ref<InputEventKey> k = p_event;
@@ -217,7 +217,7 @@ void FileDialog::_post_popup() {
tree->grab_focus();
}
- set_process_unhandled_input(true);
+ set_process_shortcut_input(true);
// For open dir mode, deselect all items on file dialog open.
if (mode == FILE_MODE_OPEN_DIR) {
@@ -798,7 +798,6 @@ void FileDialog::set_access(Access p_access) {
if (access == p_access) {
return;
}
- memdelete(dir_access);
switch (p_access) {
case ACCESS_FILESYSTEM: {
dir_access = DirAccess::create(DirAccess::ACCESS_FILESYSTEM);
@@ -989,7 +988,7 @@ FileDialog::FileDialog() {
hbc->add_child(drives);
dir = memnew(LineEdit);
- dir->set_structured_text_bidi_override(Control::STRUCTURED_TEXT_FILE);
+ dir->set_structured_text_bidi_override(TextServer::STRUCTURED_TEXT_FILE);
hbc->add_child(dir);
dir->set_h_size_flags(Control::SIZE_EXPAND_FILL);
@@ -1030,7 +1029,7 @@ FileDialog::FileDialog() {
file_box = memnew(HBoxContainer);
file_box->add_child(memnew(Label(TTRC("File:"))));
file = memnew(LineEdit);
- file->set_structured_text_bidi_override(Control::STRUCTURED_TEXT_FILE);
+ file->set_structured_text_bidi_override(TextServer::STRUCTURED_TEXT_FILE);
file->set_stretch_ratio(4);
file->set_h_size_flags(Control::SIZE_EXPAND_FILL);
file_box->add_child(file);
@@ -1064,7 +1063,7 @@ FileDialog::FileDialog() {
makedialog->add_child(makevb);
makedirname = memnew(LineEdit);
- makedirname->set_structured_text_bidi_override(Control::STRUCTURED_TEXT_FILE);
+ makedirname->set_structured_text_bidi_override(TextServer::STRUCTURED_TEXT_FILE);
makevb->add_margin_child(TTRC("Name:"), makedirname);
add_child(makedialog, false, INTERNAL_MODE_FRONT);
makedialog->register_text_enter(makedirname);
@@ -1091,5 +1090,4 @@ FileDialog::~FileDialog() {
if (unregister_func) {
unregister_func(this);
}
- memdelete(dir_access);
}
diff --git a/scene/gui/file_dialog.h b/scene/gui/file_dialog.h
index 7a50efe40f..2e326d2949 100644
--- a/scene/gui/file_dialog.h
+++ b/scene/gui/file_dialog.h
@@ -65,34 +65,34 @@ public:
static RegisterFunc unregister_func;
private:
- ConfirmationDialog *makedialog;
- LineEdit *makedirname;
+ ConfirmationDialog *makedialog = nullptr;
+ LineEdit *makedirname = nullptr;
- Button *makedir;
+ Button *makedir = nullptr;
Access access = ACCESS_RESOURCES;
- VBoxContainer *vbox;
+ VBoxContainer *vbox = nullptr;
FileMode mode;
- LineEdit *dir;
- HBoxContainer *drives_container;
- HBoxContainer *shortcuts_container;
- OptionButton *drives;
- Tree *tree;
- HBoxContainer *file_box;
- LineEdit *file;
- OptionButton *filter;
- AcceptDialog *mkdirerr;
- AcceptDialog *exterr;
- DirAccess *dir_access;
- ConfirmationDialog *confirm_save;
-
- Label *message;
-
- Button *dir_prev;
- Button *dir_next;
- Button *dir_up;
-
- Button *refresh;
- Button *show_hidden;
+ LineEdit *dir = nullptr;
+ HBoxContainer *drives_container = nullptr;
+ HBoxContainer *shortcuts_container = nullptr;
+ OptionButton *drives = nullptr;
+ Tree *tree = nullptr;
+ HBoxContainer *file_box = nullptr;
+ LineEdit *file = nullptr;
+ OptionButton *filter = nullptr;
+ AcceptDialog *mkdirerr = nullptr;
+ AcceptDialog *exterr = nullptr;
+ Ref<DirAccess> dir_access;
+ ConfirmationDialog *confirm_save = nullptr;
+
+ Label *message = nullptr;
+
+ Button *dir_prev = nullptr;
+ Button *dir_next = nullptr;
+ Button *dir_up = nullptr;
+
+ Button *refresh = nullptr;
+ Button *show_hidden = nullptr;
Vector<String> filters;
@@ -133,7 +133,7 @@ private:
void _update_drives(bool p_select = true);
- virtual void unhandled_input(const Ref<InputEvent> &p_event) override;
+ virtual void shortcut_input(const Ref<InputEvent> &p_event) override;
bool _is_open_should_be_disabled();
diff --git a/scene/gui/flow_container.cpp b/scene/gui/flow_container.cpp
index 40aca555db..1e5863b845 100644
--- a/scene/gui/flow_container.cpp
+++ b/scene/gui/flow_container.cpp
@@ -44,8 +44,8 @@ void FlowContainer::_resort() {
return;
}
- int separation_horizontal = get_theme_constant(SNAME("hseparation"));
- int separation_vertical = get_theme_constant(SNAME("vseparation"));
+ int separation_horizontal = get_theme_constant(SNAME("h_separation"));
+ int separation_vertical = get_theme_constant(SNAME("v_separation"));
bool rtl = is_layout_rtl();
diff --git a/scene/gui/gradient_edit.h b/scene/gui/gradient_edit.h
index 67531d4f4a..4e3c6525f9 100644
--- a/scene/gui/gradient_edit.h
+++ b/scene/gui/gradient_edit.h
@@ -38,8 +38,8 @@
class GradientEdit : public Control {
GDCLASS(GradientEdit, Control);
- PopupPanel *popup;
- ColorPicker *picker;
+ PopupPanel *popup = nullptr;
+ ColorPicker *picker = nullptr;
bool grabbing = false;
int grabbed = -1;
diff --git a/scene/gui/graph_edit.cpp b/scene/gui/graph_edit.cpp
index 1394b4192f..f2b724fa39 100644
--- a/scene/gui/graph_edit.cpp
+++ b/scene/gui/graph_edit.cpp
@@ -696,7 +696,7 @@ void GraphEdit::_top_layer_input(const Ref<InputEvent> &p_ev) {
for (int j = 0; j < gn->get_connection_output_count(); j++) {
Vector2 pos = gn->get_connection_output_position(j) + gn->get_position();
int type = gn->get_connection_output_type(j);
- if ((type == connecting_type || valid_connection_types.has(ConnType(type, connecting_type))) && is_in_output_hotzone(gn, j, mpos, port_size)) {
+ if ((type == connecting_type || valid_connection_types.has(ConnType(connecting_type, type))) && is_in_output_hotzone(gn, j, mpos, port_size)) {
connecting_target = true;
connecting_to = pos;
connecting_target_to = gn->get_name();
@@ -708,7 +708,7 @@ void GraphEdit::_top_layer_input(const Ref<InputEvent> &p_ev) {
for (int j = 0; j < gn->get_connection_input_count(); j++) {
Vector2 pos = gn->get_connection_input_position(j) + gn->get_position();
int type = gn->get_connection_input_type(j);
- if ((type == connecting_type || valid_connection_types.has(ConnType(type, connecting_type))) && is_in_input_hotzone(gn, j, mpos, port_size)) {
+ if ((type == connecting_type || valid_connection_types.has(ConnType(connecting_type, type))) && is_in_input_hotzone(gn, j, mpos, port_size)) {
connecting_target = true;
connecting_to = pos;
connecting_target_to = gn->get_name();
@@ -981,7 +981,7 @@ void GraphEdit::_minimap_draw() {
Ref<StyleBoxFlat> sb_minimap = minimap->get_theme_stylebox(SNAME("node"))->duplicate();
// Override default values with colors provided by the GraphNode's stylebox, if possible.
- Ref<StyleBoxFlat> sbf = gn->get_theme_stylebox(gn->is_selected() ? "commentfocus" : "comment");
+ Ref<StyleBoxFlat> sbf = gn->get_theme_stylebox(gn->is_selected() ? "comment_focus" : "comment");
if (sbf.is_valid()) {
Color node_color = sbf->get_bg_color();
sb_minimap->set_bg_color(node_color);
@@ -1004,7 +1004,7 @@ void GraphEdit::_minimap_draw() {
Ref<StyleBoxFlat> sb_minimap = minimap->get_theme_stylebox(SNAME("node"))->duplicate();
// Override default values with colors provided by the GraphNode's stylebox, if possible.
- Ref<StyleBoxFlat> sbf = gn->get_theme_stylebox(gn->is_selected() ? "selectedframe" : "frame");
+ Ref<StyleBoxFlat> sbf = gn->get_theme_stylebox(gn->is_selected() ? "selected_frame" : "frame");
if (sbf.is_valid()) {
Color node_color = sbf->get_border_color();
sb_minimap->set_bg_color(node_color);
@@ -1568,26 +1568,17 @@ void GraphEdit::_update_zoom_label() {
}
void GraphEdit::add_valid_connection_type(int p_type, int p_with_type) {
- ConnType ct;
- ct.type_a = p_type;
- ct.type_b = p_with_type;
-
+ ConnType ct(p_type, p_with_type);
valid_connection_types.insert(ct);
}
void GraphEdit::remove_valid_connection_type(int p_type, int p_with_type) {
- ConnType ct;
- ct.type_a = p_type;
- ct.type_b = p_with_type;
-
+ ConnType ct(p_type, p_with_type);
valid_connection_types.erase(ct);
}
bool GraphEdit::is_valid_connection_type(int p_type, int p_with_type) const {
- ConnType ct;
- ct.type_a = p_type;
- ct.type_b = p_with_type;
-
+ ConnType ct(p_type, p_with_type);
return valid_connection_types.has(ct);
}
@@ -1646,6 +1637,7 @@ float GraphEdit::get_minimap_opacity() const {
void GraphEdit::set_minimap_enabled(bool p_enable) {
minimap_button->set_pressed(p_enable);
+ _minimap_toggled();
minimap->update();
}
@@ -2202,7 +2194,7 @@ void GraphEdit::_bind_methods() {
ClassDB::bind_method(D_METHOD("clear_connections"), &GraphEdit::clear_connections);
ClassDB::bind_method(D_METHOD("force_connection_drag_end"), &GraphEdit::force_connection_drag_end);
ClassDB::bind_method(D_METHOD("get_scroll_ofs"), &GraphEdit::get_scroll_ofs);
- ClassDB::bind_method(D_METHOD("set_scroll_ofs", "ofs"), &GraphEdit::set_scroll_ofs);
+ ClassDB::bind_method(D_METHOD("set_scroll_ofs", "offset"), &GraphEdit::set_scroll_ofs);
ClassDB::bind_method(D_METHOD("add_valid_right_disconnect_type", "type"), &GraphEdit::add_valid_right_disconnect_type);
ClassDB::bind_method(D_METHOD("remove_valid_right_disconnect_type", "type"), &GraphEdit::remove_valid_right_disconnect_type);
@@ -2301,7 +2293,7 @@ void GraphEdit::_bind_methods() {
ADD_SIGNAL(MethodInfo("delete_nodes_request"));
ADD_SIGNAL(MethodInfo("begin_node_move"));
ADD_SIGNAL(MethodInfo("end_node_move"));
- ADD_SIGNAL(MethodInfo("scroll_offset_changed", PropertyInfo(Variant::VECTOR2, "ofs")));
+ ADD_SIGNAL(MethodInfo("scroll_offset_changed", PropertyInfo(Variant::VECTOR2, "offset")));
ADD_SIGNAL(MethodInfo("connection_drag_started", PropertyInfo(Variant::STRING, "from"), PropertyInfo(Variant::STRING, "slot"), PropertyInfo(Variant::BOOL, "is_output")));
ADD_SIGNAL(MethodInfo("connection_drag_ended"));
diff --git a/scene/gui/graph_edit.h b/scene/gui/graph_edit.h
index b0d1944d6e..18b9eeebd4 100644
--- a/scene/gui/graph_edit.h
+++ b/scene/gui/graph_edit.h
@@ -46,7 +46,7 @@ class GraphEditFilter : public Control {
friend class GraphEdit;
friend class GraphEditMinimap;
- GraphEdit *ge;
+ GraphEdit *ge = nullptr;
virtual bool has_point(const Point2 &p_point) const override;
public:
@@ -58,7 +58,7 @@ class GraphEditMinimap : public Control {
friend class GraphEdit;
friend class GraphEditFilter;
- GraphEdit *ge;
+ GraphEdit *ge = nullptr;
protected:
public:
@@ -109,20 +109,20 @@ public:
};
private:
- Label *zoom_label;
- Button *zoom_minus;
- Button *zoom_reset;
- Button *zoom_plus;
+ Label *zoom_label = nullptr;
+ Button *zoom_minus = nullptr;
+ Button *zoom_reset = nullptr;
+ Button *zoom_plus = nullptr;
- Button *snap_button;
- SpinBox *snap_amount;
+ Button *snap_button = nullptr;
+ SpinBox *snap_amount = nullptr;
- Button *minimap_button;
+ Button *minimap_button = nullptr;
- Button *layout_button;
+ Button *layout_button = nullptr;
- HScrollBar *h_scroll;
- VScrollBar *v_scroll;
+ HScrollBar *h_scroll = nullptr;
+ VScrollBar *v_scroll = nullptr;
float port_grab_distance_horizontal = 0.0;
float port_grab_distance_vertical;
@@ -190,9 +190,9 @@ private:
void _scroll_moved(double);
virtual void gui_input(const Ref<InputEvent> &p_ev) override;
- Control *connections_layer;
- GraphEditFilter *top_layer;
- GraphEditMinimap *minimap;
+ Control *connections_layer = nullptr;
+ GraphEditFilter *top_layer = nullptr;
+ GraphEditMinimap *minimap = nullptr;
void _top_layer_input(const Ref<InputEvent> &p_ev);
bool is_in_input_hotzone(GraphNode *p_graph_node, int p_slot_index, const Vector2 &p_mouse_pos, const Vector2i &p_port_size);
@@ -236,7 +236,7 @@ private:
void _set_drag_comment_enclosed_nodes(GraphNode *p_node, HashMap<StringName, Vector<GraphNode *>> &p_comment_enclosed_nodes, bool p_drag);
void _set_position_of_comment_enclosed_nodes(GraphNode *p_node, HashMap<StringName, Vector<GraphNode *>> &p_comment_enclosed_nodes, Vector2 p_pos);
- HBoxContainer *zoom_hb;
+ HBoxContainer *zoom_hb = nullptr;
friend class GraphEditFilter;
bool _filter_input(const Point2 &p_point);
diff --git a/scene/gui/graph_node.cpp b/scene/gui/graph_node.cpp
index ef0ac75cb4..e3ecd17ed8 100644
--- a/scene/gui/graph_node.cpp
+++ b/scene/gui/graph_node.cpp
@@ -351,10 +351,10 @@ void GraphNode::_notification(int p_what) {
Ref<StyleBox> sb;
if (comment) {
- sb = get_theme_stylebox(selected ? "commentfocus" : "comment");
+ sb = get_theme_stylebox(selected ? "comment_focus" : "comment");
} else {
- sb = get_theme_stylebox(selected ? "selectedframe" : "frame");
+ sb = get_theme_stylebox(selected ? "selected_frame" : "frame");
}
//sb=sb->duplicate();
@@ -393,7 +393,6 @@ void GraphNode::_notification(int p_what) {
w -= close->get_width();
}
- title_buf->set_width(w);
title_buf->draw(get_canvas_item(), Point2(sb->get_margin(SIDE_LEFT) + title_h_offset, -title_buf->get_size().y + title_offset), title_color);
if (show_close) {
Vector2 cpos = Point2(w + sb->get_margin(SIDE_LEFT) + close_h_offset, -close->get_height() + close_offset);
diff --git a/scene/gui/grid_container.cpp b/scene/gui/grid_container.cpp
index 3c1f4bb93b..b58bb4d74a 100644
--- a/scene/gui/grid_container.cpp
+++ b/scene/gui/grid_container.cpp
@@ -38,8 +38,8 @@ void GridContainer::_notification(int p_what) {
Set<int> col_expanded; // Columns which have the SIZE_EXPAND flag set.
Set<int> row_expanded; // Rows which have the SIZE_EXPAND flag set.
- int hsep = get_theme_constant(SNAME("hseparation"));
- int vsep = get_theme_constant(SNAME("vseparation"));
+ int hsep = get_theme_constant(SNAME("h_separation"));
+ int vsep = get_theme_constant(SNAME("v_separation"));
int max_col = MIN(get_child_count(), columns);
int max_row = ceil((float)get_child_count() / (float)columns);
@@ -50,6 +50,9 @@ void GridContainer::_notification(int p_what) {
if (!c || !c->is_visible_in_tree()) {
continue;
}
+ if (c->is_set_as_top_level()) {
+ continue;
+ }
int row = valid_controls_index / columns;
int col = valid_controls_index % columns;
@@ -214,8 +217,8 @@ Size2 GridContainer::get_minimum_size() const {
Map<int, int> col_minw;
Map<int, int> row_minh;
- int hsep = get_theme_constant(SNAME("hseparation"));
- int vsep = get_theme_constant(SNAME("vseparation"));
+ int hsep = get_theme_constant(SNAME("h_separation"));
+ int vsep = get_theme_constant(SNAME("v_separation"));
int max_row = 0;
int max_col = 0;
diff --git a/scene/gui/item_list.cpp b/scene/gui/item_list.cpp
index 8c0f696a9f..05a5ac75d1 100644
--- a/scene/gui/item_list.cpp
+++ b/scene/gui/item_list.cpp
@@ -585,6 +585,9 @@ Size2 ItemList::Item::get_icon_size() const {
void ItemList::gui_input(const Ref<InputEvent> &p_event) {
ERR_FAIL_COND(p_event.is_null());
+#define CAN_SELECT(i) (items[i].selectable && !items[i].disabled)
+#define IS_SAME_ROW(i, row) (i / current_columns == row)
+
double prev_scroll = scroll_bar->get_value();
Ref<InputEventMouseMotion> mm = p_event;
@@ -642,6 +645,9 @@ void ItemList::gui_input(const Ref<InputEvent> &p_event) {
SWAP(from, to);
}
for (int j = from; j <= to; j++) {
+ if (!CAN_SELECT(j)) {
+ continue;
+ }
bool selected = !items[j].selected;
select(j, false);
if (selected) {
@@ -650,6 +656,9 @@ void ItemList::gui_input(const Ref<InputEvent> &p_event) {
}
if (mb->get_button_index() == MouseButton::RIGHT) {
+ if (!CAN_SELECT(i)) {
+ return;
+ }
emit_signal(SNAME("item_rmb_selected"), i, get_local_mouse_position());
}
} else {
@@ -659,8 +668,15 @@ void ItemList::gui_input(const Ref<InputEvent> &p_event) {
}
if (items[i].selected && mb->get_button_index() == MouseButton::RIGHT) {
+ if (!CAN_SELECT(i)) {
+ return;
+ }
emit_signal(SNAME("item_rmb_selected"), i, get_local_mouse_position());
} else {
+ if (!CAN_SELECT(i)) {
+ return;
+ }
+
bool selected = items[i].selected;
select(i, select_mode == SELECT_SINGLE || !mb->is_command_pressed());
@@ -707,7 +723,7 @@ void ItemList::gui_input(const Ref<InputEvent> &p_event) {
if (diff < uint64_t(ProjectSettings::get_singleton()->get("gui/timers/incremental_search_max_interval_msec")) * 2) {
for (int i = current - 1; i >= 0; i--) {
- if (items[i].text.begins_with(search_string)) {
+ if (CAN_SELECT(i) && items[i].text.begins_with(search_string)) {
set_current(i);
ensure_current_is_visible();
if (select_mode == SELECT_SINGLE) {
@@ -723,7 +739,15 @@ void ItemList::gui_input(const Ref<InputEvent> &p_event) {
}
if (current >= current_columns) {
- set_current(current - current_columns);
+ int next = current - current_columns;
+ while (next >= 0 && !CAN_SELECT(next)) {
+ next = next - current_columns;
+ }
+ if (next < 0) {
+ accept_event();
+ return;
+ }
+ set_current(next);
ensure_current_is_visible();
if (select_mode == SELECT_SINGLE) {
emit_signal(SNAME("item_selected"), current);
@@ -737,7 +761,7 @@ void ItemList::gui_input(const Ref<InputEvent> &p_event) {
if (diff < uint64_t(ProjectSettings::get_singleton()->get("gui/timers/incremental_search_max_interval_msec")) * 2) {
for (int i = current + 1; i < items.size(); i++) {
- if (items[i].text.begins_with(search_string)) {
+ if (CAN_SELECT(i) && items[i].text.begins_with(search_string)) {
set_current(i);
ensure_current_is_visible();
if (select_mode == SELECT_SINGLE) {
@@ -752,7 +776,15 @@ void ItemList::gui_input(const Ref<InputEvent> &p_event) {
}
if (current < items.size() - current_columns) {
- set_current(current + current_columns);
+ int next = current + current_columns;
+ while (next < items.size() && !CAN_SELECT(next)) {
+ next = next + current_columns;
+ }
+ if (next >= items.size()) {
+ accept_event();
+ return;
+ }
+ set_current(next);
ensure_current_is_visible();
if (select_mode == SELECT_SINGLE) {
emit_signal(SNAME("item_selected"), current);
@@ -763,7 +795,7 @@ void ItemList::gui_input(const Ref<InputEvent> &p_event) {
search_string = ""; //any mousepress cancels
for (int i = 4; i > 0; i--) {
- if (current - current_columns * i >= 0) {
+ if (current - current_columns * i >= 0 && CAN_SELECT(current - current_columns * i)) {
set_current(current - current_columns * i);
ensure_current_is_visible();
if (select_mode == SELECT_SINGLE) {
@@ -777,7 +809,7 @@ void ItemList::gui_input(const Ref<InputEvent> &p_event) {
search_string = ""; //any mousepress cancels
for (int i = 4; i > 0; i--) {
- if (current + current_columns * i < items.size()) {
+ if (current + current_columns * i < items.size() && CAN_SELECT(current + current_columns * i)) {
set_current(current + current_columns * i);
ensure_current_is_visible();
if (select_mode == SELECT_SINGLE) {
@@ -792,7 +824,16 @@ void ItemList::gui_input(const Ref<InputEvent> &p_event) {
search_string = ""; //any mousepress cancels
if (current % current_columns != 0) {
- set_current(current - 1);
+ int current_row = current / current_columns;
+ int next = current - 1;
+ while (!CAN_SELECT(next)) {
+ next = next - 1;
+ }
+ if (next < 0 || !IS_SAME_ROW(next, current_row)) {
+ accept_event();
+ return;
+ }
+ set_current(next);
ensure_current_is_visible();
if (select_mode == SELECT_SINGLE) {
emit_signal(SNAME("item_selected"), current);
@@ -803,7 +844,16 @@ void ItemList::gui_input(const Ref<InputEvent> &p_event) {
search_string = ""; //any mousepress cancels
if (current % current_columns != (current_columns - 1) && current + 1 < items.size()) {
- set_current(current + 1);
+ int current_row = current / current_columns;
+ int next = current + 1;
+ while (!CAN_SELECT(next)) {
+ next = next + 1;
+ }
+ if (items.size() <= next || !IS_SAME_ROW(next, current_row)) {
+ accept_event();
+ return;
+ }
+ set_current(next);
ensure_current_is_visible();
if (select_mode == SELECT_SINGLE) {
emit_signal(SNAME("item_selected"), current);
@@ -879,6 +929,9 @@ void ItemList::gui_input(const Ref<InputEvent> &p_event) {
if (scroll_bar->get_value() != prev_scroll) {
accept_event(); //accept event if scroll changed
}
+
+#undef CAN_SELECT
+#undef IS_SAME_ROW
}
void ItemList::ensure_current_is_visible() {
@@ -937,8 +990,8 @@ void ItemList::_notification(int p_what) {
draw_style_box(bg, Rect2(Point2(), size));
- int hseparation = get_theme_constant(SNAME("hseparation"));
- int vseparation = get_theme_constant(SNAME("vseparation"));
+ int hseparation = get_theme_constant(SNAME("h_separation"));
+ int vseparation = get_theme_constant(SNAME("v_separation"));
int icon_margin = get_theme_constant(SNAME("icon_margin"));
int line_separation = get_theme_constant(SNAME("line_separation"));
Color font_outline_color = get_theme_color(SNAME("font_outline_color"));
diff --git a/scene/gui/item_list.h b/scene/gui/item_list.h
index 96735678c1..ffbe7d055a 100644
--- a/scene/gui/item_list.h
+++ b/scene/gui/item_list.h
@@ -98,7 +98,7 @@ private:
SelectMode select_mode = SELECT_SINGLE;
IconMode icon_mode = ICON_MODE_LEFT;
- VScrollBar *scroll_bar;
+ VScrollBar *scroll_bar = nullptr;
TextParagraph::OverrunBehavior text_overrun_behavior = TextParagraph::OVERRUN_TRIM_ELLIPSIS;
uint64_t search_time_msec = 0;
diff --git a/scene/gui/label.cpp b/scene/gui/label.cpp
index cd6fc168c2..eda3d40f63 100644
--- a/scene/gui/label.cpp
+++ b/scene/gui/label.cpp
@@ -648,21 +648,21 @@ void Label::set_text_direction(Control::TextDirection p_text_direction) {
}
}
-void Label::set_structured_text_bidi_override(Control::StructuredTextParser p_parser) {
+void Label::set_structured_text_bidi_override(TextServer::StructuredTextParser p_parser) {
if (st_parser != p_parser) {
st_parser = p_parser;
- font_dirty = true;
+ dirty = true;
update();
}
}
-Control::StructuredTextParser Label::get_structured_text_bidi_override() const {
+TextServer::StructuredTextParser Label::get_structured_text_bidi_override() const {
return st_parser;
}
void Label::set_structured_text_bidi_override_options(Array p_args) {
st_args = p_args;
- font_dirty = true;
+ dirty = true;
update();
}
diff --git a/scene/gui/label.h b/scene/gui/label.h
index 0b931b3084..f7b725928f 100644
--- a/scene/gui/label.h
+++ b/scene/gui/label.h
@@ -80,7 +80,7 @@ private:
Dictionary opentype_features;
String language;
TextDirection text_direction = TEXT_DIRECTION_AUTO;
- Control::StructuredTextParser st_parser = STRUCTURED_TEXT_DEFAULT;
+ TextServer::StructuredTextParser st_parser = TextServer::STRUCTURED_TEXT_DEFAULT;
Array st_args;
float percent_visible = 1.0;
@@ -124,8 +124,8 @@ public:
void set_language(const String &p_language);
String get_language() const;
- void set_structured_text_bidi_override(Control::StructuredTextParser p_parser);
- Control::StructuredTextParser get_structured_text_bidi_override() const;
+ void set_structured_text_bidi_override(TextServer::StructuredTextParser p_parser);
+ TextServer::StructuredTextParser get_structured_text_bidi_override() const;
void set_structured_text_bidi_override_options(Array p_args);
Array get_structured_text_bidi_override_options() const;
diff --git a/scene/gui/line_edit.cpp b/scene/gui/line_edit.cpp
index e063d3aeba..e5b58a7cc8 100644
--- a/scene/gui/line_edit.cpp
+++ b/scene/gui/line_edit.cpp
@@ -215,6 +215,27 @@ void LineEdit::_delete(bool p_word, bool p_all_to_right) {
}
}
+void LineEdit::unhandled_key_input(const Ref<InputEvent> &p_event) {
+ Ref<InputEventKey> k = p_event;
+
+ if (k.is_valid()) {
+ if (!k->is_pressed()) {
+ return;
+ }
+ // Handle Unicode (with modifiers active, process after shortcuts).
+ if (has_focus() && editable && (k->get_unicode() >= 32)) {
+ selection_delete();
+ char32_t ucodestr[2] = { (char32_t)k->get_unicode(), 0 };
+ int prev_len = text.length();
+ insert_text_at_caret(ucodestr);
+ if (text.length() != prev_len) {
+ _text_changed();
+ }
+ accept_event();
+ }
+ }
+}
+
void LineEdit::gui_input(const Ref<InputEvent> &p_event) {
ERR_FAIL_COND(p_event.is_null());
@@ -1431,7 +1452,7 @@ bool LineEdit::get_draw_control_chars() const {
return draw_control_chars;
}
-void LineEdit::set_structured_text_bidi_override(Control::StructuredTextParser p_parser) {
+void LineEdit::set_structured_text_bidi_override(TextServer::StructuredTextParser p_parser) {
if (st_parser != p_parser) {
st_parser = p_parser;
_shape();
@@ -1439,7 +1460,7 @@ void LineEdit::set_structured_text_bidi_override(Control::StructuredTextParser p
}
}
-Control::StructuredTextParser LineEdit::get_structured_text_bidi_override() const {
+TextServer::StructuredTextParser LineEdit::get_structured_text_bidi_override() const {
return st_parser;
}
@@ -2445,6 +2466,7 @@ LineEdit::LineEdit(const String &p_placeholder) {
set_focus_mode(FOCUS_ALL);
set_default_cursor_shape(CURSOR_IBEAM);
set_mouse_filter(MOUSE_FILTER_STOP);
+ set_process_unhandled_key_input(true);
caret_blink_timer = memnew(Timer);
add_child(caret_blink_timer, false, INTERNAL_MODE_FRONT);
diff --git a/scene/gui/line_edit.h b/scene/gui/line_edit.h
index 444c9a1c50..50aa2f4460 100644
--- a/scene/gui/line_edit.h
+++ b/scene/gui/line_edit.h
@@ -106,7 +106,7 @@ private:
String language;
TextDirection text_direction = TEXT_DIRECTION_AUTO;
TextDirection input_direction = TEXT_DIRECTION_LTR;
- Control::StructuredTextParser st_parser = STRUCTURED_TEXT_DEFAULT;
+ TextServer::StructuredTextParser st_parser = TextServer::STRUCTURED_TEXT_DEFAULT;
Array st_args;
bool draw_control_chars = false;
@@ -202,6 +202,7 @@ private:
protected:
void _notification(int p_what);
static void _bind_methods();
+ virtual void unhandled_key_input(const Ref<InputEvent> &p_event) override;
virtual void gui_input(const Ref<InputEvent> &p_event) override;
bool _set(const StringName &p_name, const Variant &p_value);
@@ -252,8 +253,8 @@ public:
void set_draw_control_chars(bool p_draw_control_chars);
bool get_draw_control_chars() const;
- void set_structured_text_bidi_override(Control::StructuredTextParser p_parser);
- Control::StructuredTextParser get_structured_text_bidi_override() const;
+ void set_structured_text_bidi_override(TextServer::StructuredTextParser p_parser);
+ TextServer::StructuredTextParser get_structured_text_bidi_override() const;
void set_structured_text_bidi_override_options(Array p_args);
Array get_structured_text_bidi_override_options() const;
diff --git a/scene/gui/link_button.cpp b/scene/gui/link_button.cpp
index dc4f09d22d..dca6437519 100644
--- a/scene/gui/link_button.cpp
+++ b/scene/gui/link_button.cpp
@@ -61,7 +61,7 @@ String LinkButton::get_text() const {
return text;
}
-void LinkButton::set_structured_text_bidi_override(Control::StructuredTextParser p_parser) {
+void LinkButton::set_structured_text_bidi_override(TextServer::StructuredTextParser p_parser) {
if (st_parser != p_parser) {
st_parser = p_parser;
_shape();
@@ -69,7 +69,7 @@ void LinkButton::set_structured_text_bidi_override(Control::StructuredTextParser
}
}
-Control::StructuredTextParser LinkButton::get_structured_text_bidi_override() const {
+TextServer::StructuredTextParser LinkButton::get_structured_text_bidi_override() const {
return st_parser;
}
diff --git a/scene/gui/link_button.h b/scene/gui/link_button.h
index f996558f32..6d2dcbde84 100644
--- a/scene/gui/link_button.h
+++ b/scene/gui/link_button.h
@@ -53,7 +53,7 @@ private:
Dictionary opentype_features;
String language;
TextDirection text_direction = TEXT_DIRECTION_AUTO;
- Control::StructuredTextParser st_parser = STRUCTURED_TEXT_DEFAULT;
+ TextServer::StructuredTextParser st_parser = TextServer::STRUCTURED_TEXT_DEFAULT;
Array st_args;
void _shape();
@@ -71,8 +71,8 @@ public:
void set_text(const String &p_text);
String get_text() const;
- void set_structured_text_bidi_override(Control::StructuredTextParser p_parser);
- Control::StructuredTextParser get_structured_text_bidi_override() const;
+ void set_structured_text_bidi_override(TextServer::StructuredTextParser p_parser);
+ TextServer::StructuredTextParser get_structured_text_bidi_override() const;
void set_structured_text_bidi_override_options(Array p_args);
Array get_structured_text_bidi_override_options() const;
diff --git a/scene/gui/menu_button.cpp b/scene/gui/menu_button.cpp
index 7e724e4d71..1feee017c2 100644
--- a/scene/gui/menu_button.cpp
+++ b/scene/gui/menu_button.cpp
@@ -33,7 +33,7 @@
#include "core/os/keyboard.h"
#include "scene/main/window.h"
-void MenuButton::unhandled_key_input(const Ref<InputEvent> &p_event) {
+void MenuButton::shortcut_input(const Ref<InputEvent> &p_event) {
ERR_FAIL_COND(p_event.is_null());
if (!_is_focus_owner_in_shortcut_context()) {
@@ -232,7 +232,7 @@ MenuButton::MenuButton(const String &p_text) :
set_flat(true);
set_toggle_mode(true);
set_disable_shortcuts(false);
- set_process_unhandled_key_input(true);
+ set_process_shortcut_input(true);
set_focus_mode(FOCUS_NONE);
set_action_mode(ACTION_MODE_BUTTON_PRESS);
diff --git a/scene/gui/menu_button.h b/scene/gui/menu_button.h
index 9cfb780255..0a6b46c796 100644
--- a/scene/gui/menu_button.h
+++ b/scene/gui/menu_button.h
@@ -40,7 +40,7 @@ class MenuButton : public Button {
bool clicked = false;
bool switch_on_hover = false;
bool disable_shortcuts = false;
- PopupMenu *popup;
+ PopupMenu *popup = nullptr;
Vector2i mouse_pos_adjusted;
@@ -54,7 +54,7 @@ protected:
bool _get(const StringName &p_name, Variant &r_ret) const;
void _get_property_list(List<PropertyInfo> *p_list) const;
static void _bind_methods();
- virtual void unhandled_key_input(const Ref<InputEvent> &p_event) override;
+ virtual void shortcut_input(const Ref<InputEvent> &p_event) override;
public:
virtual void pressed() override;
diff --git a/scene/gui/option_button.cpp b/scene/gui/option_button.cpp
index 1e8a149e11..4b79d79846 100644
--- a/scene/gui/option_button.cpp
+++ b/scene/gui/option_button.cpp
@@ -42,7 +42,7 @@ Size2 OptionButton::get_minimum_size() const {
const Size2 arrow_size = Control::get_theme_icon(SNAME("arrow"))->get_size();
Size2 content_size = minsize - padding;
- content_size.width += arrow_size.width + get_theme_constant(SNAME("hseparation"));
+ content_size.width += arrow_size.width + get_theme_constant(SNAME("h_separation"));
content_size.height = MAX(content_size.height, arrow_size.height);
minsize = content_size + padding;
@@ -203,16 +203,18 @@ void OptionButton::pressed() {
}
void OptionButton::add_icon_item(const Ref<Texture2D> &p_icon, const String &p_label, int p_id) {
+ bool first_selectable = !has_selectable_items();
popup->add_icon_radio_check_item(p_icon, p_label, p_id);
- if (popup->get_item_count() == 1) {
- select(0);
+ if (first_selectable) {
+ select(get_item_count() - 1);
}
}
void OptionButton::add_item(const String &p_label, int p_id) {
+ bool first_selectable = !has_selectable_items();
popup->add_radio_check_item(p_label, p_id);
- if (popup->get_item_count() == 1) {
- select(0);
+ if (first_selectable) {
+ select(get_item_count() - 1);
}
}
@@ -280,6 +282,9 @@ bool OptionButton::is_item_disabled(int p_idx) const {
return popup->is_item_disabled(p_idx);
}
+bool OptionButton::is_item_separator(int p_idx) const {
+ return popup->is_item_separator(p_idx);
+}
void OptionButton::set_item_count(int p_count) {
ERR_FAIL_COND(p_count < 0);
@@ -299,12 +304,37 @@ void OptionButton::set_item_count(int p_count) {
notify_property_list_changed();
}
+bool OptionButton::has_selectable_items() const {
+ for (int i = 0; i < get_item_count(); i++) {
+ if (!is_item_disabled(i) && !is_item_separator(i)) {
+ return true;
+ }
+ }
+ return false;
+}
+int OptionButton::get_selectable_item(bool p_from_last) const {
+ if (!p_from_last) {
+ for (int i = 0; i < get_item_count(); i++) {
+ if (!is_item_disabled(i) && !is_item_separator(i)) {
+ return i;
+ }
+ }
+ } else {
+ for (int i = get_item_count() - 1; i >= 0; i++) {
+ if (!is_item_disabled(i) && !is_item_separator(i)) {
+ return i;
+ }
+ }
+ }
+ return -1;
+}
+
int OptionButton::get_item_count() const {
return popup->get_item_count();
}
-void OptionButton::add_separator() {
- popup->add_separator();
+void OptionButton::add_separator(const String &p_text) {
+ popup->add_separator(p_text);
}
void OptionButton::clear() {
@@ -407,7 +437,8 @@ void OptionButton::_bind_methods() {
ClassDB::bind_method(D_METHOD("get_item_metadata", "idx"), &OptionButton::get_item_metadata);
ClassDB::bind_method(D_METHOD("get_item_tooltip", "idx"), &OptionButton::get_item_tooltip);
ClassDB::bind_method(D_METHOD("is_item_disabled", "idx"), &OptionButton::is_item_disabled);
- ClassDB::bind_method(D_METHOD("add_separator"), &OptionButton::add_separator);
+ ClassDB::bind_method(D_METHOD("is_item_separator", "idx"), &OptionButton::is_item_separator);
+ ClassDB::bind_method(D_METHOD("add_separator", "text"), &OptionButton::add_separator, DEFVAL(String()));
ClassDB::bind_method(D_METHOD("clear"), &OptionButton::clear);
ClassDB::bind_method(D_METHOD("select", "idx"), &OptionButton::select);
ClassDB::bind_method(D_METHOD("get_selected"), &OptionButton::get_selected);
@@ -420,6 +451,8 @@ void OptionButton::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_item_count", "count"), &OptionButton::set_item_count);
ClassDB::bind_method(D_METHOD("get_item_count"), &OptionButton::get_item_count);
+ ClassDB::bind_method(D_METHOD("has_selectable_items"), &OptionButton::has_selectable_items);
+ ClassDB::bind_method(D_METHOD("get_selectable_item", "from_last"), &OptionButton::get_selectable_item, DEFVAL(false));
// "selected" property must come after "item_count", otherwise GH-10213 occurs.
ADD_ARRAY_COUNT("Items", "item_count", "set_item_count", "get_item_count", "popup/item_");
diff --git a/scene/gui/option_button.h b/scene/gui/option_button.h
index 732730e0f4..7896132626 100644
--- a/scene/gui/option_button.h
+++ b/scene/gui/option_button.h
@@ -37,7 +37,7 @@
class OptionButton : public Button {
GDCLASS(OptionButton, Button);
- PopupMenu *popup;
+ PopupMenu *popup = nullptr;
int current = -1;
void _focused(int p_which);
@@ -77,12 +77,16 @@ public:
int get_item_index(int p_id) const;
Variant get_item_metadata(int p_idx) const;
bool is_item_disabled(int p_idx) const;
+ bool is_item_separator(int p_idx) const;
String get_item_tooltip(int p_idx) const;
+ bool has_selectable_items() const;
+ int get_selectable_item(bool p_from_last = false) const;
+
void set_item_count(int p_count);
int get_item_count() const;
- void add_separator();
+ void add_separator(const String &p_text = "");
void clear();
diff --git a/scene/gui/popup.h b/scene/gui/popup.h
index c45f4ddc24..6211af4d20 100644
--- a/scene/gui/popup.h
+++ b/scene/gui/popup.h
@@ -65,7 +65,7 @@ public:
class PopupPanel : public Popup {
GDCLASS(PopupPanel, Popup);
- Panel *panel;
+ Panel *panel = nullptr;
protected:
void _update_child_rects();
diff --git a/scene/gui/popup_menu.cpp b/scene/gui/popup_menu.cpp
index 9fc1fb072c..8303d6db57 100644
--- a/scene/gui/popup_menu.cpp
+++ b/scene/gui/popup_menu.cpp
@@ -47,8 +47,8 @@ String PopupMenu::_get_accel_text(const Item &p_item) const {
}
Size2 PopupMenu::_get_contents_minimum_size() const {
- int vseparation = get_theme_constant(SNAME("vseparation"));
- int hseparation = get_theme_constant(SNAME("hseparation"));
+ int vseparation = get_theme_constant(SNAME("v_separation"));
+ int hseparation = get_theme_constant(SNAME("h_separation"));
Size2 minsize = get_theme_stylebox(SNAME("panel"))->get_minimum_size(); // Accounts for margin in the margin container
minsize.x += scroll_container->get_v_scroll_bar()->get_size().width * 2; // Adds a buffer so that the scrollbar does not render over the top of content
@@ -129,7 +129,7 @@ int PopupMenu::_get_item_height(int p_item) const {
}
int PopupMenu::_get_items_total_height() const {
- int vsep = get_theme_constant(SNAME("vseparation"));
+ int vsep = get_theme_constant(SNAME("v_separation"));
// Get total height of all items by taking max of icon height and font height
int items_total_height = 0;
@@ -148,7 +148,7 @@ int PopupMenu::_get_mouse_over(const Point2 &p_over) const {
Ref<StyleBox> style = get_theme_stylebox(SNAME("panel")); // Accounts for margin in the margin container
- int vseparation = get_theme_constant(SNAME("vseparation"));
+ int vseparation = get_theme_constant(SNAME("v_separation"));
Point2 ofs = style->get_offset() + Point2(0, vseparation / 2);
@@ -169,7 +169,7 @@ int PopupMenu::_get_mouse_over(const Point2 &p_over) const {
return -1;
}
-void PopupMenu::_activate_submenu(int p_over) {
+void PopupMenu::_activate_submenu(int p_over, bool p_by_keyboard) {
Node *n = get_node(items[p_over].submenu);
ERR_FAIL_COND_MSG(!n, "Item subnode does not exist: " + items[p_over].submenu + ".");
Popup *submenu_popup = Object::cast_to<Popup>(n);
@@ -179,7 +179,7 @@ void PopupMenu::_activate_submenu(int p_over) {
}
Ref<StyleBox> style = get_theme_stylebox(SNAME("panel"));
- int vsep = get_theme_constant(SNAME("vseparation"));
+ int vsep = get_theme_constant(SNAME("v_separation"));
Point2 this_pos = get_position();
Rect2 this_rect(this_pos, get_size());
@@ -213,8 +213,10 @@ void PopupMenu::_activate_submenu(int p_over) {
return;
}
+ submenu_pum->activated_by_keyboard = p_by_keyboard;
+
// If not triggered by the mouse, start the popup with its first item selected.
- if (submenu_pum->get_item_count() > 0 && Input::get_singleton()->is_action_just_pressed("ui_accept")) {
+ if (submenu_pum->get_item_count() > 0 && p_by_keyboard) {
submenu_pum->set_current_index(0);
}
@@ -323,14 +325,14 @@ void PopupMenu::gui_input(const Ref<InputEvent> &p_event) {
set_input_as_handled();
}
} else if (p_event->is_action("ui_right") && p_event->is_pressed()) {
- if (mouse_over >= 0 && mouse_over < items.size() && !!items[mouse_over].separator && items[mouse_over].submenu.is_empty() && submenu_over != mouse_over) {
- _activate_submenu(mouse_over);
+ if (mouse_over >= 0 && mouse_over < items.size() && !items[mouse_over].separator && !items[mouse_over].submenu.is_empty() && submenu_over != mouse_over) {
+ _activate_submenu(mouse_over, true);
set_input_as_handled();
}
} else if (p_event->is_action("ui_accept") && p_event->is_pressed()) {
if (mouse_over >= 0 && mouse_over < items.size() && !items[mouse_over].separator) {
if (!items[mouse_over].submenu.is_empty() && submenu_over != mouse_over) {
- _activate_submenu(mouse_over);
+ _activate_submenu(mouse_over, true);
} else {
activate_item(mouse_over);
}
@@ -396,6 +398,11 @@ void PopupMenu::gui_input(const Ref<InputEvent> &p_event) {
Ref<InputEventMouseMotion> m = p_event;
if (m.is_valid()) {
+ if (m->get_velocity().is_equal_approx(Vector2())) {
+ return;
+ }
+ activated_by_keyboard = false;
+
for (const Rect2 &E : autohide_areas) {
if (!Rect2(Point2(), get_size()).has_point(m->get_position()) && E.has_point(m->get_position())) {
_close_pressed();
@@ -497,8 +504,8 @@ void PopupMenu::_draw_items() {
Ref<StyleBox> labeled_separator_left = get_theme_stylebox(SNAME("labeled_separator_left"));
Ref<StyleBox> labeled_separator_right = get_theme_stylebox(SNAME("labeled_separator_right"));
- int vseparation = get_theme_constant(SNAME("vseparation"));
- int hseparation = get_theme_constant(SNAME("hseparation"));
+ int vseparation = get_theme_constant(SNAME("v_separation"));
+ int hseparation = get_theme_constant(SNAME("h_separation"));
Color font_color = get_theme_color(SNAME("font_color"));
Color font_disabled_color = get_theme_color(SNAME("font_disabled_color"));
Color font_accelerator_color = get_theme_color(SNAME("font_accelerator_color"));
@@ -557,10 +564,8 @@ void PopupMenu::_draw_items() {
// Separator
item_ofs.x += items[i].h_ofs;
if (items[i].separator) {
- int sep_h = separator->get_center_size().height + separator->get_minimum_size().height;
- int sep_ofs = Math::floor((h - sep_h) / 2.0);
if (!text.is_empty() || !items[i].icon.is_null()) {
- int content_size = items[i].text_buf->get_size().width;
+ int content_size = items[i].text_buf->get_size().width + hseparation * 2;
if (!items[i].icon.is_null()) {
content_size += icon_size.width + hseparation;
}
@@ -569,12 +574,18 @@ void PopupMenu::_draw_items() {
int content_left = content_center - content_size / 2;
int content_right = content_center + content_size / 2;
if (content_left > item_ofs.x) {
+ int sep_h = labeled_separator_left->get_center_size().height + labeled_separator_left->get_minimum_size().height;
+ int sep_ofs = Math::floor((h - sep_h) / 2.0);
labeled_separator_left->draw(ci, Rect2(item_ofs + Point2(0, sep_ofs), Size2(MAX(0, content_left - item_ofs.x), sep_h)));
}
if (content_right < display_width) {
+ int sep_h = labeled_separator_right->get_center_size().height + labeled_separator_right->get_minimum_size().height;
+ int sep_ofs = Math::floor((h - sep_h) / 2.0);
labeled_separator_right->draw(ci, Rect2(Point2(content_right, item_ofs.y + sep_ofs), Size2(MAX(0, display_width - content_right), sep_h)));
}
} else {
+ int sep_h = separator->get_center_size().height + separator->get_minimum_size().height;
+ int sep_ofs = Math::floor((h - sep_h) / 2.0);
separator->draw(ci, Rect2(item_ofs + Point2(0, sep_ofs), Size2(display_width, sep_h)));
}
}
@@ -624,23 +635,28 @@ void PopupMenu::_draw_items() {
}
}
- // Text
Color font_outline_color = get_theme_color(SNAME("font_outline_color"));
int outline_size = get_theme_constant(SNAME("outline_size"));
+
+ // Text
if (items[i].separator) {
+ Color font_separator_outline_color = get_theme_color(SNAME("font_separator_outline_color"));
+ int separator_outline_size = get_theme_constant(SNAME("separator_outline_size"));
+
if (!text.is_empty()) {
Vector2 text_pos = Point2(separator_ofs, item_ofs.y + Math::floor((h - items[i].text_buf->get_size().y) / 2.0));
if (!rtl && !items[i].icon.is_null()) {
text_pos.x += icon_size.width + hseparation;
}
- if (outline_size > 0 && font_outline_color.a > 0) {
- items[i].text_buf->draw_outline(ci, text_pos, outline_size, font_outline_color);
+ if (separator_outline_size > 0 && font_separator_outline_color.a > 0) {
+ items[i].text_buf->draw_outline(ci, text_pos, separator_outline_size, font_separator_outline_color);
}
items[i].text_buf->draw(ci, text_pos, font_separator_color);
}
} else {
item_ofs.x += icon_ofs + check_ofs;
+
if (rtl) {
Vector2 text_pos = Size2(control->get_size().width - items[i].text_buf->get_size().width - item_ofs.x, item_ofs.y) + Point2(0, Math::floor((h - items[i].text_buf->get_size().y) / 2.0));
if (outline_size > 0 && font_outline_color.a > 0) {
@@ -687,7 +703,7 @@ void PopupMenu::_draw_background() {
void PopupMenu::_minimum_lifetime_timeout() {
close_allowed = true;
// If the mouse still isn't in this popup after timer expires, close.
- if (!get_visible_rect().has_point(get_mouse_position())) {
+ if (!activated_by_keyboard && !get_visible_rect().has_point(get_mouse_position())) {
_close_pressed();
}
}
@@ -713,8 +729,8 @@ void PopupMenu::_shape_item(int p_item) {
if (items.write[p_item].dirty) {
items.write[p_item].text_buf->clear();
- Ref<Font> font = get_theme_font(SNAME("font"));
- int font_size = get_theme_font_size(SNAME("font_size"));
+ Ref<Font> font = get_theme_font(items[p_item].separator ? SNAME("font_separator") : SNAME("font"));
+ int font_size = get_theme_font_size(items[p_item].separator ? SNAME("font_separator_size") : SNAME("font_size"));
if (items[p_item].text_direction == Control::TEXT_DIRECTION_INHERITED) {
items.write[p_item].text_buf->set_direction(is_layout_rtl() ? TextServer::DIRECTION_RTL : TextServer::DIRECTION_LTR);
@@ -772,7 +788,7 @@ void PopupMenu::_notification(int p_what) {
case NOTIFICATION_INTERNAL_PROCESS: {
// Only used when using operating system windows.
- if (!is_embedded() && autohide_areas.size()) {
+ if (!activated_by_keyboard && !is_embedded() && autohide_areas.size()) {
Point2 mouse_pos = DisplayServer::get_singleton()->mouse_get_position();
mouse_pos -= get_position();
@@ -818,10 +834,10 @@ void PopupMenu::_notification(int p_what) {
// Set margin on the margin container
Ref<StyleBox> panel_style = get_theme_stylebox(SNAME("panel"));
- margin_container->add_theme_constant_override("margin_top", panel_style->get_margin(Side::SIDE_TOP));
- margin_container->add_theme_constant_override("margin_bottom", panel_style->get_margin(Side::SIDE_BOTTOM));
margin_container->add_theme_constant_override("margin_left", panel_style->get_margin(Side::SIDE_LEFT));
+ margin_container->add_theme_constant_override("margin_top", panel_style->get_margin(Side::SIDE_TOP));
margin_container->add_theme_constant_override("margin_right", panel_style->get_margin(Side::SIDE_RIGHT));
+ margin_container->add_theme_constant_override("margin_bottom", panel_style->get_margin(Side::SIDE_BOTTOM));
}
} break;
}
diff --git a/scene/gui/popup_menu.h b/scene/gui/popup_menu.h
index 5ce55209d4..98d76875cb 100644
--- a/scene/gui/popup_menu.h
+++ b/scene/gui/popup_menu.h
@@ -87,9 +87,10 @@ class PopupMenu : public Popup {
};
bool close_allowed = false;
+ bool activated_by_keyboard = false;
Timer *minimum_lifetime_timer = nullptr;
- Timer *submenu_timer;
+ Timer *submenu_timer = nullptr;
List<Rect2> autohide_areas;
Vector<Item> items;
MouseButton initial_button_mask = MouseButton::NONE;
@@ -107,7 +108,7 @@ class PopupMenu : public Popup {
void _shape_item(int p_item);
virtual void gui_input(const Ref<InputEvent> &p_event);
- void _activate_submenu(int p_over);
+ void _activate_submenu(int p_over, bool p_by_keyboard = false);
void _submenu_timeout();
uint64_t popup_time_msec = 0;
@@ -125,9 +126,9 @@ class PopupMenu : public Popup {
uint64_t search_time_msec = 0;
String search_string = "";
- MarginContainer *margin_container;
- ScrollContainer *scroll_container;
- Control *control;
+ MarginContainer *margin_container = nullptr;
+ ScrollContainer *scroll_container = nullptr;
+ Control *control = nullptr;
void _draw_items();
void _draw_background();
diff --git a/scene/gui/range.cpp b/scene/gui/range.cpp
index 2fb6452a97..8e66826e9d 100644
--- a/scene/gui/range.cpp
+++ b/scene/gui/range.cpp
@@ -34,7 +34,7 @@ TypedArray<String> Range::get_configuration_warnings() const {
TypedArray<String> warnings = Node::get_configuration_warnings();
if (shared->exp_ratio && shared->min <= 0) {
- warnings.push_back(TTR("If \"Exp Edit\" is enabled, \"Min Value\" must be greater than 0."));
+ warnings.push_back(RTR("If \"Exp Edit\" is enabled, \"Min Value\" must be greater than 0."));
}
return warnings;
@@ -282,7 +282,7 @@ void Range::_bind_methods() {
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "allow_greater"), "set_allow_greater", "is_greater_allowed");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "allow_lesser"), "set_allow_lesser", "is_lesser_allowed");
- GDVIRTUAL_BIND(_value_changed);
+ GDVIRTUAL_BIND(_value_changed, "new_value");
ADD_LINKED_PROPERTY("min_value", "value");
ADD_LINKED_PROPERTY("min_value", "max_value");
diff --git a/scene/gui/range.h b/scene/gui/range.h
index 597c50ca26..46b0d39202 100644
--- a/scene/gui/range.h
+++ b/scene/gui/range.h
@@ -50,7 +50,7 @@ class Range : public Control {
void emit_changed(const char *p_what = "");
};
- Shared *shared;
+ Shared *shared = nullptr;
void _ref_shared(Shared *p_shared);
void _unref_shared();
diff --git a/scene/gui/rich_text_label.cpp b/scene/gui/rich_text_label.cpp
index d585fb3a7a..7ed28ac3c8 100644
--- a/scene/gui/rich_text_label.cpp
+++ b/scene/gui/rich_text_label.cpp
@@ -30,6 +30,7 @@
#include "rich_text_label.h"
+#include "core/input/input_map.h"
#include "core/math/math_defs.h"
#include "core/os/keyboard.h"
#include "core/os/os.h"
@@ -142,7 +143,7 @@ RichTextLabel::Item *RichTextLabel::_get_item_at_pos(RichTextLabel::Item *p_item
for (Item *it = p_item_from; it && it != p_item_to; it = _get_next_item(it)) {
switch (it->type) {
case ITEM_TEXT: {
- ItemText *t = (ItemText *)it;
+ ItemText *t = static_cast<ItemText *>(it);
offset += t->text.length();
if (offset > p_position) {
return it;
@@ -166,16 +167,16 @@ String RichTextLabel::_roman(int p_num, bool p_capitalize) const {
};
String s;
if (p_capitalize) {
- String M[] = { "", "M", "MM", "MMM" };
- String C[] = { "", "C", "CC", "CCC", "CD", "D", "DC", "DCC", "DCCC", "CM" };
- String X[] = { "", "X", "XX", "XXX", "XL", "L", "LX", "LXX", "LXXX", "XC" };
- String I[] = { "", "I", "II", "III", "IV", "V", "VI", "VII", "VIII", "IX" };
+ const String M[] = { "", "M", "MM", "MMM" };
+ const String C[] = { "", "C", "CC", "CCC", "CD", "D", "DC", "DCC", "DCCC", "CM" };
+ const String X[] = { "", "X", "XX", "XXX", "XL", "L", "LX", "LXX", "LXXX", "XC" };
+ const String I[] = { "", "I", "II", "III", "IV", "V", "VI", "VII", "VIII", "IX" };
s = M[p_num / 1000] + C[(p_num % 1000) / 100] + X[(p_num % 100) / 10] + I[p_num % 10];
} else {
- String M[] = { "", "m", "mm", "mmm" };
- String C[] = { "", "c", "cc", "ccc", "cd", "d", "dc", "dcc", "dccc", "cm" };
- String X[] = { "", "x", "xx", "xxx", "xl", "l", "lx", "lxx", "lxxx", "xc" };
- String I[] = { "", "i", "ii", "iii", "iv", "v", "vi", "vii", "viii", "ix" };
+ const String M[] = { "", "m", "mm", "mmm" };
+ const String C[] = { "", "c", "cc", "ccc", "cd", "d", "dc", "dcc", "dccc", "cm" };
+ const String X[] = { "", "x", "xx", "xxx", "xl", "l", "lx", "lxx", "lxxx", "xc" };
+ const String I[] = { "", "i", "ii", "iii", "iv", "v", "vi", "vii", "viii", "ix" };
s = M[p_num / 1000] + C[(p_num % 1000) / 100] + X[(p_num % 100) / 10] + I[p_num % 10];
}
return s;
@@ -215,7 +216,7 @@ void RichTextLabel::_update_line_font(ItemFrame *p_frame, int p_line, const Ref<
RID t = l.text_buf->get_rid();
int spans = TS->shaped_get_span_count(t);
for (int i = 0; i < spans; i++) {
- ItemText *it = (ItemText *)(uint64_t)TS->shaped_get_span_meta(t, i);
+ ItemText *it = reinterpret_cast<ItemText *>((uint64_t)TS->shaped_get_span_meta(t, i));
if (it) {
Ref<Font> font = _find_font(it);
if (font.is_null()) {
@@ -269,8 +270,8 @@ void RichTextLabel::_resize_line(ItemFrame *p_frame, int p_line, const Ref<Font>
switch (it->type) {
case ITEM_TABLE: {
ItemTable *table = static_cast<ItemTable *>(it);
- int hseparation = get_theme_constant(SNAME("table_hseparation"));
- int vseparation = get_theme_constant(SNAME("table_vseparation"));
+ int hseparation = get_theme_constant(SNAME("table_h_separation"));
+ int vseparation = get_theme_constant(SNAME("table_v_separation"));
int col_count = table->columns.size();
for (int i = 0; i < col_count; i++) {
@@ -466,15 +467,11 @@ void RichTextLabel::_shape_line(ItemFrame *p_frame, int p_line, const Ref<Font>
switch (it->type) {
case ITEM_DROPCAP: {
// Add dropcap.
- const ItemDropcap *dc = (ItemDropcap *)it;
- if (dc != nullptr) {
- l.text_buf->set_dropcap(dc->text, dc->font, dc->font_size, dc->dropcap_margins);
- l.dc_color = dc->color;
- l.dc_ol_size = dc->ol_size;
- l.dc_ol_color = dc->ol_color;
- } else {
- l.text_buf->clear_dropcap();
- }
+ const ItemDropcap *dc = static_cast<ItemDropcap *>(it);
+ l.text_buf->set_dropcap(dc->text, dc->font, dc->font_size, dc->dropcap_margins);
+ l.dc_color = dc->color;
+ l.dc_ol_size = dc->ol_size;
+ l.dc_ol_color = dc->ol_color;
} break;
case ITEM_NEWLINE: {
Ref<Font> font = _find_font(it);
@@ -491,7 +488,7 @@ void RichTextLabel::_shape_line(ItemFrame *p_frame, int p_line, const Ref<Font>
remaining_characters--;
} break;
case ITEM_TEXT: {
- ItemText *t = (ItemText *)it;
+ ItemText *t = static_cast<ItemText *>(it);
Ref<Font> font = _find_font(it);
if (font.is_null()) {
font = p_base_font;
@@ -513,7 +510,7 @@ void RichTextLabel::_shape_line(ItemFrame *p_frame, int p_line, const Ref<Font>
l.char_count += tx.length();
} break;
case ITEM_IMAGE: {
- ItemImage *img = (ItemImage *)it;
+ ItemImage *img = static_cast<ItemImage *>(it);
l.text_buf->add_object((uint64_t)it, img->size, img->inline_align, 1);
text += String::chr(0xfffc);
l.char_count++;
@@ -521,8 +518,8 @@ void RichTextLabel::_shape_line(ItemFrame *p_frame, int p_line, const Ref<Font>
} break;
case ITEM_TABLE: {
ItemTable *table = static_cast<ItemTable *>(it);
- int hseparation = get_theme_constant(SNAME("table_hseparation"));
- int vseparation = get_theme_constant(SNAME("table_vseparation"));
+ int hseparation = get_theme_constant(SNAME("table_h_separation"));
+ int vseparation = get_theme_constant(SNAME("table_v_separation"));
int col_count = table->columns.size();
int t_char_count = 0;
// Set minimums to zero.
@@ -842,7 +839,7 @@ int RichTextLabel::_draw_line(ItemFrame *p_frame, int p_line, const Vector2 &p_o
// Draw inlined objects.
Array objects = TS->shaped_text_get_objects(rid);
for (int i = 0; i < objects.size(); i++) {
- Item *it = (Item *)(uint64_t)objects[i];
+ Item *it = reinterpret_cast<Item *>((uint64_t)objects[i]);
if (it != nullptr) {
Rect2 rect = TS->shaped_text_get_object_rect(rid, objects[i]);
//draw_rect(rect, Color(1,0,0), false, 2); //DEBUG_RECTS
@@ -856,7 +853,7 @@ int RichTextLabel::_draw_line(ItemFrame *p_frame, int p_line, const Vector2 &p_o
Color odd_row_bg = get_theme_color(SNAME("table_odd_row_bg"));
Color even_row_bg = get_theme_color(SNAME("table_even_row_bg"));
Color border = get_theme_color(SNAME("table_border"));
- int hseparation = get_theme_constant(SNAME("table_hseparation"));
+ int hseparation = get_theme_constant(SNAME("table_h_separation"));
int col_count = table->columns.size();
int row_count = table->rows.size();
@@ -944,8 +941,8 @@ int RichTextLabel::_draw_line(ItemFrame *p_frame, int p_line, const Vector2 &p_o
}
//Apply fx.
- float faded_visibility = 1.0f;
if (fade) {
+ float faded_visibility = 1.0f;
if (glyphs[i].start >= fade->starting_index) {
faded_visibility -= (float)(glyphs[i].start - fade->starting_index) / (float)fade->length;
faded_visibility = faded_visibility < 0.0f ? 0.0f : faded_visibility;
@@ -1160,8 +1157,8 @@ int RichTextLabel::_draw_line(ItemFrame *p_frame, int p_line, const Vector2 &p_o
}
//Apply fx.
- float faded_visibility = 1.0f;
if (fade) {
+ float faded_visibility = 1.0f;
if (glyphs[i].start >= fade->starting_index) {
faded_visibility -= (float)(glyphs[i].start - fade->starting_index) / (float)fade->length;
faded_visibility = faded_visibility < 0.0f ? 0.0f : faded_visibility;
@@ -1330,14 +1327,22 @@ void RichTextLabel::_find_click(ItemFrame *p_frame, const Point2i &p_click, Item
}
}
-float RichTextLabel::_find_click_in_line(ItemFrame *p_frame, int p_line, const Vector2 &p_ofs, int p_width, const Point2i &p_click, ItemFrame **r_click_frame, int *r_click_line, Item **r_click_item, int *r_click_char) {
+float RichTextLabel::_find_click_in_line(ItemFrame *p_frame, int p_line, const Vector2 &p_ofs, int p_width, const Point2i &p_click, ItemFrame **r_click_frame, int *r_click_line, Item **r_click_item, int *r_click_char, bool p_table) {
Vector2 off;
int char_pos = -1;
Line &l = p_frame->lines.write[p_line];
bool rtl = (l.text_buf->get_direction() == TextServer::DIRECTION_RTL);
bool lrtl = is_layout_rtl();
+
+ // Table hit test results.
bool table_hit = false;
+ Vector2i table_range;
+ float table_offy = 0.f;
+ ItemFrame *table_click_frame = nullptr;
+ int table_click_line = -1;
+ Item *table_click_item = nullptr;
+ int table_click_char = -1;
for (int line = 0; line < l.text_buf->get_line_count(); line++) {
RID rid = l.text_buf->get_line_rid(line);
@@ -1378,19 +1383,18 @@ float RichTextLabel::_find_click_in_line(ItemFrame *p_frame, int p_line, const V
Array objects = TS->shaped_text_get_objects(rid);
for (int i = 0; i < objects.size(); i++) {
- Item *it = (Item *)(uint64_t)objects[i];
+ Item *it = reinterpret_cast<Item *>((uint64_t)objects[i]);
if (it != nullptr) {
Rect2 rect = TS->shaped_text_get_object_rect(rid, objects[i]);
- if (rect.has_point(p_click - p_ofs - off)) {
+ rect.position += p_ofs + off;
+ if (p_click.y >= rect.position.y && p_click.y <= rect.position.y + rect.size.y) {
switch (it->type) {
case ITEM_TABLE: {
- int hseparation = get_theme_constant(SNAME("table_hseparation"));
- int vseparation = get_theme_constant(SNAME("table_vseparation"));
+ int hseparation = get_theme_constant(SNAME("table_h_separation"));
+ int vseparation = get_theme_constant(SNAME("table_v_separation"));
ItemTable *table = static_cast<ItemTable *>(it);
- table_hit = true;
-
int idx = 0;
int col_count = table->columns.size();
int row_count = table->rows.size();
@@ -1406,7 +1410,7 @@ float RichTextLabel::_find_click_in_line(ItemFrame *p_frame, int p_line, const V
if (rtl) {
coff.x = rect.size.width - table->columns[col].width - coff.x;
}
- Rect2 crect = Rect2(p_ofs + off + rect.position + coff - frame->padding.position, Size2(table->columns[col].width + hseparation, table->rows[row] + vseparation) + frame->padding.position + frame->padding.size);
+ Rect2 crect = Rect2(rect.position + coff - frame->padding.position, Size2(table->columns[col].width + hseparation, table->rows[row] + vseparation) + frame->padding.position + frame->padding.size);
if (col == col_count - 1) {
if (rtl) {
crect.size.x = crect.position.x + crect.size.x;
@@ -1417,9 +1421,19 @@ float RichTextLabel::_find_click_in_line(ItemFrame *p_frame, int p_line, const V
}
if (crect.has_point(p_click)) {
for (int j = 0; j < frame->lines.size(); j++) {
- _find_click_in_line(frame, j, p_ofs + off + rect.position + Vector2(0, frame->lines[j].offset.y), rect.size.x, p_click, r_click_frame, r_click_line, r_click_item, r_click_char);
- if (((r_click_item != nullptr) && ((*r_click_item) != nullptr)) || ((r_click_frame != nullptr) && ((*r_click_frame) != nullptr))) {
- return off.y;
+ _find_click_in_line(frame, j, rect.position + Vector2(0, frame->lines[j].offset.y), rect.size.x, p_click, &table_click_frame, &table_click_line, &table_click_item, &table_click_char, true);
+ if (table_click_frame && table_click_item) {
+ // Save cell detected cell hit data.
+ table_range = Vector2i(INT32_MAX, 0);
+ for (Item *F : table->subitems) {
+ ItemFrame *sub_frame = static_cast<ItemFrame *>(F);
+ for (int k = 0; k < sub_frame->lines.size(); k++) {
+ table_range.x = MIN(table_range.x, sub_frame->lines[k].char_offset);
+ table_range.y = MAX(table_range.y, sub_frame->lines[k].char_offset + sub_frame->lines[k].char_count);
+ }
+ }
+ table_offy = off.y;
+ table_hit = true;
}
}
}
@@ -1433,14 +1447,39 @@ float RichTextLabel::_find_click_in_line(ItemFrame *p_frame, int p_line, const V
}
}
}
- Rect2 rect = Rect2(p_ofs + off - Vector2(0, TS->shaped_text_get_ascent(rid)), Size2(get_size().x, TS->shaped_text_get_size(rid).y));
+ Rect2 rect = Rect2(p_ofs + off - Vector2(0, TS->shaped_text_get_ascent(rid)) - p_frame->padding.position, TS->shaped_text_get_size(rid) + p_frame->padding.position + p_frame->padding.size);
+ if (p_table) {
+ rect.size.y += get_theme_constant(SNAME("table_v_separation"));
+ }
- if (rect.has_point(p_click) && !table_hit) {
+ if (p_click.y >= rect.position.y && p_click.y <= rect.position.y + rect.size.y) {
char_pos = TS->shaped_text_hit_test_position(rid, p_click.x - rect.position.x);
}
+
+ // If table hit was detected, and line hit is in the table bounds use table hit.
+ if (table_hit && (((char_pos + p_frame->lines[p_line].char_offset) >= table_range.x && (char_pos + p_frame->lines[p_line].char_offset) <= table_range.y) || char_pos == -1)) {
+ if (r_click_frame != nullptr) {
+ *r_click_frame = table_click_frame;
+ }
+
+ if (r_click_line != nullptr) {
+ *r_click_line = table_click_line;
+ }
+
+ if (r_click_item != nullptr) {
+ *r_click_item = table_click_item;
+ }
+
+ if (r_click_char != nullptr) {
+ *r_click_char = table_click_char;
+ }
+ return table_offy;
+ }
+
off.y += TS->shaped_text_get_descent(rid) + l.text_buf->get_spacing_bottom() + get_theme_constant(SNAME("line_separation"));
}
+ // Text line hit.
if (char_pos >= 0) {
// Find item.
if (r_click_item != nullptr) {
@@ -1451,7 +1490,7 @@ float RichTextLabel::_find_click_in_line(ItemFrame *p_frame, int p_line, const V
if (it_to != nullptr) {
*r_click_item = _get_prev_item(it_to);
} else {
- for (Item *i = it; i && i != it_to; i = _get_next_item(i)) {
+ for (Item *i = it; i; i = _get_next_item(i)) {
*r_click_item = i;
}
}
@@ -1650,8 +1689,7 @@ void RichTextLabel::_notification(int p_what) {
case NOTIFICATION_FOCUS_EXIT: {
if (deselect_on_focus_loss_enabled) {
- selection.active = false;
- update();
+ deselect();
}
} break;
@@ -1684,9 +1722,9 @@ Control::CursorShape RichTextLabel::get_cursor_shape(const Point2 &p_pos) const
Item *item = nullptr;
bool outside = true;
- ((RichTextLabel *)(this))->_find_click(main, p_pos, nullptr, nullptr, &item, nullptr, &outside);
+ const_cast<RichTextLabel *>(this)->_find_click(main, p_pos, nullptr, nullptr, &item, nullptr, &outside);
- if (item && !outside && ((RichTextLabel *)(this))->_find_meta(item, nullptr)) {
+ if (item && !outside && const_cast<RichTextLabel *>(this)->_find_meta(item, nullptr)) {
return CURSOR_POINTING_HAND;
}
@@ -1742,9 +1780,7 @@ void RichTextLabel::gui_input(const Ref<InputEvent> &p_event) {
selection.to_line = 0;
selection.to_item = nullptr;
selection.to_char = 0;
- selection.active = false;
-
- update();
+ deselect();
}
}
}
@@ -1802,9 +1838,7 @@ void RichTextLabel::gui_input(const Ref<InputEvent> &p_event) {
selection.to_line = 0;
selection.to_item = nullptr;
selection.to_char = 0;
- selection.active = false;
-
- update();
+ deselect();
}
}
@@ -1835,6 +1869,13 @@ void RichTextLabel::gui_input(const Ref<InputEvent> &p_event) {
vscroll->set_value(vscroll->get_value() + vscroll->get_page() * b->get_factor() * 0.5 / 8);
}
}
+ if (b->get_button_index() == MouseButton::RIGHT && context_menu_enabled) {
+ _generate_context_menu();
+ menu->set_position(get_screen_position() + b->get_position());
+ menu->reset_size();
+ menu->popup();
+ grab_focus();
+ }
}
Ref<InputEventPanGesture> pan_gesture = p_event;
@@ -1876,8 +1917,24 @@ void RichTextLabel::gui_input(const Ref<InputEvent> &p_event) {
vscroll->set_value(vscroll->get_max());
handled = true;
}
- if (k->is_action("ui_copy")) {
- selection_copy();
+ if (is_shortcut_keys_enabled()) {
+ if (k->is_action("ui_text_select_all")) {
+ select_all();
+ handled = true;
+ }
+ if (k->is_action("ui_copy")) {
+ selection_copy();
+ handled = true;
+ }
+ }
+ if (k->is_action("ui_menu", true)) {
+ if (context_menu_enabled) {
+ _generate_context_menu();
+ menu->set_position(get_screen_position());
+ menu->reset_size();
+ menu->popup();
+ menu->grab_focus();
+ }
handled = true;
}
@@ -1919,14 +1976,13 @@ void RichTextLabel::gui_input(const Ref<InputEvent> &p_event) {
selection.to_char = c_index;
bool swap = false;
- if (selection.from_item->index > selection.to_item->index) {
- swap = true;
- } else if (selection.from_item->index == selection.to_item->index) {
- if (selection.from_char > selection.to_char) {
+ if (selection.click_frame && c_frame) {
+ const Line &l1 = c_frame->lines[c_line];
+ const Line &l2 = selection.click_frame->lines[selection.click_line];
+ if (l1.char_offset + c_index < l2.char_offset + selection.click_char) {
swap = true;
- } else if (selection.from_char == selection.to_char) {
- selection.active = false;
- update();
+ } else if (l1.char_offset + c_index == l2.char_offset + selection.click_char) {
+ deselect();
return;
}
}
@@ -1988,7 +2044,7 @@ void RichTextLabel::_find_frame(Item *p_item, ItemFrame **r_frame, int *r_line)
while (item) {
if (item->parent != nullptr && item->parent->type == ITEM_FRAME) {
if (r_frame != nullptr) {
- *r_frame = (ItemFrame *)item->parent;
+ *r_frame = static_cast<ItemFrame *>(item->parent);
}
if (r_line != nullptr) {
*r_line = item->line;
@@ -2192,7 +2248,7 @@ TextServer::Direction RichTextLabel::_find_direction(Item *p_item) {
}
}
-Control::StructuredTextParser RichTextLabel::_find_stt(Item *p_item) {
+TextServer::StructuredTextParser RichTextLabel::_find_stt(Item *p_item) {
Item *item = p_item;
while (item) {
@@ -2523,7 +2579,7 @@ void RichTextLabel::_add_item(Item *p_item, bool p_enter, bool p_ensure_newline)
p_item->index = current_idx++;
p_item->char_ofs = current_char_ofs;
if (p_item->type == ITEM_TEXT) {
- ItemText *t = (ItemText *)p_item;
+ ItemText *t = static_cast<ItemText *>(p_item);
current_char_ofs += t->text.length();
} else if (p_item->type == ITEM_IMAGE) {
current_char_ofs++;
@@ -2782,7 +2838,7 @@ void RichTextLabel::push_strikethrough() {
_add_item(item, true);
}
-void RichTextLabel::push_paragraph(HorizontalAlignment p_alignment, Control::TextDirection p_direction, const String &p_language, Control::StructuredTextParser p_st_parser) {
+void RichTextLabel::push_paragraph(HorizontalAlignment p_alignment, Control::TextDirection p_direction, const String &p_language, TextServer::StructuredTextParser p_st_parser) {
ERR_FAIL_COND(current->type == ITEM_TABLE);
ItemParagraph *item = memnew(ItemParagraph);
@@ -2977,11 +3033,10 @@ void RichTextLabel::clear() {
main->lines.clear();
main->lines.resize(1);
main->first_invalid_line = 0;
- update();
selection.click_frame = nullptr;
selection.click_item = nullptr;
- selection.active = false;
+ deselect();
current_idx = 1;
current_char_ofs = 0;
@@ -3408,7 +3463,7 @@ void RichTextLabel::append_text(const String &p_bbcode) {
HorizontalAlignment alignment = HORIZONTAL_ALIGNMENT_LEFT;
Control::TextDirection dir = Control::TEXT_DIRECTION_INHERITED;
String lang;
- Control::StructuredTextParser st_parser = STRUCTURED_TEXT_DEFAULT;
+ TextServer::StructuredTextParser st_parser = TextServer::STRUCTURED_TEXT_DEFAULT;
for (int i = 0; i < subtag.size(); i++) {
Vector<String> subtag_a = subtag[i].split("=");
if (subtag_a.size() == 2) {
@@ -3434,19 +3489,19 @@ void RichTextLabel::append_text(const String &p_bbcode) {
lang = subtag_a[1];
} else if (subtag_a[0] == "st" || subtag_a[0] == "bidi_override") {
if (subtag_a[1] == "d" || subtag_a[1] == "default") {
- st_parser = STRUCTURED_TEXT_DEFAULT;
+ st_parser = TextServer::STRUCTURED_TEXT_DEFAULT;
} else if (subtag_a[1] == "u" || subtag_a[1] == "uri") {
- st_parser = STRUCTURED_TEXT_URI;
+ st_parser = TextServer::STRUCTURED_TEXT_URI;
} else if (subtag_a[1] == "f" || subtag_a[1] == "file") {
- st_parser = STRUCTURED_TEXT_FILE;
+ st_parser = TextServer::STRUCTURED_TEXT_FILE;
} else if (subtag_a[1] == "e" || subtag_a[1] == "email") {
- st_parser = STRUCTURED_TEXT_EMAIL;
+ st_parser = TextServer::STRUCTURED_TEXT_EMAIL;
} else if (subtag_a[1] == "l" || subtag_a[1] == "list") {
- st_parser = STRUCTURED_TEXT_LIST;
+ st_parser = TextServer::STRUCTURED_TEXT_LIST;
} else if (subtag_a[1] == "n" || subtag_a[1] == "none") {
- st_parser = STRUCTURED_TEXT_NONE;
+ st_parser = TextServer::STRUCTURED_TEXT_NONE;
} else if (subtag_a[1] == "c" || subtag_a[1] == "custom") {
- st_parser = STRUCTURED_TEXT_CUSTOM;
+ st_parser = TextServer::STRUCTURED_TEXT_CUSTOM;
}
}
}
@@ -3890,8 +3945,7 @@ void RichTextLabel::set_selection_enabled(bool p_enabled) {
selection.enabled = p_enabled;
if (!p_enabled) {
if (selection.active) {
- selection.active = false;
- update();
+ deselect();
}
set_focus_mode(FOCUS_NONE);
} else {
@@ -3902,8 +3956,7 @@ void RichTextLabel::set_selection_enabled(bool p_enabled) {
void RichTextLabel::set_deselect_on_focus_loss_enabled(const bool p_enabled) {
deselect_on_focus_loss_enabled = p_enabled;
if (p_enabled && selection.active && !has_focus()) {
- selection.active = false;
- update();
+ deselect();
}
}
@@ -3967,7 +4020,7 @@ bool RichTextLabel::_search_line(ItemFrame *p_frame, int p_line, const String &p
text += "\n";
} break;
case ITEM_TEXT: {
- ItemText *t = (ItemText *)it;
+ ItemText *t = static_cast<ItemText *>(it);
text += t->text;
} break;
case ITEM_IMAGE: {
@@ -4100,7 +4153,7 @@ String RichTextLabel::_get_line_text(ItemFrame *p_frame, int p_line, Selection p
if (it_to != nullptr) {
end_idx = it_to->index;
} else {
- for (Item *it = l.from; it && it != it_to; it = _get_next_item(it)) {
+ for (Item *it = l.from; it; it = _get_next_item(it)) {
end_idx = it->index + 1;
}
}
@@ -4142,6 +4195,32 @@ String RichTextLabel::_get_line_text(ItemFrame *p_frame, int p_line, Selection p
return text;
}
+void RichTextLabel::set_context_menu_enabled(bool p_enabled) {
+ context_menu_enabled = p_enabled;
+}
+
+bool RichTextLabel::is_context_menu_enabled() const {
+ return context_menu_enabled;
+}
+
+void RichTextLabel::set_shortcut_keys_enabled(bool p_enabled) {
+ shortcut_keys_enabled = p_enabled;
+}
+
+bool RichTextLabel::is_shortcut_keys_enabled() const {
+ return shortcut_keys_enabled;
+}
+
+// Context menu.
+PopupMenu *RichTextLabel::get_menu() const {
+ const_cast<RichTextLabel *>(this)->_generate_context_menu();
+ return menu;
+}
+
+bool RichTextLabel::is_menu_visible() const {
+ return menu && menu->is_visible();
+}
+
String RichTextLabel::get_selected_text() const {
if (!selection.active || !selection.enabled) {
return "";
@@ -4154,6 +4233,11 @@ String RichTextLabel::get_selected_text() const {
return text;
}
+void RichTextLabel::deselect() {
+ selection.active = false;
+ update();
+}
+
void RichTextLabel::selection_copy() {
String text = get_selected_text();
@@ -4162,6 +4246,53 @@ void RichTextLabel::selection_copy() {
}
}
+void RichTextLabel::select_all() {
+ if (!selection.enabled) {
+ return;
+ }
+
+ Item *it = main;
+ Item *from_item = nullptr;
+ Item *to_item = nullptr;
+
+ while (it) {
+ if (it->type != ITEM_FRAME) {
+ if (!from_item) {
+ from_item = it;
+ } else {
+ to_item = it;
+ }
+ }
+ it = _get_next_item(it, true);
+ }
+ if (!from_item || !to_item) {
+ return;
+ }
+
+ ItemFrame *from_frame = nullptr;
+ int from_line = 0;
+ _find_frame(from_item, &from_frame, &from_line);
+ if (!from_frame) {
+ return;
+ }
+ ItemFrame *to_frame = nullptr;
+ int to_line = 0;
+ _find_frame(to_item, &to_frame, &to_line);
+ if (!to_frame) {
+ return;
+ }
+ selection.from_line = from_line;
+ selection.from_frame = from_frame;
+ selection.from_char = 0;
+ selection.from_item = from_item;
+ selection.to_line = to_line;
+ selection.to_frame = to_frame;
+ selection.to_char = to_frame->lines[to_line].char_count;
+ selection.to_item = to_item;
+ selection.active = true;
+ update();
+}
+
bool RichTextLabel::is_selection_enabled() const {
return selection.enabled;
}
@@ -4218,10 +4349,8 @@ String RichTextLabel::get_parsed_text() const {
Item *it = main;
while (it) {
if (it->type == ITEM_DROPCAP) {
- const ItemDropcap *dc = (ItemDropcap *)it;
- if (dc != nullptr) {
- text += dc->text;
- }
+ ItemDropcap *dc = static_cast<ItemDropcap *>(it);
+ text += dc->text;
} else if (it->type == ITEM_TEXT) {
ItemText *t = static_cast<ItemText *>(it);
text += t->text;
@@ -4247,7 +4376,7 @@ void RichTextLabel::set_text_direction(Control::TextDirection p_text_direction)
}
}
-void RichTextLabel::set_structured_text_bidi_override(Control::StructuredTextParser p_parser) {
+void RichTextLabel::set_structured_text_bidi_override(TextServer::StructuredTextParser p_parser) {
if (st_parser != p_parser) {
st_parser = p_parser;
main->first_invalid_line = 0; //invalidate ALL
@@ -4256,7 +4385,7 @@ void RichTextLabel::set_structured_text_bidi_override(Control::StructuredTextPar
}
}
-Control::StructuredTextParser RichTextLabel::get_structured_text_bidi_override() const {
+TextServer::StructuredTextParser RichTextLabel::get_structured_text_bidi_override() const {
return st_parser;
}
@@ -4391,7 +4520,7 @@ void RichTextLabel::_bind_methods() {
ClassDB::bind_method(D_METHOD("push_color", "color"), &RichTextLabel::push_color);
ClassDB::bind_method(D_METHOD("push_outline_size", "outline_size"), &RichTextLabel::push_outline_size);
ClassDB::bind_method(D_METHOD("push_outline_color", "color"), &RichTextLabel::push_outline_color);
- ClassDB::bind_method(D_METHOD("push_paragraph", "alignment", "base_direction", "language", "st_parser"), &RichTextLabel::push_paragraph, DEFVAL(TextServer::DIRECTION_AUTO), DEFVAL(""), DEFVAL(STRUCTURED_TEXT_DEFAULT));
+ ClassDB::bind_method(D_METHOD("push_paragraph", "alignment", "base_direction", "language", "st_parser"), &RichTextLabel::push_paragraph, DEFVAL(TextServer::DIRECTION_AUTO), DEFVAL(""), DEFVAL(TextServer::STRUCTURED_TEXT_DEFAULT));
ClassDB::bind_method(D_METHOD("push_indent", "level"), &RichTextLabel::push_indent);
ClassDB::bind_method(D_METHOD("push_list", "level", "type", "capitalize"), &RichTextLabel::push_list);
ClassDB::bind_method(D_METHOD("push_meta", "data"), &RichTextLabel::push_meta);
@@ -4453,13 +4582,21 @@ void RichTextLabel::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_selection_enabled", "enabled"), &RichTextLabel::set_selection_enabled);
ClassDB::bind_method(D_METHOD("is_selection_enabled"), &RichTextLabel::is_selection_enabled);
+ ClassDB::bind_method(D_METHOD("set_context_menu_enabled", "enabled"), &RichTextLabel::set_context_menu_enabled);
+ ClassDB::bind_method(D_METHOD("is_context_menu_enabled"), &RichTextLabel::is_context_menu_enabled);
+
+ ClassDB::bind_method(D_METHOD("set_shortcut_keys_enabled", "enabled"), &RichTextLabel::set_shortcut_keys_enabled);
+ ClassDB::bind_method(D_METHOD("is_shortcut_keys_enabled"), &RichTextLabel::is_shortcut_keys_enabled);
+
ClassDB::bind_method(D_METHOD("set_deselect_on_focus_loss_enabled", "enable"), &RichTextLabel::set_deselect_on_focus_loss_enabled);
ClassDB::bind_method(D_METHOD("is_deselect_on_focus_loss_enabled"), &RichTextLabel::is_deselect_on_focus_loss_enabled);
ClassDB::bind_method(D_METHOD("get_selection_from"), &RichTextLabel::get_selection_from);
ClassDB::bind_method(D_METHOD("get_selection_to"), &RichTextLabel::get_selection_to);
+ ClassDB::bind_method(D_METHOD("select_all"), &RichTextLabel::select_all);
ClassDB::bind_method(D_METHOD("get_selected_text"), &RichTextLabel::get_selected_text);
+ ClassDB::bind_method(D_METHOD("deselect"), &RichTextLabel::deselect);
ClassDB::bind_method(D_METHOD("parse_bbcode", "bbcode"), &RichTextLabel::parse_bbcode);
ClassDB::bind_method(D_METHOD("append_text", "bbcode"), &RichTextLabel::append_text);
@@ -4500,7 +4637,10 @@ void RichTextLabel::_bind_methods() {
ClassDB::bind_method(D_METHOD("get_effects"), &RichTextLabel::get_effects);
ClassDB::bind_method(D_METHOD("install_effect", "effect"), &RichTextLabel::install_effect);
- // Note: set "bbcode_enabled" first, to avoid unnecessery "text" resets.
+ ClassDB::bind_method(D_METHOD("get_menu"), &RichTextLabel::get_menu);
+ ClassDB::bind_method(D_METHOD("is_menu_visible"), &RichTextLabel::is_menu_visible);
+
+ // Note: set "bbcode_enabled" first, to avoid unnecessary "text" resets.
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "bbcode_enabled"), "set_use_bbcode", "is_using_bbcode");
ADD_PROPERTY(PropertyInfo(Variant::INT, "tab_size", PROPERTY_HINT_RANGE, "0,24,1"), "set_tab_size", "get_tab_size");
@@ -4521,6 +4661,9 @@ void RichTextLabel::_bind_methods() {
ADD_PROPERTY(PropertyInfo(Variant::INT, "visible_characters_behavior", PROPERTY_HINT_ENUM, "Characters Before Shaping,Characters After Shaping,Glyphs (Layout Direction),Glyphs (Left-to-Right),Glyphs (Right-to-Left)"), "set_visible_characters_behavior", "get_visible_characters_behavior");
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "percent_visible", PROPERTY_HINT_RANGE, "0,1,0.001"), "set_percent_visible", "get_percent_visible");
+ ADD_PROPERTY(PropertyInfo(Variant::BOOL, "context_menu_enabled"), "set_context_menu_enabled", "is_context_menu_enabled");
+ ADD_PROPERTY(PropertyInfo(Variant::BOOL, "shortcut_keys_enabled"), "set_shortcut_keys_enabled", "is_shortcut_keys_enabled");
+
ADD_GROUP("Locale", "");
ADD_PROPERTY(PropertyInfo(Variant::INT, "text_direction", PROPERTY_HINT_ENUM, "Auto,Left-to-Right,Right-to-Left,Inherited"), "set_text_direction", "get_text_direction");
ADD_PROPERTY(PropertyInfo(Variant::STRING, "language", PROPERTY_HINT_LOCALE_ID, ""), "set_language", "get_language");
@@ -4700,6 +4843,59 @@ Size2 RichTextLabel::get_minimum_size() const {
return size;
}
+// Context menu.
+void RichTextLabel::_generate_context_menu() {
+ if (!menu) {
+ menu = memnew(PopupMenu);
+ add_child(menu, false, INTERNAL_MODE_FRONT);
+
+ menu->connect("id_pressed", callable_mp(this, &RichTextLabel::_menu_option));
+ }
+
+ // Reorganize context menu.
+ menu->clear();
+ if (selection.enabled) {
+ menu->add_item(RTR("Copy"), MENU_COPY, is_shortcut_keys_enabled() ? _get_menu_action_accelerator("ui_copy") : Key::NONE);
+ menu->add_item(RTR("Select All"), MENU_SELECT_ALL, is_shortcut_keys_enabled() ? _get_menu_action_accelerator("ui_text_select_all") : Key::NONE);
+ }
+}
+
+Key RichTextLabel::_get_menu_action_accelerator(const String &p_action) {
+ const List<Ref<InputEvent>> *events = InputMap::get_singleton()->action_get_events(p_action);
+ if (!events) {
+ return Key::NONE;
+ }
+
+ // Use first event in the list for the accelerator.
+ const List<Ref<InputEvent>>::Element *first_event = events->front();
+ if (!first_event) {
+ return Key::NONE;
+ }
+
+ const Ref<InputEventKey> event = first_event->get();
+ if (event.is_null()) {
+ return Key::NONE;
+ }
+
+ // Use physical keycode if non-zero
+ if (event->get_physical_keycode() != Key::NONE) {
+ return event->get_physical_keycode_with_modifiers();
+ } else {
+ return event->get_keycode_with_modifiers();
+ }
+}
+
+void RichTextLabel::_menu_option(int p_option) {
+ switch (p_option) {
+ case MENU_COPY: {
+ selection_copy();
+ } break;
+ case MENU_SELECT_ALL: {
+ select_all();
+ } break;
+ }
+}
+
void RichTextLabel::_draw_fbg_boxes(RID p_ci, RID p_rid, Vector2 line_off, Item *it_from, Item *it_to, int start, int end, int fbg_flag) {
Vector2i fbg_index = Vector2i(end, start);
Color last_color = Color(0, 0, 0, 0);
@@ -4707,7 +4903,7 @@ void RichTextLabel::_draw_fbg_boxes(RID p_ci, RID p_rid, Vector2 line_off, Item
// Draw a box based on color tags associated with glyphs
for (int i = start; i < end; i++) {
Item *it = _get_item_at_pos(it_from, it_to, i);
- Color color = Color(0, 0, 0, 0);
+ Color color;
if (fbg_flag == 0) {
color = _find_bgcolor(it);
diff --git a/scene/gui/rich_text_label.h b/scene/gui/rich_text_label.h
index c9cbbe9d15..c6d0d0875d 100644
--- a/scene/gui/rich_text_label.h
+++ b/scene/gui/rich_text_label.h
@@ -32,6 +32,7 @@
#define RICH_TEXT_LABEL_H
#include "rich_text_effect.h"
+#include "scene/gui/popup_menu.h"
#include "scene/gui/scroll_bar.h"
#include "scene/resources/text_paragraph.h"
@@ -91,6 +92,11 @@ public:
VC_GLYPHS_RTL,
};
+ enum MenuItems {
+ MENU_COPY,
+ MENU_SELECT_ALL,
+ };
+
protected:
void _notification(int p_what);
static void _bind_methods();
@@ -228,7 +234,7 @@ private:
HorizontalAlignment alignment = HORIZONTAL_ALIGNMENT_LEFT;
String language;
Control::TextDirection direction = Control::TEXT_DIRECTION_AUTO;
- Control::StructuredTextParser st_parser = STRUCTURED_TEXT_DEFAULT;
+ TextServer::StructuredTextParser st_parser = TextServer::STRUCTURED_TEXT_DEFAULT;
ItemParagraph() { type = ITEM_PARAGRAPH; }
};
@@ -393,7 +399,7 @@ private:
String language;
TextDirection text_direction = TEXT_DIRECTION_AUTO;
- Control::StructuredTextParser st_parser = STRUCTURED_TEXT_DEFAULT;
+ TextServer::StructuredTextParser st_parser = TextServer::STRUCTURED_TEXT_DEFAULT;
Array st_args;
struct Selection {
@@ -420,6 +426,15 @@ private:
Selection selection;
bool deselect_on_focus_loss_enabled = true;
+ bool context_menu_enabled = false;
+ bool shortcut_keys_enabled = true;
+
+ // Context menu.
+ PopupMenu *menu = nullptr;
+ void _generate_context_menu();
+ Key _get_menu_action_accelerator(const String &p_action);
+ void _menu_option(int p_option);
+
int visible_characters = -1;
float percent_visible = 1.0;
VisibleCharactersBehavior visible_chars_behavior = VC_CHARS_BEFORE_SHAPING;
@@ -435,7 +450,7 @@ private:
void _resize_line(ItemFrame *p_frame, int p_line, const Ref<Font> &p_base_font, int p_base_font_size, int p_width);
void _update_line_font(ItemFrame *p_frame, int p_line, const Ref<Font> &p_base_font, int p_base_font_size);
int _draw_line(ItemFrame *p_frame, int p_line, const Vector2 &p_ofs, int p_width, const Color &p_base_color, int p_outline_size, const Color &p_outline_color, const Color &p_font_shadow_color, int p_shadow_outline_size, const Point2 &p_shadow_ofs, int &r_processed_glyphs);
- float _find_click_in_line(ItemFrame *p_frame, int p_line, const Vector2 &p_ofs, int p_width, const Point2i &p_click, ItemFrame **r_click_frame = nullptr, int *r_click_line = nullptr, Item **r_click_item = nullptr, int *r_click_char = nullptr);
+ float _find_click_in_line(ItemFrame *p_frame, int p_line, const Vector2 &p_ofs, int p_width, const Point2i &p_click, ItemFrame **r_click_frame = nullptr, int *r_click_line = nullptr, Item **r_click_item = nullptr, int *r_click_char = nullptr, bool p_table = false);
String _roman(int p_num, bool p_capitalize) const;
String _letters(int p_num, bool p_capitalize) const;
@@ -452,7 +467,7 @@ private:
int _find_margin(Item *p_item, const Ref<Font> &p_base_font, int p_base_font_size);
HorizontalAlignment _find_alignment(Item *p_item);
TextServer::Direction _find_direction(Item *p_item);
- Control::StructuredTextParser _find_stt(Item *p_item);
+ TextServer::StructuredTextParser _find_stt(Item *p_item);
String _find_language(Item *p_item);
Color _find_color(Item *p_item, const Color &p_default_color);
Color _find_outline_color(Item *p_item, const Color &p_default_color);
@@ -510,7 +525,7 @@ public:
void push_outline_color(const Color &p_color);
void push_underline();
void push_strikethrough();
- void push_paragraph(HorizontalAlignment p_alignment, Control::TextDirection p_direction = Control::TEXT_DIRECTION_INHERITED, const String &p_language = "", Control::StructuredTextParser p_st_parser = STRUCTURED_TEXT_DEFAULT);
+ void push_paragraph(HorizontalAlignment p_alignment, Control::TextDirection p_direction = Control::TEXT_DIRECTION_INHERITED, const String &p_language = "", TextServer::StructuredTextParser p_st_parser = TextServer::STRUCTURED_TEXT_DEFAULT);
void push_indent(int p_level);
void push_list(int p_level, ListType p_list, bool p_capitalize);
void push_meta(const Variant &p_meta);
@@ -555,6 +570,12 @@ public:
void set_tab_size(int p_spaces);
int get_tab_size() const;
+ void set_context_menu_enabled(bool p_enabled);
+ bool is_context_menu_enabled() const;
+
+ void set_shortcut_keys_enabled(bool p_enabled);
+ bool is_shortcut_keys_enabled() const;
+
void set_fit_content_height(bool p_enabled);
bool is_fit_content_height_enabled() const;
@@ -584,9 +605,15 @@ public:
int get_selection_from() const;
int get_selection_to() const;
String get_selected_text() const;
+ void select_all();
void selection_copy();
void set_deselect_on_focus_loss_enabled(const bool p_enabled);
bool is_deselect_on_focus_loss_enabled() const;
+ void deselect();
+
+ // Context menu.
+ PopupMenu *get_menu() const;
+ bool is_menu_visible() const;
void parse_bbcode(const String &p_bbcode);
void append_text(const String &p_bbcode);
@@ -606,8 +633,8 @@ public:
void set_autowrap_mode(AutowrapMode p_mode);
AutowrapMode get_autowrap_mode() const;
- void set_structured_text_bidi_override(Control::StructuredTextParser p_parser);
- Control::StructuredTextParser get_structured_text_bidi_override() const;
+ void set_structured_text_bidi_override(TextServer::StructuredTextParser p_parser);
+ TextServer::StructuredTextParser get_structured_text_bidi_override() const;
void set_structured_text_bidi_override_options(Array p_args);
Array get_structured_text_bidi_override_options() const;
diff --git a/scene/gui/scroll_container.cpp b/scene/gui/scroll_container.cpp
index b3cf2cbf7e..135bad4689 100644
--- a/scene/gui/scroll_container.cpp
+++ b/scene/gui/scroll_container.cpp
@@ -533,7 +533,7 @@ TypedArray<String> ScrollContainer::get_configuration_warnings() const {
}
if (found != 1) {
- warnings.push_back(TTR("ScrollContainer is intended to work with a single child control.\nUse a container as child (VBox, HBox, etc.), or a Control and set the custom minimum size manually."));
+ warnings.push_back(RTR("ScrollContainer is intended to work with a single child control.\nUse a container as child (VBox, HBox, etc.), or a Control and set the custom minimum size manually."));
}
return warnings;
diff --git a/scene/gui/scroll_container.h b/scene/gui/scroll_container.h
index c00df87b18..b9fcf64db6 100644
--- a/scene/gui/scroll_container.h
+++ b/scene/gui/scroll_container.h
@@ -47,8 +47,8 @@ public:
};
private:
- HScrollBar *h_scroll;
- VScrollBar *v_scroll;
+ HScrollBar *h_scroll = nullptr;
+ VScrollBar *v_scroll = nullptr;
Size2 child_max_size;
diff --git a/scene/gui/spin_box.h b/scene/gui/spin_box.h
index a15e3fe5f5..d118b28334 100644
--- a/scene/gui/spin_box.h
+++ b/scene/gui/spin_box.h
@@ -38,11 +38,11 @@
class SpinBox : public Range {
GDCLASS(SpinBox, Range);
- LineEdit *line_edit;
+ LineEdit *line_edit = nullptr;
int last_w = 0;
bool update_on_text_changed = false;
- Timer *range_click_timer;
+ Timer *range_click_timer = nullptr;
void _range_click_timeout();
void _release_mouse();
diff --git a/scene/gui/subviewport_container.cpp b/scene/gui/subviewport_container.cpp
index c66e145bc4..68281b6a72 100644
--- a/scene/gui/subviewport_container.cpp
+++ b/scene/gui/subviewport_container.cpp
@@ -176,7 +176,7 @@ void SubViewportContainer::input(const Ref<InputEvent> &p_event) {
return;
}
- Transform2D xform = get_global_transform();
+ Transform2D xform = get_global_transform_with_canvas();
if (stretch) {
Transform2D scale_xf;
@@ -203,7 +203,7 @@ void SubViewportContainer::unhandled_input(const Ref<InputEvent> &p_event) {
return;
}
- Transform2D xform = get_global_transform();
+ Transform2D xform = get_global_transform_with_canvas();
if (stretch) {
Transform2D scale_xf;
@@ -223,6 +223,23 @@ void SubViewportContainer::unhandled_input(const Ref<InputEvent> &p_event) {
}
}
+TypedArray<String> SubViewportContainer::get_configuration_warnings() const {
+ TypedArray<String> warnings = Node::get_configuration_warnings();
+
+ bool has_viewport = false;
+ for (int i = 0; i < get_child_count(); i++) {
+ if (Object::cast_to<SubViewport>(get_child(i))) {
+ has_viewport = true;
+ break;
+ }
+ }
+ if (!has_viewport) {
+ warnings.push_back(RTR("This node doesn't have a SubViewport as child, so it can't display its intended content.\nConsider adding a SubViewport as a child to provide something displayable."));
+ }
+
+ return warnings;
+}
+
void SubViewportContainer::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_stretch", "enable"), &SubViewportContainer::set_stretch);
ClassDB::bind_method(D_METHOD("is_stretch_enabled"), &SubViewportContainer::is_stretch_enabled);
diff --git a/scene/gui/subviewport_container.h b/scene/gui/subviewport_container.h
index f52f01e4e2..55b7802aa4 100644
--- a/scene/gui/subviewport_container.h
+++ b/scene/gui/subviewport_container.h
@@ -58,6 +58,8 @@ public:
virtual Vector<int> get_allowed_size_flags_horizontal() const override;
virtual Vector<int> get_allowed_size_flags_vertical() const override;
+ TypedArray<String> get_configuration_warnings() const override;
+
SubViewportContainer();
};
diff --git a/scene/gui/tab_bar.cpp b/scene/gui/tab_bar.cpp
index 8128bbd11d..b96ba0ebf9 100644
--- a/scene/gui/tab_bar.cpp
+++ b/scene/gui/tab_bar.cpp
@@ -35,6 +35,7 @@
#include "scene/gui/box_container.h"
#include "scene/gui/label.h"
#include "scene/gui/texture_rect.h"
+#include "scene/main/viewport.h"
Size2 TabBar::get_minimum_size() const {
Size2 ms;
@@ -48,7 +49,7 @@ Size2 TabBar::get_minimum_size() const {
Ref<StyleBox> tab_disabled = get_theme_stylebox(SNAME("tab_disabled"));
Ref<StyleBox> button_highlight = get_theme_stylebox(SNAME("button_highlight"));
Ref<Texture2D> close = get_theme_icon(SNAME("close"));
- int hseparation = get_theme_constant(SNAME("hseparation"));
+ int hseparation = get_theme_constant(SNAME("h_separation"));
int y_margin = MAX(MAX(tab_unselected->get_minimum_size().height, tab_selected->get_minimum_size().height), tab_disabled->get_minimum_size().height);
@@ -158,7 +159,13 @@ void TabBar::gui_input(const Ref<InputEvent> &p_event) {
}
}
+ if (get_viewport()->gui_is_dragging() && can_drop_data(pos, get_viewport()->gui_get_drag_data())) {
+ dragging_valid_tab = true;
+ update();
+ }
+
_update_hover();
+
return;
}
@@ -333,6 +340,13 @@ void TabBar::_notification(int p_what) {
}
} break;
+ case NOTIFICATION_DRAG_END: {
+ if (dragging_valid_tab) {
+ dragging_valid_tab = false;
+ update();
+ }
+ } break;
+
case NOTIFICATION_DRAW: {
if (tabs.is_empty()) {
return;
@@ -346,8 +360,6 @@ void TabBar::_notification(int p_what) {
Color font_disabled_color = get_theme_color(SNAME("font_disabled_color"));
Ref<Texture2D> incr = get_theme_icon(SNAME("increment"));
Ref<Texture2D> decr = get_theme_icon(SNAME("decrement"));
- Ref<Texture2D> incr_hl = get_theme_icon(SNAME("increment_highlight"));
- Ref<Texture2D> decr_hl = get_theme_icon(SNAME("decrement_highlight"));
bool rtl = is_layout_rtl();
Vector2 size = get_size();
@@ -391,7 +403,10 @@ void TabBar::_notification(int p_what) {
}
if (buttons_visible) {
- int vofs = (get_size().height - incr->get_size().height) / 2;
+ Ref<Texture2D> incr_hl = get_theme_icon(SNAME("increment_highlight"));
+ Ref<Texture2D> decr_hl = get_theme_icon(SNAME("decrement_highlight"));
+
+ int vofs = (size.height - incr->get_size().height) / 2;
if (rtl) {
if (missing_right) {
@@ -419,6 +434,39 @@ void TabBar::_notification(int p_what) {
}
}
}
+
+ if (dragging_valid_tab) {
+ int x;
+
+ int tab_hover = get_hovered_tab();
+ if (tab_hover != -1) {
+ Rect2 tab_rect = get_tab_rect(tab_hover);
+
+ x = tab_rect.position.x;
+ if (get_local_mouse_position().x > x + tab_rect.size.width / 2) {
+ x += tab_rect.size.width;
+ }
+ } else {
+ if (rtl ^ (get_local_mouse_position().x < get_tab_rect(0).position.x)) {
+ x = get_tab_rect(0).position.x;
+ if (rtl) {
+ x += get_tab_rect(0).size.width;
+ }
+ } else {
+ Rect2 tab_rect = get_tab_rect(get_tab_count() - 1);
+
+ x = tab_rect.position.x;
+ if (!rtl) {
+ x += tab_rect.size.width;
+ }
+ }
+ }
+
+ Ref<Texture2D> drop_mark = get_theme_icon(SNAME("drop_mark"));
+ Color drop_mark_color = get_theme_color(SNAME("drop_mark_color"));
+
+ drop_mark->draw(get_canvas_item(), Point2(x - drop_mark->get_width() / 2, (size.height - drop_mark->get_height()) / 2), drop_mark_color);
+ }
} break;
}
}
@@ -429,7 +477,7 @@ void TabBar::_draw_tab(Ref<StyleBox> &p_tab_style, Color &p_font_color, int p_in
Color font_outline_color = get_theme_color(SNAME("font_outline_color"));
int outline_size = get_theme_constant(SNAME("outline_size"));
- int hseparation = get_theme_constant(SNAME("hseparation"));
+ int hseparation = get_theme_constant(SNAME("h_separation"));
Rect2 sb_rect = Rect2(p_x, 0, tabs[p_index].size_cache, get_size().height);
p_tab_style->draw(ci, sb_rect);
@@ -906,6 +954,8 @@ void TabBar::_on_mouse_exited() {
cb_hover = -1;
hover = -1;
highlight_arrow = -1;
+ dragging_valid_tab = false;
+
update();
}
@@ -1057,13 +1107,29 @@ void TabBar::drop_data(const Point2 &p_point, const Variant &p_data) {
NodePath to_path = get_path();
if (from_path == to_path) {
- if (hover_now < 0) {
- hover_now = get_tab_count() - 1;
+ if (tab_from_id == hover_now) {
+ return;
+ }
+
+ // Drop the new tab to the left or right depending on where the target tab is being hovered.
+ if (hover_now != -1) {
+ Rect2 tab_rect = get_tab_rect(hover_now);
+ if (is_layout_rtl() ^ (p_point.x <= tab_rect.position.x + tab_rect.size.width / 2)) {
+ if (hover_now > tab_from_id) {
+ hover_now -= 1;
+ }
+ } else if (tab_from_id > hover_now) {
+ hover_now += 1;
+ }
+ } else {
+ hover_now = is_layout_rtl() ^ (p_point.x < get_tab_rect(0).position.x) ? 0 : get_tab_count() - 1;
}
move_tab(tab_from_id, hover_now);
- emit_signal(SNAME("active_tab_rearranged"), hover_now);
- set_current_tab(hover_now);
+ if (!is_tab_disabled(hover_now)) {
+ emit_signal(SNAME("active_tab_rearranged"), hover_now);
+ set_current_tab(hover_now);
+ }
} else if (get_tabs_rearrange_group() != -1) {
// Drag and drop between Tabs.
@@ -1075,11 +1141,17 @@ void TabBar::drop_data(const Point2 &p_point, const Variant &p_data) {
return;
}
- Tab moving_tab = from_tabs->tabs[tab_from_id];
- if (hover_now < 0) {
- hover_now = get_tab_count();
+ // Drop the new tab to the left or right depending on where the target tab is being hovered.
+ if (hover_now != -1) {
+ Rect2 tab_rect = get_tab_rect(hover_now);
+ if (is_layout_rtl() ^ (p_point.x > tab_rect.position.x + tab_rect.size.width / 2)) {
+ hover_now += 1;
+ }
+ } else {
+ hover_now = is_layout_rtl() ^ (p_point.x < get_tab_rect(0).position.x) ? 0 : get_tab_count();
}
+ Tab moving_tab = from_tabs->tabs[tab_from_id];
from_tabs->remove_tab(tab_from_id);
tabs.insert(hover_now, moving_tab);
@@ -1092,7 +1164,13 @@ void TabBar::drop_data(const Point2 &p_point, const Variant &p_data) {
}
}
- set_current_tab(hover_now);
+ if (!is_tab_disabled(hover_now)) {
+ set_current_tab(hover_now);
+ } else {
+ _update_cache();
+ update();
+ }
+
update_minimum_size();
if (tabs.size() == 1) {
@@ -1194,7 +1272,7 @@ int TabBar::get_tab_width(int p_idx) const {
Ref<StyleBox> tab_unselected = get_theme_stylebox(SNAME("tab_unselected"));
Ref<StyleBox> tab_selected = get_theme_stylebox(SNAME("tab_selected"));
Ref<StyleBox> tab_disabled = get_theme_stylebox(SNAME("tab_disabled"));
- int hseparation = get_theme_constant(SNAME("hseparation"));
+ int hseparation = get_theme_constant(SNAME("h_separation"));
Ref<StyleBox> style;
diff --git a/scene/gui/tab_bar.h b/scene/gui/tab_bar.h
index e0c4ba85ef..548a2e62af 100644
--- a/scene/gui/tab_bar.h
+++ b/scene/gui/tab_bar.h
@@ -101,6 +101,7 @@ private:
int max_width = 0;
bool scrolling_enabled = true;
bool drag_to_rearrange_enabled = false;
+ bool dragging_valid_tab = false;
bool scroll_to_selected = true;
int tabs_rearrange_group = -1;
diff --git a/scene/gui/tab_container.cpp b/scene/gui/tab_container.cpp
index ee61c862b7..3a3a84b481 100644
--- a/scene/gui/tab_container.cpp
+++ b/scene/gui/tab_container.cpp
@@ -173,9 +173,9 @@ void TabContainer::_notification(int p_what) {
int x = is_layout_rtl() ? 0 : get_size().width - menu->get_width();
if (menu_hovered) {
- menu_hl->draw(get_canvas_item(), Size2(x, (header_height - menu_hl->get_height()) / 2));
+ menu_hl->draw(get_canvas_item(), Point2(x, (header_height - menu_hl->get_height()) / 2));
} else {
- menu->draw(get_canvas_item(), Size2(x, (header_height - menu->get_height()) / 2));
+ menu->draw(get_canvas_item(), Point2(x, (header_height - menu->get_height()) / 2));
}
}
} break;
@@ -201,13 +201,15 @@ void TabContainer::_on_theme_changed() {
tab_bar->add_theme_icon_override(SNAME("increment_highlight"), get_theme_icon(SNAME("increment_highlight")));
tab_bar->add_theme_icon_override(SNAME("decrement"), get_theme_icon(SNAME("decrement")));
tab_bar->add_theme_icon_override(SNAME("decrement_highlight"), get_theme_icon(SNAME("decrement_highlight")));
+ tab_bar->add_theme_icon_override(SNAME("drop_mark"), get_theme_icon(SNAME("drop_mark")));
+ tab_bar->add_theme_color_override(SNAME("drop_mark_color"), get_theme_color(SNAME("drop_mark_color")));
tab_bar->add_theme_color_override(SNAME("font_selected_color"), get_theme_color(SNAME("font_selected_color")));
tab_bar->add_theme_color_override(SNAME("font_unselected_color"), get_theme_color(SNAME("font_unselected_color")));
tab_bar->add_theme_color_override(SNAME("font_disabled_color"), get_theme_color(SNAME("font_disabled_color")));
tab_bar->add_theme_color_override(SNAME("font_outline_color"), get_theme_color(SNAME("font_outline_color")));
tab_bar->add_theme_font_override(SNAME("font"), get_theme_font(SNAME("font")));
- tab_bar->add_theme_constant_override(SNAME("font_size"), get_theme_constant(SNAME("font_size")));
- tab_bar->add_theme_constant_override(SNAME("icon_separation"), get_theme_constant(SNAME("icon_separation")));
+ tab_bar->add_theme_font_size_override(SNAME("font_size"), get_theme_font_size(SNAME("font_size")));
+ tab_bar->add_theme_constant_override(SNAME("h_separation"), get_theme_constant(SNAME("icon_separation")));
tab_bar->add_theme_constant_override(SNAME("outline_size"), get_theme_constant(SNAME("outline_size")));
_update_margins();
@@ -384,8 +386,6 @@ void TabContainer::_drop_data_fw(const Point2 &p_point, const Variant &p_data, C
return;
}
- int hover_now = get_tab_idx_at_point(p_point);
-
Dictionary d = p_data;
if (!d.has("type")) {
return;
@@ -393,11 +393,27 @@ void TabContainer::_drop_data_fw(const Point2 &p_point, const Variant &p_data, C
if (String(d["type"]) == "tabc_element") {
int tab_from_id = d["tabc_element"];
+ int hover_now = get_tab_idx_at_point(p_point);
NodePath from_path = d["from_path"];
NodePath to_path = get_path();
+
if (from_path == to_path) {
- if (hover_now < 0) {
- hover_now = get_tab_count() - 1;
+ if (tab_from_id == hover_now) {
+ return;
+ }
+
+ // 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));
@@ -407,16 +423,31 @@ void TabContainer::_drop_data_fw(const Point2 &p_point, const Variant &p_data, C
} 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);
+ bool tab_disabled = from_tabc->is_tab_disabled(tab_from_id);
+
+ // Drop the new tab to the left or right depending on where the target tab is being hovered.
+ if (hover_now != -1) {
+ Rect2 tab_rect = tab_bar->get_tab_rect(hover_now);
+ if (is_layout_rtl() ^ (p_point.x > tab_rect.position.x + tab_rect.size.width / 2)) {
+ hover_now += 1;
+ }
+ } else {
+ hover_now = is_layout_rtl() ^ (p_point.x < tab_bar->get_tab_rect(0).position.x) ? 0 : get_tab_count();
+ }
+
Control *moving_tabc = from_tabc->get_tab_control(tab_from_id);
from_tabc->remove_child(moving_tabc);
add_child(moving_tabc, true);
- if (hover_now < 0) {
- hover_now = get_tab_count() - 1;
- }
+ set_tab_title(get_tab_count() - 1, tab_title);
+ set_tab_disabled(get_tab_count() - 1, tab_disabled);
move_child(moving_tabc, get_tab_control(hover_now)->get_index(false));
if (!is_tab_disabled(hover_now)) {
@@ -441,6 +472,10 @@ void TabContainer::_on_tab_selected(int p_tab) {
emit_signal(SNAME("tab_selected"), p_tab);
}
+void TabContainer::_on_tab_button_pressed(int p_tab) {
+ emit_signal(SNAME("tab_button_pressed"), p_tab);
+}
+
void TabContainer::_refresh_tab_names() {
Vector<Control *> controls = _get_tab_controls();
for (int i = 0; i < controls.size(); i++) {
@@ -466,6 +501,9 @@ void TabContainer::add_child_notify(Node *p_child) {
tab_bar->add_tab(p_child->get_name());
_update_margins();
+ if (get_tab_count() == 1) {
+ update();
+ }
p_child->connect("renamed", callable_mp(this, &TabContainer::_refresh_tab_names));
@@ -514,6 +552,9 @@ void TabContainer::remove_child_notify(Node *p_child) {
tab_bar->remove_tab(get_tab_idx_from_control(c));
_update_margins();
+ if (get_tab_count() == 0) {
+ update();
+ }
if (p_child->has_meta("_tab_name")) {
p_child->remove_meta("_tab_name");
@@ -696,6 +737,17 @@ bool TabContainer::is_tab_hidden(int p_tab) const {
return tab_bar->is_tab_hidden(p_tab);
}
+void TabContainer::set_tab_button_icon(int p_tab, const Ref<Texture2D> &p_icon) {
+ tab_bar->set_tab_button_icon(p_tab, p_icon);
+
+ _update_margins();
+ _repaint();
+}
+
+Ref<Texture2D> TabContainer::get_tab_button_icon(int p_tab) const {
+ return tab_bar->get_tab_button_icon(p_tab);
+}
+
void TabContainer::get_translatable_strings(List<String> *p_strings) const {
Vector<Control *> controls = _get_tab_controls();
for (int i = 0; i < controls.size(); i++) {
@@ -840,6 +892,8 @@ void TabContainer::_bind_methods() {
ClassDB::bind_method(D_METHOD("is_tab_disabled", "tab_idx"), &TabContainer::is_tab_disabled);
ClassDB::bind_method(D_METHOD("set_tab_hidden", "tab_idx", "hidden"), &TabContainer::set_tab_hidden);
ClassDB::bind_method(D_METHOD("is_tab_hidden", "tab_idx"), &TabContainer::is_tab_hidden);
+ ClassDB::bind_method(D_METHOD("set_tab_button_icon", "tab_idx", "icon"), &TabContainer::set_tab_button_icon);
+ ClassDB::bind_method(D_METHOD("get_tab_button_icon", "tab_idx"), &TabContainer::get_tab_button_icon);
ClassDB::bind_method(D_METHOD("get_tab_idx_at_point", "point"), &TabContainer::get_tab_idx_at_point);
ClassDB::bind_method(D_METHOD("get_tab_idx_from_control", "control"), &TabContainer::get_tab_idx_from_control);
ClassDB::bind_method(D_METHOD("set_popup", "popup"), &TabContainer::set_popup);
@@ -859,6 +913,7 @@ void TabContainer::_bind_methods() {
ADD_SIGNAL(MethodInfo("tab_changed", PropertyInfo(Variant::INT, "tab")));
ADD_SIGNAL(MethodInfo("tab_selected", PropertyInfo(Variant::INT, "tab")));
+ ADD_SIGNAL(MethodInfo("tab_button_pressed", PropertyInfo(Variant::INT, "tab")));
ADD_SIGNAL(MethodInfo("pre_popup_pressed"));
ADD_PROPERTY(PropertyInfo(Variant::INT, "tab_alignment", PROPERTY_HINT_ENUM, "Left,Center,Right"), "set_tab_alignment", "get_tab_alignment");
@@ -878,6 +933,7 @@ TabContainer::TabContainer() {
tab_bar->set_anchors_and_offsets_preset(Control::PRESET_TOP_WIDE);
tab_bar->connect("tab_changed", callable_mp(this, &TabContainer::_on_tab_changed));
tab_bar->connect("tab_selected", callable_mp(this, &TabContainer::_on_tab_selected));
+ tab_bar->connect("tab_button_pressed", callable_mp(this, &TabContainer::_on_tab_button_pressed));
connect("mouse_exited", callable_mp(this, &TabContainer::_on_mouse_exited));
}
diff --git a/scene/gui/tab_container.h b/scene/gui/tab_container.h
index c54934b37b..9adaa0d844 100644
--- a/scene/gui/tab_container.h
+++ b/scene/gui/tab_container.h
@@ -38,7 +38,7 @@
class TabContainer : public Container {
GDCLASS(TabContainer, Container);
- TabBar *tab_bar;
+ TabBar *tab_bar = nullptr;
bool tabs_visible = true;
bool all_tabs_in_front = false;
bool menu_hovered = false;
@@ -56,6 +56,7 @@ class TabContainer : public Container {
void _on_mouse_exited();
void _on_tab_changed(int p_tab);
void _on_tab_selected(int p_tab);
+ void _on_tab_button_pressed(int p_tab);
Variant _get_drag_data_fw(const Point2 &p_point, Control *p_from_control);
bool _can_drop_data_fw(const Point2 &p_point, const Variant &p_data, Control *p_from_control) const;
@@ -97,6 +98,9 @@ public:
void set_tab_hidden(int p_tab, bool p_hidden);
bool is_tab_hidden(int p_tab) const;
+ void set_tab_button_icon(int p_tab, const Ref<Texture2D> &p_icon);
+ Ref<Texture2D> get_tab_button_icon(int p_tab) const;
+
int get_tab_count() const;
void set_current_tab(int p_current);
int get_current_tab() const;
diff --git a/scene/gui/text_edit.cpp b/scene/gui/text_edit.cpp
index 3c80e3f987..1a439a5c1d 100644
--- a/scene/gui/text_edit.cpp
+++ b/scene/gui/text_edit.cpp
@@ -471,6 +471,11 @@ void TextEdit::_notification(int p_what) {
// To ensure minimap is responsive override the speed setting.
double vel = ((target_y / dist) * ((minimap_clicked) ? 3000 : v_scroll_speed)) * get_physics_process_delta_time();
+ // Prevent too small velocity to block scrolling
+ if (Math::abs(vel) < v_scroll->get_step()) {
+ vel = v_scroll->get_step() * SIGN(vel);
+ }
+
if (Math::abs(vel) >= dist) {
set_v_scroll(target_v_scroll);
scrolling = false;
@@ -1423,7 +1428,7 @@ void TextEdit::_notification(int p_what) {
}
}
- if (draw_placeholder) {
+ if (!draw_placeholder) {
line_drawing_cache[line] = cache_entry;
}
}
@@ -1537,6 +1542,21 @@ void TextEdit::_notification(int p_what) {
}
}
+void TextEdit::unhandled_key_input(const Ref<InputEvent> &p_event) {
+ Ref<InputEventKey> k = p_event;
+
+ if (k.is_valid()) {
+ if (!k->is_pressed()) {
+ return;
+ }
+ // Handle Unicode (with modifiers active, process after shortcuts).
+ if (has_focus() && editable && (k->get_unicode() >= 32)) {
+ handle_unicode_input(k->get_unicode());
+ accept_event();
+ }
+ }
+}
+
void TextEdit::gui_input(const Ref<InputEvent> &p_gui_input) {
ERR_FAIL_COND(p_gui_input.is_null());
@@ -1620,7 +1640,7 @@ void TextEdit::gui_input(const Ref<InputEvent> &p_gui_input) {
set_caret_column(col);
selection.drag_attempt = false;
- if (mb->is_shift_pressed() && (caret.column != prev_col || caret.line != prev_line)) {
+ if (selecting_enabled && mb->is_shift_pressed() && (caret.column != prev_col || caret.line != prev_line)) {
if (!selection.active) {
selection.active = true;
selection.selecting_mode = SelectionMode::SELECTION_MODE_POINTER;
@@ -1688,7 +1708,6 @@ void TextEdit::gui_input(const Ref<InputEvent> &p_gui_input) {
last_dblclk = OS::get_singleton()->get_ticks_msec();
last_dblclk_pos = mb->get_position();
}
-
update();
}
@@ -1696,7 +1715,7 @@ void TextEdit::gui_input(const Ref<InputEvent> &p_gui_input) {
paste_primary_clipboard();
}
- if (mb->get_button_index() == MouseButton::RIGHT && context_menu_enabled) {
+ if (mb->get_button_index() == MouseButton::RIGHT && (context_menu_enabled || is_move_caret_on_right_click_enabled())) {
_reset_caret_blink_timer();
Point2i pos = get_line_column_at_pos(mpos);
@@ -1721,11 +1740,13 @@ void TextEdit::gui_input(const Ref<InputEvent> &p_gui_input) {
}
}
- _generate_context_menu();
- menu->set_position(get_screen_position() + mpos);
- menu->reset_size();
- menu->popup();
- grab_focus();
+ if (context_menu_enabled) {
+ _generate_context_menu();
+ menu->set_position(get_screen_position() + mpos);
+ menu->reset_size();
+ menu->popup();
+ grab_focus();
+ }
}
} else {
if (mb->get_button_index() == MouseButton::LEFT) {
@@ -1924,44 +1945,45 @@ void TextEdit::gui_input(const Ref<InputEvent> &p_gui_input) {
return;
}
- // SELECT ALL, SELECT WORD UNDER CARET, CUT, COPY, PASTE.
-
- if (k->is_action("ui_text_select_all", true)) {
- select_all();
- accept_event();
- return;
- }
- if (k->is_action("ui_text_select_word_under_caret", true)) {
- select_word_under_caret();
- accept_event();
- return;
- }
- if (k->is_action("ui_cut", true)) {
- cut();
- accept_event();
- return;
- }
- if (k->is_action("ui_copy", true)) {
- copy();
- accept_event();
- return;
- }
- if (k->is_action("ui_paste", true)) {
- paste();
- accept_event();
- return;
- }
+ if (is_shortcut_keys_enabled()) {
+ // SELECT ALL, SELECT WORD UNDER CARET, CUT, COPY, PASTE.
+ if (k->is_action("ui_text_select_all", true)) {
+ select_all();
+ accept_event();
+ return;
+ }
+ if (k->is_action("ui_text_select_word_under_caret", true)) {
+ select_word_under_caret();
+ accept_event();
+ return;
+ }
+ if (k->is_action("ui_cut", true)) {
+ cut();
+ accept_event();
+ return;
+ }
+ if (k->is_action("ui_copy", true)) {
+ copy();
+ accept_event();
+ return;
+ }
+ if (k->is_action("ui_paste", true)) {
+ paste();
+ accept_event();
+ return;
+ }
- // UNDO/REDO.
- if (k->is_action("ui_undo", true)) {
- undo();
- accept_event();
- return;
- }
- if (k->is_action("ui_redo", true)) {
- redo();
- accept_event();
- return;
+ // UNDO/REDO.
+ if (k->is_action("ui_undo", true)) {
+ undo();
+ accept_event();
+ return;
+ }
+ if (k->is_action("ui_redo", true)) {
+ redo();
+ accept_event();
+ return;
+ }
}
// MISC.
@@ -2133,16 +2155,21 @@ void TextEdit::_move_caret_left(bool p_select, bool p_move_by_word) {
if (p_move_by_word) {
int cc = caret.column;
-
+ // If the caret is at the start of the line, and not on the first line, move it up to the end of the previous line.
if (cc == 0 && caret.line > 0) {
set_caret_line(caret.line - 1);
set_caret_column(text[caret.line].length());
} else {
PackedInt32Array words = TS->shaped_text_get_word_breaks(text.get_line_data(caret.line)->get_rid());
- for (int i = words.size() - 2; i >= 0; i = i - 2) {
- if (words[i] < cc) {
- cc = words[i];
- break;
+ if (words.is_empty() || cc <= words[0]) {
+ // This solves the scenario where there are no words but glyfs that can be ignored.
+ cc = 0;
+ } else {
+ for (int i = words.size() - 2; i >= 0; i = i - 2) {
+ if (words[i] < cc) {
+ cc = words[i];
+ break;
+ }
}
}
set_caret_column(cc);
@@ -2184,16 +2211,21 @@ void TextEdit::_move_caret_right(bool p_select, bool p_move_by_word) {
if (p_move_by_word) {
int cc = caret.column;
-
+ // If the caret is at the end of the line, and not on the last line, move it down to the beginning of the next line.
if (cc == text[caret.line].length() && caret.line < text.size() - 1) {
set_caret_line(caret.line + 1);
set_caret_column(0);
} else {
PackedInt32Array words = TS->shaped_text_get_word_breaks(text.get_line_data(caret.line)->get_rid());
- for (int i = 1; i < words.size(); i = i + 2) {
- if (words[i] > cc) {
- cc = words[i];
- break;
+ if (words.is_empty() || cc >= words[words.size() - 1]) {
+ // This solves the scenario where there are no words but glyfs that can be ignored.
+ cc = text[caret.line].length();
+ } else {
+ for (int i = 1; i < words.size(); i = i + 2) {
+ if (words[i] > cc) {
+ cc = words[i];
+ break;
+ }
}
}
set_caret_column(cc);
@@ -2283,15 +2315,7 @@ void TextEdit::_move_caret_to_line_start(bool p_select) {
}
if (caret.column == row_start_col || wi == 0) {
// Compute whitespace symbols sequence length.
- int current_line_whitespace_len = 0;
- while (current_line_whitespace_len < text[caret.line].length()) {
- char32_t c = text[caret.line][current_line_whitespace_len];
- if (c != '\t' && c != ' ') {
- break;
- }
- current_line_whitespace_len++;
- }
-
+ int current_line_whitespace_len = get_first_non_whitespace_column(caret.line);
if (get_caret_column() == current_line_whitespace_len) {
set_caret_column(0);
} else {
@@ -2364,11 +2388,11 @@ void TextEdit::_move_caret_page_down(bool p_select) {
}
void TextEdit::_do_backspace(bool p_word, bool p_all_to_left) {
- if (!editable) {
+ if (!editable || (caret.column == 0 && caret.line == 0)) {
return;
}
- if (has_selection() || (!p_all_to_left && !p_word)) {
+ if (has_selection() || (!p_all_to_left && !p_word) || caret.column == 0) {
backspace();
return;
}
@@ -2381,20 +2405,30 @@ void TextEdit::_do_backspace(bool p_word, bool p_all_to_left) {
}
if (p_word) {
- int line = caret.line;
int column = caret.column;
-
- PackedInt32Array words = TS->shaped_text_get_word_breaks(text.get_line_data(line)->get_rid());
- for (int i = words.size() - 2; i >= 0; i = i - 2) {
- if (words[i] < column) {
- column = words[i];
- break;
+ // Check for the case "<word><space><caret>" and ignore the space.
+ // No need to check for column being 0 since it is cheked above.
+ if (is_whitespace(text[caret.line][caret.column - 1])) {
+ column -= 1;
+ }
+ // Get a list with the indices of the word bounds of the given text line.
+ const PackedInt32Array words = TS->shaped_text_get_word_breaks(text.get_line_data(caret.line)->get_rid());
+ if (words.is_empty() || column <= words[0]) {
+ // If "words" is empty, meaning no words are left, we can remove everything until the begining of the line.
+ column = 0;
+ } else {
+ // Otherwise search for the first word break that is smaller than the index from we're currentlu deleteing
+ for (int i = words.size() - 2; i >= 0; i = i - 2) {
+ if (words[i] < column) {
+ column = words[i];
+ break;
+ }
}
}
- _remove_text(line, column, caret.line, caret.column);
+ _remove_text(caret.line, column, caret.line, caret.column);
- set_caret_line(line, false);
+ set_caret_line(caret.line, false);
set_caret_column(column);
return;
}
@@ -2419,6 +2453,10 @@ void TextEdit::_delete(bool p_word, bool p_all_to_right) {
int next_column;
if (p_all_to_right) {
+ if (caret.column == curline_len) {
+ return;
+ }
+
// Delete everything to right of caret
next_column = curline_len;
next_line = caret.line;
@@ -2818,7 +2856,7 @@ String TextEdit::get_language() const {
return language;
}
-void TextEdit::set_structured_text_bidi_override(Control::StructuredTextParser p_parser) {
+void TextEdit::set_structured_text_bidi_override(TextServer::StructuredTextParser p_parser) {
if (st_parser != p_parser) {
st_parser = p_parser;
for (int i = 0; i < text.size(); i++) {
@@ -2828,7 +2866,7 @@ void TextEdit::set_structured_text_bidi_override(Control::StructuredTextParser p
}
}
-Control::StructuredTextParser TextEdit::get_structured_text_bidi_override() const {
+TextServer::StructuredTextParser TextEdit::get_structured_text_bidi_override() const {
return st_parser;
}
@@ -3081,6 +3119,7 @@ void TextEdit::insert_line_at(int p_at, const String &p_text) {
++selection.to_line;
}
}
+ update();
}
void TextEdit::insert_text_at_caret(const String &p_text) {
@@ -3461,9 +3500,6 @@ void TextEdit::undo() {
TextOperation op = undo_stack_pos->get();
_do_text_op(op, true);
- if (op.type != TextOperation::TYPE_INSERT && (op.from_line != op.to_line || op.to_column != op.from_column + 1)) {
- select(op.from_line, op.from_column, op.to_line, op.to_column);
- }
current_op.version = op.prev_version;
if (undo_stack_pos->get().chain_backward) {
@@ -3479,6 +3515,10 @@ void TextEdit::undo() {
}
}
+ if (op.type != TextOperation::TYPE_INSERT && (op.from_line != op.to_line || op.to_column != op.from_column + 1)) {
+ select(op.from_line, op.from_column, op.to_line, op.to_column);
+ }
+
_update_scrollbars();
if (undo_stack_pos->get().type == TextOperation::TYPE_REMOVE) {
set_caret_line(undo_stack_pos->get().to_line, false);
@@ -3775,7 +3815,7 @@ Point2i TextEdit::get_line_column_at_pos(const Point2i &p_pos, bool p_allow_out_
Point2i TextEdit::get_pos_at_line_column(int p_line, int p_column) const {
Rect2i rect = get_rect_at_line_column(p_line, p_column);
- return rect.position + Vector2i(0, get_line_height());
+ return rect.position.x == -1 ? rect.position : rect.position + Vector2i(0, get_line_height());
}
Rect2i TextEdit::get_rect_at_line_column(int p_line, int p_column) const {
@@ -4013,12 +4053,12 @@ void TextEdit::set_caret_column(int p_col, bool p_adjust_viewport) {
if (p_col < 0) {
p_col = 0;
}
+ if (p_col > get_line(caret.line).length()) {
+ p_col = get_line(caret.line).length();
+ }
bool caret_moved = caret.column != p_col;
caret.column = p_col;
- if (caret.column > get_line(caret.line).length()) {
- caret.column = get_line(caret.line).length();
- }
caret.last_fit_x = _get_column_x_offset_for_line(caret.column, caret.line);
@@ -4147,13 +4187,18 @@ void TextEdit::select_word_under_caret() {
int end = 0;
const PackedInt32Array words = TS->shaped_text_get_word_breaks(text.get_line_data(caret.line)->get_rid());
for (int i = 0; i < words.size(); i = i + 2) {
- if ((words[i] < caret.column && words[i + 1] > caret.column) || (i == words.size() - 2 && caret.column == words[i + 1])) {
+ if ((words[i] <= caret.column && words[i + 1] >= caret.column) || (i == words.size() - 2 && caret.column == words[i + 1])) {
begin = words[i];
end = words[i + 1];
break;
}
}
+ // No word found.
+ if (begin == 0 && end == 0) {
+ return;
+ }
+
select(caret.line, begin, caret.line, end);
/* Move the caret to the end of the word for easier editing. */
set_caret_column(end, false);
@@ -4229,10 +4274,12 @@ String TextEdit::get_selected_text() const {
}
int TextEdit::get_selection_line() const {
+ ERR_FAIL_COND_V(!selection.active, -1);
return selection.selecting_line;
}
int TextEdit::get_selection_column() const {
+ ERR_FAIL_COND_V(!selection.active, -1);
return selection.selecting_column;
}
@@ -4390,6 +4437,8 @@ int TextEdit::get_h_scroll() const {
}
void TextEdit::set_v_scroll_speed(float p_speed) {
+ // Prevent setting a vertical scroll speed value under 1.0
+ ERR_FAIL_COND(p_speed < 1.0);
v_scroll_speed = p_speed;
}
@@ -4432,9 +4481,13 @@ void TextEdit::set_line_as_center_visible(int p_line, int p_wrap_index) {
ERR_FAIL_COND(p_wrap_index > get_line_wrap_count(p_line));
int visible_rows = get_visible_line_count();
- Point2i next_line = get_next_visible_line_index_offset_from(p_line, p_wrap_index, -visible_rows / 2);
+ Point2i next_line = get_next_visible_line_index_offset_from(p_line, p_wrap_index, (-visible_rows / 2) - 1);
int first_line = p_line - next_line.x + 1;
+ if (first_line < 0) {
+ set_v_scroll(0);
+ return;
+ }
set_v_scroll(get_scroll_pos_for_line(first_line, next_line.y));
}
@@ -4446,6 +4499,12 @@ void TextEdit::set_line_as_last_visible(int p_line, int p_wrap_index) {
Point2i next_line = get_next_visible_line_index_offset_from(p_line, p_wrap_index, -get_visible_line_count() - 1);
int first_line = p_line - next_line.x + 1;
+ // Adding _get_visible_lines_offset is not 100% correct as we end up showing almost p_line + 1, however, it provides a
+ // better user experience. Therefore we need to special case < visible line count, else showing line 0 is impossible.
+ if (get_visible_line_count_in_range(0, p_line) < get_visible_line_count() + 1) {
+ set_v_scroll(0);
+ return;
+ }
set_v_scroll(get_scroll_pos_for_line(first_line, next_line.y) + _get_visible_lines_offset());
}
@@ -5855,7 +5914,7 @@ int TextEdit::_get_column_x_offset_for_line(int p_char, int p_line) const {
int row = 0;
Vector<Vector2i> rows2 = text.get_line_wrap_ranges(p_line);
for (int i = 0; i < rows2.size(); i++) {
- if ((p_char >= rows2[i].x) && (p_char < rows2[i].y)) {
+ if ((p_char >= rows2[i].x) && (p_char <= rows2[i].y)) {
row = i;
break;
}
@@ -5939,8 +5998,8 @@ void TextEdit::_update_selection_mode_word() {
selection.selected_word_beg = beg;
selection.selected_word_end = end;
selection.selected_word_origin = beg;
- set_caret_line(selection.to_line, false);
- set_caret_column(selection.to_column);
+ set_caret_line(line, false);
+ set_caret_column(end);
} else {
if ((col <= selection.selected_word_origin && line == selection.selecting_line) || line < selection.selecting_line) {
selection.selecting_column = selection.selected_word_end;
@@ -5996,6 +6055,10 @@ void TextEdit::_update_selection_mode_line() {
}
void TextEdit::_pre_shift_selection() {
+ if (!selecting_enabled) {
+ return;
+ }
+
if (!selection.active || selection.selecting_mode == SelectionMode::SELECTION_MODE_NONE) {
selection.selecting_line = caret.line;
selection.selecting_column = caret.column;
@@ -6006,6 +6069,10 @@ void TextEdit::_pre_shift_selection() {
}
void TextEdit::_post_shift_selection() {
+ if (!selecting_enabled) {
+ return;
+ }
+
if (selection.active && selection.selecting_mode == SelectionMode::SELECTION_MODE_SHIFT) {
select(selection.selecting_line, selection.selecting_column, caret.line, caret.column);
update();
@@ -6592,6 +6659,7 @@ TextEdit::TextEdit(const String &p_placeholder) {
set_focus_mode(FOCUS_ALL);
_update_caches();
set_default_cursor_shape(CURSOR_IBEAM);
+ set_process_unhandled_key_input(true);
text.set_tab_size(text.get_tab_size());
diff --git a/scene/gui/text_edit.h b/scene/gui/text_edit.h
index 6deaf76e5e..a0ae9631f9 100644
--- a/scene/gui/text_edit.h
+++ b/scene/gui/text_edit.h
@@ -271,7 +271,7 @@ private:
Dictionary opentype_features;
String language = "";
- Control::StructuredTextParser st_parser = STRUCTURED_TEXT_DEFAULT;
+ TextServer::StructuredTextParser st_parser = TextServer::STRUCTURED_TEXT_DEFAULT;
Array st_args;
void _clear();
@@ -325,7 +325,7 @@ private:
List<TextOperation> undo_stack;
List<TextOperation>::Element *undo_stack_pos = nullptr;
- Timer *idle_detect;
+ Timer *idle_detect = nullptr;
uint32_t version = 0;
uint32_t saved_version = 0;
@@ -380,7 +380,7 @@ private:
bool draw_caret = true;
bool caret_blink_enabled = false;
- Timer *caret_blink_timer;
+ Timer *caret_blink_timer = nullptr;
bool move_caret_on_right_click = true;
@@ -426,7 +426,7 @@ private:
bool dragging_selection = false;
- Timer *click_select_held;
+ Timer *click_select_held = nullptr;
uint64_t last_dblclk = 0;
Vector2 last_dblclk_pos;
void _click_selection_held();
@@ -449,8 +449,8 @@ private:
void _update_caret_wrap_offset();
/* Viewport. */
- HScrollBar *h_scroll;
- VScrollBar *v_scroll;
+ HScrollBar *h_scroll = nullptr;
+ VScrollBar *v_scroll = nullptr;
bool scroll_past_end_of_file_enabled = false;
@@ -623,6 +623,7 @@ protected:
public:
/* General overrides. */
+ virtual void unhandled_key_input(const Ref<InputEvent> &p_event) override;
virtual void gui_input(const Ref<InputEvent> &p_gui_input) override;
virtual Size2 get_minimum_size() const override;
virtual bool is_text_field() const override;
@@ -650,8 +651,8 @@ public:
void set_language(const String &p_language);
String get_language() const;
- void set_structured_text_bidi_override(Control::StructuredTextParser p_parser);
- Control::StructuredTextParser get_structured_text_bidi_override() const;
+ void set_structured_text_bidi_override(TextServer::StructuredTextParser p_parser);
+ TextServer::StructuredTextParser get_structured_text_bidi_override() const;
void set_structured_text_bidi_override_options(Array p_args);
Array get_structured_text_bidi_override_options() const;
diff --git a/scene/gui/texture_progress_bar.cpp b/scene/gui/texture_progress_bar.cpp
index 043c0f464c..f79c68671c 100644
--- a/scene/gui/texture_progress_bar.cpp
+++ b/scene/gui/texture_progress_bar.cpp
@@ -629,26 +629,30 @@ void TextureProgressBar::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_nine_patch_stretch", "stretch"), &TextureProgressBar::set_nine_patch_stretch);
ClassDB::bind_method(D_METHOD("get_nine_patch_stretch"), &TextureProgressBar::get_nine_patch_stretch);
+ ADD_PROPERTY(PropertyInfo(Variant::INT, "fill_mode", PROPERTY_HINT_ENUM, "Left to Right,Right to Left,Top to Bottom,Bottom to Top,Clockwise,Counter Clockwise,Bilinear (Left and Right),Bilinear (Top and Bottom),Clockwise and Counter Clockwise"), "set_fill_mode", "get_fill_mode");
+ ADD_PROPERTY(PropertyInfo(Variant::BOOL, "nine_patch_stretch"), "set_nine_patch_stretch", "get_nine_patch_stretch");
+
+ ADD_GROUP("Stretch Margin", "stretch_margin_");
+ ADD_PROPERTYI(PropertyInfo(Variant::INT, "stretch_margin_left", PROPERTY_HINT_RANGE, "0,16384,1"), "set_stretch_margin", "get_stretch_margin", SIDE_LEFT);
+ ADD_PROPERTYI(PropertyInfo(Variant::INT, "stretch_margin_top", PROPERTY_HINT_RANGE, "0,16384,1"), "set_stretch_margin", "get_stretch_margin", SIDE_TOP);
+ ADD_PROPERTYI(PropertyInfo(Variant::INT, "stretch_margin_right", PROPERTY_HINT_RANGE, "0,16384,1"), "set_stretch_margin", "get_stretch_margin", SIDE_RIGHT);
+ ADD_PROPERTYI(PropertyInfo(Variant::INT, "stretch_margin_bottom", PROPERTY_HINT_RANGE, "0,16384,1"), "set_stretch_margin", "get_stretch_margin", SIDE_BOTTOM);
+
ADD_GROUP("Textures", "texture_");
ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "texture_under", PROPERTY_HINT_RESOURCE_TYPE, "Texture2D"), "set_under_texture", "get_under_texture");
ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "texture_over", PROPERTY_HINT_RESOURCE_TYPE, "Texture2D"), "set_over_texture", "get_over_texture");
ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "texture_progress", PROPERTY_HINT_RESOURCE_TYPE, "Texture2D"), "set_progress_texture", "get_progress_texture");
ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "texture_progress_offset"), "set_texture_progress_offset", "get_texture_progress_offset");
- ADD_PROPERTY(PropertyInfo(Variant::INT, "fill_mode", PROPERTY_HINT_ENUM, "Left to Right,Right to Left,Top to Bottom,Bottom to Top,Clockwise,Counter Clockwise,Bilinear (Left and Right),Bilinear (Top and Bottom),Clockwise and Counter Clockwise"), "set_fill_mode", "get_fill_mode");
+
ADD_GROUP("Tint", "tint_");
ADD_PROPERTY(PropertyInfo(Variant::COLOR, "tint_under"), "set_tint_under", "get_tint_under");
ADD_PROPERTY(PropertyInfo(Variant::COLOR, "tint_over"), "set_tint_over", "get_tint_over");
ADD_PROPERTY(PropertyInfo(Variant::COLOR, "tint_progress"), "set_tint_progress", "get_tint_progress");
+
ADD_GROUP("Radial Fill", "radial_");
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "radial_initial_angle", PROPERTY_HINT_RANGE, "0.0,360.0,0.1,slider"), "set_radial_initial_angle", "get_radial_initial_angle");
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "radial_fill_degrees", PROPERTY_HINT_RANGE, "0.0,360.0,0.1,slider"), "set_fill_degrees", "get_fill_degrees");
ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "radial_center_offset"), "set_radial_center_offset", "get_radial_center_offset");
- ADD_GROUP("Stretch", "stretch_");
- ADD_PROPERTY(PropertyInfo(Variant::BOOL, "nine_patch_stretch"), "set_nine_patch_stretch", "get_nine_patch_stretch");
- ADD_PROPERTYI(PropertyInfo(Variant::INT, "stretch_margin_left", PROPERTY_HINT_RANGE, "0,16384,1"), "set_stretch_margin", "get_stretch_margin", SIDE_LEFT);
- ADD_PROPERTYI(PropertyInfo(Variant::INT, "stretch_margin_top", PROPERTY_HINT_RANGE, "0,16384,1"), "set_stretch_margin", "get_stretch_margin", SIDE_TOP);
- ADD_PROPERTYI(PropertyInfo(Variant::INT, "stretch_margin_right", PROPERTY_HINT_RANGE, "0,16384,1"), "set_stretch_margin", "get_stretch_margin", SIDE_RIGHT);
- ADD_PROPERTYI(PropertyInfo(Variant::INT, "stretch_margin_bottom", PROPERTY_HINT_RANGE, "0,16384,1"), "set_stretch_margin", "get_stretch_margin", SIDE_BOTTOM);
BIND_ENUM_CONSTANT(FILL_LEFT_TO_RIGHT);
BIND_ENUM_CONSTANT(FILL_RIGHT_TO_LEFT);
diff --git a/scene/gui/tree.cpp b/scene/gui/tree.cpp
index ff8d2b88b1..89807dbe95 100644
--- a/scene/gui/tree.cpp
+++ b/scene/gui/tree.cpp
@@ -101,6 +101,7 @@ void TreeItem::_change_tree(Tree *p_tree) {
if (tree->popup_edited_item == this) {
tree->popup_edited_item = nullptr;
+ tree->popup_pressing_edited_item = nullptr;
tree->pressing_for_editor = false;
}
@@ -333,7 +334,7 @@ int TreeItem::get_opentype_feature(int p_column, const String &p_name) const {
return cells[p_column].opentype_features[tag];
}
-void TreeItem::set_structured_text_bidi_override(int p_column, Control::StructuredTextParser p_parser) {
+void TreeItem::set_structured_text_bidi_override(int p_column, TextServer::StructuredTextParser p_parser) {
ERR_FAIL_INDEX(p_column, cells.size());
if (cells[p_column].st_parser != p_parser) {
@@ -345,8 +346,8 @@ void TreeItem::set_structured_text_bidi_override(int p_column, Control::Structur
}
}
-Control::StructuredTextParser TreeItem::get_structured_text_bidi_override(int p_column) const {
- ERR_FAIL_INDEX_V(p_column, cells.size(), Control::STRUCTURED_TEXT_NONE);
+TextServer::StructuredTextParser TreeItem::get_structured_text_bidi_override(int p_column) const {
+ ERR_FAIL_INDEX_V(p_column, cells.size(), TextServer::STRUCTURED_TEXT_NONE);
return cells[p_column].st_parser;
}
@@ -1162,24 +1163,23 @@ Size2 TreeItem::get_minimum_size(int p_column) {
return cell.cached_minimum_size;
}
-Variant TreeItem::_call_recursive_bind(const Variant **p_args, int p_argcount, Callable::CallError &r_error) {
+void TreeItem::_call_recursive_bind(const Variant **p_args, int p_argcount, Callable::CallError &r_error) {
if (p_argcount < 1) {
r_error.error = Callable::CallError::CALL_ERROR_TOO_FEW_ARGUMENTS;
r_error.argument = 0;
- return Variant();
+ return;
}
if (p_args[0]->get_type() != Variant::STRING && p_args[0]->get_type() != Variant::STRING_NAME) {
r_error.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT;
r_error.argument = 0;
r_error.expected = Variant::STRING_NAME;
- return Variant();
+ return;
}
StringName method = *p_args[0];
call_recursive(method, &p_args[1], p_argcount - 1, r_error);
- return Variant();
}
void recursive_call_aux(TreeItem *p_item, const StringName &p_method, const Variant **p_args, int p_argcount, Callable::CallError &r_error) {
@@ -1412,8 +1412,8 @@ void Tree::update_cache() {
cache.font_color = get_theme_color(SNAME("font_color"));
cache.font_selected_color = get_theme_color(SNAME("font_selected_color"));
cache.drop_position_color = get_theme_color(SNAME("drop_position_color"));
- cache.hseparation = get_theme_constant(SNAME("hseparation"));
- cache.vseparation = get_theme_constant(SNAME("vseparation"));
+ cache.hseparation = get_theme_constant(SNAME("h_separation"));
+ cache.vseparation = get_theme_constant(SNAME("v_separation"));
cache.item_margin = get_theme_constant(SNAME("item_margin"));
cache.button_margin = get_theme_constant(SNAME("button_margin"));
@@ -2089,7 +2089,6 @@ int Tree::draw_item(const Point2i &p_pos, const Point2 &p_draw_ofs, const Size2
}
if (!p_item->collapsed) { /* if not collapsed, check the children */
-
TreeItem *c = p_item->first_child;
int base_ofs = children_pos.y - cache.offset.y + p_draw_ofs.y;
@@ -2097,82 +2096,97 @@ int Tree::draw_item(const Point2i &p_pos, const Point2 &p_draw_ofs, const Size2
int prev_hl_ofs = base_ofs;
while (c) {
+ int child_h = -1;
if (htotal >= 0) {
- int child_h = draw_item(children_pos, p_draw_ofs, p_draw_size, c);
+ child_h = draw_item(children_pos, p_draw_ofs, p_draw_size, c);
+ }
- // Draw relationship lines.
- if (cache.draw_relationship_lines > 0 && (!hide_root || c->parent != root)) {
- int root_ofs = children_pos.x + ((p_item->disable_folding || hide_folding) ? cache.hseparation : cache.item_margin);
- int parent_ofs = p_pos.x + cache.item_margin;
- Point2i root_pos = Point2i(root_ofs, children_pos.y + label_h / 2) - cache.offset + p_draw_ofs;
+ // Draw relationship lines.
+ if (cache.draw_relationship_lines > 0 && (!hide_root || c->parent != root)) {
+ int root_ofs = children_pos.x + ((p_item->disable_folding || hide_folding) ? cache.hseparation : cache.item_margin);
+ int parent_ofs = p_pos.x + cache.item_margin;
+ Point2i root_pos = Point2i(root_ofs, children_pos.y + label_h / 2) - cache.offset + p_draw_ofs;
- if (c->get_first_child() != nullptr) {
- root_pos -= Point2i(cache.arrow->get_width(), 0);
- }
+ if (c->get_first_child() != nullptr) {
+ root_pos -= Point2i(cache.arrow->get_width(), 0);
+ }
- float line_width = cache.relationship_line_width * Math::round(cache.base_scale);
- float parent_line_width = cache.parent_hl_line_width * Math::round(cache.base_scale);
- float children_line_width = cache.children_hl_line_width * Math::round(cache.base_scale);
+ float line_width = cache.relationship_line_width * Math::round(cache.base_scale);
+ float parent_line_width = cache.parent_hl_line_width * Math::round(cache.base_scale);
+ float children_line_width = cache.children_hl_line_width * Math::round(cache.base_scale);
- Point2i parent_pos = Point2i(parent_ofs - cache.arrow->get_width() / 2, p_pos.y + label_h / 2 + cache.arrow->get_height() / 2) - cache.offset + p_draw_ofs;
+ Point2i parent_pos = Point2i(parent_ofs - cache.arrow->get_width() / 2, p_pos.y + label_h / 2 + cache.arrow->get_height() / 2) - cache.offset + p_draw_ofs;
- int more_prev_ofs = 0;
+ int more_prev_ofs = 0;
- if (root_pos.y + line_width >= 0) {
- if (rtl) {
- root_pos.x = get_size().width - root_pos.x;
- parent_pos.x = get_size().width - parent_pos.x;
- }
+ if (root_pos.y + line_width >= 0) {
+ if (rtl) {
+ root_pos.x = get_size().width - root_pos.x;
+ parent_pos.x = get_size().width - parent_pos.x;
+ }
- // Order of parts on this bend: the horizontal line first, then the vertical line.
- if (_is_branch_selected(c)) {
- // If this item or one of its children is selected, we draw the line using parent highlight style.
+ // Order of parts on this bend: the horizontal line first, then the vertical line.
+ if (_is_branch_selected(c)) {
+ // If this item or one of its children is selected, we draw the line using parent highlight style.
+ if (htotal >= 0) {
RenderingServer::get_singleton()->canvas_item_add_line(ci, root_pos, Point2i(parent_pos.x + Math::floor(parent_line_width / 2), root_pos.y), cache.parent_hl_line_color, parent_line_width);
+ }
+ RenderingServer::get_singleton()->canvas_item_add_line(ci, Point2i(parent_pos.x, root_pos.y + Math::floor(parent_line_width / 2)), Point2i(parent_pos.x, prev_hl_ofs), cache.parent_hl_line_color, parent_line_width);
+
+ more_prev_ofs = cache.parent_hl_line_margin;
+ prev_hl_ofs = root_pos.y + Math::floor(parent_line_width / 2);
+ } else if (p_item->is_selected(0)) {
+ // If parent item is selected (but this item is not), we draw the line using children highlight style.
+ // Siblings of the selected branch can be drawn with a slight offset and their vertical line must appear as highlighted.
+ if (_is_sibling_branch_selected(c)) {
+ if (htotal >= 0) {
+ RenderingServer::get_singleton()->canvas_item_add_line(ci, root_pos, Point2i(parent_pos.x + Math::floor(parent_line_width / 2), root_pos.y), cache.children_hl_line_color, children_line_width);
+ }
RenderingServer::get_singleton()->canvas_item_add_line(ci, Point2i(parent_pos.x, root_pos.y + Math::floor(parent_line_width / 2)), Point2i(parent_pos.x, prev_hl_ofs), cache.parent_hl_line_color, parent_line_width);
- more_prev_ofs = cache.parent_hl_line_margin;
prev_hl_ofs = root_pos.y + Math::floor(parent_line_width / 2);
- } else if (p_item->is_selected(0)) {
- // If parent item is selected (but this item is not), we draw the line using children highlight style.
- // Siblings of the selected branch can be drawn with a slight offset and their vertical line must appear as highlighted.
- if (_is_sibling_branch_selected(c)) {
- RenderingServer::get_singleton()->canvas_item_add_line(ci, root_pos, Point2i(parent_pos.x + Math::floor(parent_line_width / 2), root_pos.y), cache.children_hl_line_color, children_line_width);
- RenderingServer::get_singleton()->canvas_item_add_line(ci, Point2i(parent_pos.x, root_pos.y + Math::floor(parent_line_width / 2)), Point2i(parent_pos.x, prev_hl_ofs), cache.parent_hl_line_color, parent_line_width);
-
- prev_hl_ofs = root_pos.y + Math::floor(parent_line_width / 2);
- } else {
+ } else {
+ if (htotal >= 0) {
RenderingServer::get_singleton()->canvas_item_add_line(ci, root_pos, Point2i(parent_pos.x + Math::floor(children_line_width / 2), root_pos.y), cache.children_hl_line_color, children_line_width);
- RenderingServer::get_singleton()->canvas_item_add_line(ci, Point2i(parent_pos.x, root_pos.y + Math::floor(children_line_width / 2)), Point2i(parent_pos.x, prev_ofs + Math::floor(children_line_width / 2)), cache.children_hl_line_color, children_line_width);
}
- } else {
- // If nothing of the above is true, we draw the line using normal style.
- // Siblings of the selected branch can be drawn with a slight offset and their vertical line must appear as highlighted.
- if (_is_sibling_branch_selected(c)) {
+ RenderingServer::get_singleton()->canvas_item_add_line(ci, Point2i(parent_pos.x, root_pos.y + Math::floor(children_line_width / 2)), Point2i(parent_pos.x, prev_ofs + Math::floor(children_line_width / 2)), cache.children_hl_line_color, children_line_width);
+ }
+ } else {
+ // If nothing of the above is true, we draw the line using normal style.
+ // Siblings of the selected branch can be drawn with a slight offset and their vertical line must appear as highlighted.
+ if (_is_sibling_branch_selected(c)) {
+ if (htotal >= 0) {
RenderingServer::get_singleton()->canvas_item_add_line(ci, root_pos, Point2i(parent_pos.x + cache.parent_hl_line_margin, root_pos.y), cache.relationship_line_color, line_width);
- RenderingServer::get_singleton()->canvas_item_add_line(ci, Point2i(parent_pos.x, root_pos.y + Math::floor(parent_line_width / 2)), Point2i(parent_pos.x, prev_hl_ofs), cache.parent_hl_line_color, parent_line_width);
+ }
+ RenderingServer::get_singleton()->canvas_item_add_line(ci, Point2i(parent_pos.x, root_pos.y + Math::floor(parent_line_width / 2)), Point2i(parent_pos.x, prev_hl_ofs), cache.parent_hl_line_color, parent_line_width);
- prev_hl_ofs = root_pos.y + Math::floor(parent_line_width / 2);
- } else {
+ prev_hl_ofs = root_pos.y + Math::floor(parent_line_width / 2);
+ } else {
+ if (htotal >= 0) {
RenderingServer::get_singleton()->canvas_item_add_line(ci, root_pos, Point2i(parent_pos.x + Math::floor(line_width / 2), root_pos.y), cache.relationship_line_color, line_width);
- RenderingServer::get_singleton()->canvas_item_add_line(ci, Point2i(parent_pos.x, root_pos.y + Math::floor(line_width / 2)), Point2i(parent_pos.x, prev_ofs + Math::floor(line_width / 2)), cache.relationship_line_color, line_width);
}
+ RenderingServer::get_singleton()->canvas_item_add_line(ci, Point2i(parent_pos.x, root_pos.y + Math::floor(line_width / 2)), Point2i(parent_pos.x, prev_ofs + Math::floor(line_width / 2)), cache.relationship_line_color, line_width);
}
}
-
- prev_ofs = root_pos.y + more_prev_ofs;
}
- if (child_h < 0) {
- if (cache.draw_relationship_lines == 0) {
- return -1; // break, stop drawing, no need to anymore
- }
+ prev_ofs = root_pos.y + more_prev_ofs;
+ }
- htotal = -1;
- children_pos.y = cache.offset.y + p_draw_size.height;
- } else {
- htotal += child_h;
- children_pos.y += child_h;
+ if (child_h < 0) {
+ if (htotal == -1) {
+ break; // Last loop done, stop.
}
+
+ if (cache.draw_relationship_lines == 0) {
+ return -1; // No need to draw anymore, full stop.
+ }
+
+ htotal = -1;
+ children_pos.y = cache.offset.y + p_draw_size.height;
+ } else {
+ htotal += child_h;
+ children_pos.y += child_h;
}
c = c->next;
@@ -2671,8 +2685,8 @@ int Tree::propagate_mouse_event(const Point2i &p_pos, int x_ofs, int y_ofs, int
}
click_handled = true;
- popup_edited_item = p_item;
- popup_edited_item_col = col;
+ popup_pressing_edited_item = p_item;
+ popup_pressing_edited_item_column = col;
pressing_item_rect = Rect2(get_global_position() + Point2i(col_ofs, _get_title_button_height() + y_ofs) - cache.offset, Size2(col_width, item_h));
pressing_for_editor_text = editor_text;
@@ -3207,10 +3221,16 @@ void Tree::gui_input(const Ref<InputEvent> &p_event) {
update();
}
- if (pressing_for_editor && popup_edited_item && (popup_edited_item->get_cell_mode(popup_edited_item_col) == TreeItem::CELL_MODE_RANGE)) {
- //range drag
+ if (pressing_for_editor && popup_pressing_edited_item && (popup_pressing_edited_item->get_cell_mode(popup_pressing_edited_item_column) == TreeItem::CELL_MODE_RANGE)) {
+ /* This needs to happen now, because the popup can be closed when pressing another item, and must remain the popup edited item until it actually closes */
+ popup_edited_item = popup_pressing_edited_item;
+ popup_edited_item_col = popup_pressing_edited_item_column;
+
+ popup_pressing_edited_item = nullptr;
+ popup_pressing_edited_item_column = -1;
if (!range_drag_enabled) {
+ //range drag
Vector2 cpos = mm->get_position();
if (rtl) {
cpos.x = get_size().width - cpos.x;
@@ -3995,6 +4015,7 @@ void Tree::clear() {
selected_item = nullptr;
edited_item = nullptr;
popup_edited_item = nullptr;
+ popup_pressing_edited_item = nullptr;
update();
};
@@ -4310,12 +4331,16 @@ int Tree::get_pressed_button() const {
return pressed_button;
}
-Rect2 Tree::get_item_rect(TreeItem *p_item, int p_column) const {
+Rect2 Tree::get_item_rect(TreeItem *p_item, int p_column, int p_button) const {
ERR_FAIL_NULL_V(p_item, Rect2());
ERR_FAIL_COND_V(p_item->tree != this, Rect2());
if (p_column != -1) {
ERR_FAIL_INDEX_V(p_column, columns.size(), Rect2());
}
+ if (p_button != -1) {
+ ERR_FAIL_COND_V(p_column == -1, Rect2()); // pass a column if you want to pass a button
+ ERR_FAIL_INDEX_V(p_button, p_item->cells[p_column].buttons.size(), Rect2());
+ }
int ofs = get_item_offset(p_item);
int height = compute_item_height(p_item);
@@ -4333,6 +4358,19 @@ Rect2 Tree::get_item_rect(TreeItem *p_item, int p_column) const {
}
r.position.x = accum;
r.size.x = get_column_width(p_column);
+ if (p_button != -1) {
+ const TreeItem::Cell &c = p_item->cells[p_column];
+ Vector2 ofst = Vector2(r.position.x + r.size.x, r.position.y);
+ for (int j = c.buttons.size() - 1; j >= 0; j--) {
+ Ref<Texture2D> b = c.buttons[j].texture;
+ Size2 size = b->get_size() + cache.button_pressed->get_minimum_size();
+ ofst.x -= size.x;
+
+ if (j == p_button) {
+ return Rect2(ofst, size);
+ }
+ }
+ }
}
return r;
@@ -4871,7 +4909,7 @@ void Tree::_bind_methods() {
ClassDB::bind_method(D_METHOD("get_edited_column"), &Tree::get_edited_column);
ClassDB::bind_method(D_METHOD("edit_selected"), &Tree::edit_selected);
ClassDB::bind_method(D_METHOD("get_custom_popup_rect"), &Tree::get_custom_popup_rect);
- ClassDB::bind_method(D_METHOD("get_item_area_rect", "item", "column"), &Tree::get_item_rect, DEFVAL(-1));
+ ClassDB::bind_method(D_METHOD("get_item_area_rect", "item", "column", "button_index"), &Tree::get_item_rect, DEFVAL(-1), DEFVAL(-1));
ClassDB::bind_method(D_METHOD("get_item_at_position", "position"), &Tree::get_item_at_position);
ClassDB::bind_method(D_METHOD("get_column_at_position", "position"), &Tree::get_column_at_position);
ClassDB::bind_method(D_METHOD("get_drop_section_at_position", "position"), &Tree::get_drop_section_at_position);
diff --git a/scene/gui/tree.h b/scene/gui/tree.h
index dc786de6dc..8ee2a3c382 100644
--- a/scene/gui/tree.h
+++ b/scene/gui/tree.h
@@ -65,7 +65,7 @@ private:
Ref<TextLine> text_buf;
Dictionary opentype_features;
String language;
- Control::StructuredTextParser st_parser = Control::STRUCTURED_TEXT_DEFAULT;
+ TextServer::StructuredTextParser st_parser = TextServer::STRUCTURED_TEXT_DEFAULT;
Array st_args;
Control::TextDirection text_direction = Control::TEXT_DIRECTION_INHERITED;
bool dirty = true;
@@ -134,7 +134,7 @@ private:
Vector<TreeItem *> children_cache;
bool is_root = false; // for tree root
- Tree *tree; // tree (for reference)
+ Tree *tree = nullptr; // tree (for reference)
TreeItem(Tree *p_tree);
@@ -189,7 +189,7 @@ protected:
return d;
}
- Variant _call_recursive_bind(const Variant **p_args, int p_argcount, Callable::CallError &r_error);
+ void _call_recursive_bind(const Variant **p_args, int p_argcount, Callable::CallError &r_error);
public:
/* cell mode */
@@ -220,8 +220,8 @@ public:
int get_opentype_feature(int p_column, const String &p_name) const;
void clear_opentype_features(int p_column);
- void set_structured_text_bidi_override(int p_column, Control::StructuredTextParser p_parser);
- Control::StructuredTextParser get_structured_text_bidi_override(int p_column) const;
+ void set_structured_text_bidi_override(int p_column, TextServer::StructuredTextParser p_parser);
+ TextServer::StructuredTextParser get_structured_text_bidi_override(int p_column) const;
void set_structured_text_bidi_override_options(int p_column, Array p_args);
Array get_structured_text_bidi_override_options(int p_column) const;
@@ -379,6 +379,9 @@ private:
TreeItem *selected_item = nullptr;
TreeItem *edited_item = nullptr;
+ TreeItem *popup_pressing_edited_item = nullptr; // Candidate.
+ int popup_pressing_edited_item_column = -1;
+
TreeItem *drop_mode_over = nullptr;
int drop_mode_section = 0;
@@ -428,18 +431,18 @@ private:
bool show_column_titles = false;
- VBoxContainer *popup_editor_vb;
+ VBoxContainer *popup_editor_vb = nullptr;
- Popup *popup_editor;
+ Popup *popup_editor = nullptr;
LineEdit *text_editor = nullptr;
- HSlider *value_editor;
+ HSlider *value_editor = nullptr;
bool updating_value_editor = false;
uint64_t focus_in_id = 0;
PopupMenu *popup_menu = nullptr;
Vector<ColumnInfo> columns;
- Timer *range_click_timer;
+ Timer *range_click_timer = nullptr;
TreeItem *range_item_last = nullptr;
bool range_up_last = false;
void _range_click_timeout();
@@ -553,8 +556,8 @@ private:
int _get_title_button_height() const;
void _scroll_moved(float p_value);
- HScrollBar *h_scroll;
- VScrollBar *v_scroll;
+ HScrollBar *h_scroll = nullptr;
+ VScrollBar *v_scroll = nullptr;
bool h_scroll_enabled = true;
bool v_scroll_enabled = true;
@@ -673,7 +676,7 @@ public:
Rect2 get_custom_popup_rect() const;
int get_item_offset(TreeItem *p_item) const;
- Rect2 get_item_rect(TreeItem *p_item, int p_column = -1) const;
+ Rect2 get_item_rect(TreeItem *p_item, int p_column = -1, int p_button = -1) const;
bool edit_selected();
bool is_editing();
diff --git a/scene/gui/video_stream_player.cpp b/scene/gui/video_stream_player.cpp
index d7c76aa070..ca2dad71af 100644
--- a/scene/gui/video_stream_player.cpp
+++ b/scene/gui/video_stream_player.cpp
@@ -60,7 +60,7 @@ int VideoStreamPlayer::_audio_mix_callback(void *p_udata, const float *p_data, i
ERR_FAIL_NULL_V(p_udata, 0);
ERR_FAIL_NULL_V(p_data, 0);
- VideoStreamPlayer *vp = (VideoStreamPlayer *)p_udata;
+ VideoStreamPlayer *vp = static_cast<VideoStreamPlayer *>(p_udata);
int todo = MIN(vp->resampler.get_writer_space(), p_frames);
@@ -77,7 +77,7 @@ int VideoStreamPlayer::_audio_mix_callback(void *p_udata, const float *p_data, i
void VideoStreamPlayer::_mix_audios(void *p_self) {
ERR_FAIL_NULL(p_self);
- reinterpret_cast<VideoStreamPlayer *>(p_self)->_mix_audio();
+ static_cast<VideoStreamPlayer *>(p_self)->_mix_audio();
}
// Called from audio thread
diff --git a/scene/gui/view_panner.cpp b/scene/gui/view_panner.cpp
index 71865b4864..892d0aba29 100644
--- a/scene/gui/view_panner.cpp
+++ b/scene/gui/view_panner.cpp
@@ -37,7 +37,7 @@
bool ViewPanner::gui_input(const Ref<InputEvent> &p_event, Rect2 p_canvas_rect) {
Ref<InputEventMouseButton> mb = p_event;
if (mb.is_valid()) {
- Vector2i scroll_vec = Vector2((mb->get_button_index() == MouseButton::WHEEL_RIGHT) - (mb->get_button_index() == MouseButton::WHEEL_LEFT), (mb->get_button_index() == MouseButton::WHEEL_DOWN) - (mb->get_button_index() == MouseButton::WHEEL_UP));
+ Vector2 scroll_vec = Vector2((mb->get_button_index() == MouseButton::WHEEL_RIGHT) - (mb->get_button_index() == MouseButton::WHEEL_LEFT), (mb->get_button_index() == MouseButton::WHEEL_DOWN) - (mb->get_button_index() == MouseButton::WHEEL_UP));
if (scroll_vec != Vector2()) {
if (control_scheme == SCROLL_PANS) {
if (mb->is_ctrl_pressed()) {