diff options
Diffstat (limited to 'scene/gui')
44 files changed, 1019 insertions, 578 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 789c01adf3..595f0cbea7 100644 --- a/scene/gui/base_button.cpp +++ b/scene/gui/base_button.cpp @@ -43,7 +43,7 @@ void BaseButton::_unpress_group() { status.pressed = true; } - for (Set<BaseButton *>::Element *E = button_group->buttons.front(); E; E = E->next()) { + for (RBSet<BaseButton *>::Element *E = button_group->buttons.front(); E; E = E->next()) { if (E->get() == this) { continue; } @@ -485,14 +485,14 @@ BaseButton::~BaseButton() { } void ButtonGroup::get_buttons(List<BaseButton *> *r_buttons) { - for (Set<BaseButton *>::Element *E = buttons.front(); E; E = E->next()) { + for (RBSet<BaseButton *>::Element *E = buttons.front(); E; E = E->next()) { r_buttons->push_back(E->get()); } } Array ButtonGroup::_get_buttons() { Array btns; - for (Set<BaseButton *>::Element *E = buttons.front(); E; E = E->next()) { + for (RBSet<BaseButton *>::Element *E = buttons.front(); E; E = E->next()) { btns.push_back(E->get()); } @@ -500,7 +500,7 @@ Array ButtonGroup::_get_buttons() { } BaseButton *ButtonGroup::get_pressed_button() { - for (Set<BaseButton *>::Element *E = buttons.front(); E; E = E->next()) { + for (RBSet<BaseButton *>::Element *E = buttons.front(); E; E = E->next()) { if (E->get()->is_pressed()) { return E->get(); } diff --git a/scene/gui/base_button.h b/scene/gui/base_button.h index f4f9b88868..0b70d285ee 100644 --- a/scene/gui/base_button.h +++ b/scene/gui/base_button.h @@ -143,7 +143,7 @@ VARIANT_ENUM_CAST(BaseButton::ActionMode) class ButtonGroup : public Resource { GDCLASS(ButtonGroup, Resource); friend class BaseButton; - Set<BaseButton *> buttons; + RBSet<BaseButton *> buttons; protected: static void _bind_methods(); diff --git a/scene/gui/box_container.cpp b/scene/gui/box_container.cpp index 251648da69..df695feba8 100644 --- a/scene/gui/box_container.cpp +++ b/scene/gui/box_container.cpp @@ -52,7 +52,7 @@ void BoxContainer::_resort() { int stretch_min = 0; int stretch_avail = 0; float stretch_ratio_total = 0.0; - Map<Control *, _MinSizeCache> min_size_cache; + HashMap<Control *, _MinSizeCache> min_size_cache; for (int i = 0; i < get_child_count(); i++) { Control *c = Object::cast_to<Control>(get_child(i)); diff --git a/scene/gui/button.cpp b/scene/gui/button.cpp index 29a0681f9c..ff194f979d 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,22 @@ void Button::_notification(int p_what) { if (icon_align_rtl_checked == HORIZONTAL_ALIGNMENT_LEFT) { style_offset.x = style->get_margin(SIDE_LEFT); if (_internal_margin[SIDE_LEFT] > 0) { - icon_ofs_region = _internal_margin[SIDE_LEFT] + get_theme_constant(SNAME("hseparation")); + icon_ofs_region = _internal_margin[SIDE_LEFT] + get_theme_constant(SNAME("h_separation")); } } else if (icon_align_rtl_checked == HORIZONTAL_ALIGNMENT_CENTER) { style_offset.x = 0.0; } else if (icon_align_rtl_checked == HORIZONTAL_ALIGNMENT_RIGHT) { style_offset.x = -style->get_margin(SIDE_RIGHT); if (_internal_margin[SIDE_RIGHT] > 0) { - icon_ofs_region = -_internal_margin[SIDE_RIGHT] - get_theme_constant(SNAME("hseparation")); + icon_ofs_region = -_internal_margin[SIDE_RIGHT] - get_theme_constant(SNAME("h_separation")); } } style_offset.y = style->get_margin(SIDE_TOP); if (expand_icon) { Size2 _size = get_size() - style->get_offset() * 2; - _size.width -= get_theme_constant(SNAME("hseparation")) + icon_ofs_region; + int icon_text_separation = text.is_empty() ? 0 : get_theme_constant(SNAME("h_separation")); + _size.width -= icon_text_separation + icon_ofs_region; if (!clip_text && icon_align_rtl_checked != HORIZONTAL_ALIGNMENT_CENTER) { _size.width -= text_buf->get_size().width; } @@ -286,7 +287,7 @@ void Button::_notification(int p_what) { } } - Point2 icon_ofs = !_icon.is_null() ? Point2(icon_region.size.width + get_theme_constant(SNAME("hseparation")), 0) : Point2(); + 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 +297,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 +314,7 @@ void Button::_notification(int p_what) { icon_ofs.x = 0.0; } if (_internal_margin[SIDE_LEFT] > 0) { - text_ofs.x = style->get_margin(SIDE_LEFT) + icon_ofs.x + _internal_margin[SIDE_LEFT] + get_theme_constant(SNAME("hseparation")); + text_ofs.x = style->get_margin(SIDE_LEFT) + icon_ofs.x + _internal_margin[SIDE_LEFT] + get_theme_constant(SNAME("h_separation")); } else { text_ofs.x = style->get_margin(SIDE_LEFT) + icon_ofs.x; } @@ -330,7 +331,7 @@ void Button::_notification(int p_what) { } break; case HORIZONTAL_ALIGNMENT_RIGHT: { if (_internal_margin[SIDE_RIGHT] > 0) { - text_ofs.x = size.x - style->get_margin(SIDE_RIGHT) - text_width - _internal_margin[SIDE_RIGHT] - get_theme_constant(SNAME("hseparation")); + text_ofs.x = size.x - style->get_margin(SIDE_RIGHT) - text_width - _internal_margin[SIDE_RIGHT] - get_theme_constant(SNAME("h_separation")); } else { text_ofs.x = size.x - style->get_margin(SIDE_RIGHT) - text_width; } 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 2e87e71972..197c9005c3 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; @@ -357,6 +357,11 @@ void CodeEdit::gui_input(const Ref<InputEvent> &p_gui_input) { } Ref<InputEventKey> k = p_gui_input; + if (TextEdit::alt_input(p_gui_input)) { + accept_event(); + return; + } + bool update_code_completion = false; if (!k.is_valid()) { TextEdit::gui_input(p_gui_input); @@ -735,7 +740,7 @@ void CodeEdit::set_auto_indent_prefixes(const TypedArray<String> &p_prefixes) { TypedArray<String> CodeEdit::get_auto_indent_prefixes() const { TypedArray<String> prefixes; - for (const Set<char32_t>::Element *E = auto_indent_prefixes.front(); E; E = E->next()) { + for (const RBSet<char32_t>::Element *E = auto_indent_prefixes.front(); E; E = E->next()) { prefixes.push_back(String::chr(E->get())); } return prefixes; @@ -994,7 +999,8 @@ void CodeEdit::_new_line(bool p_split_current_line, bool p_above) { } /* Make sure this is the last char, trailing whitespace or comments are okay. */ - if (should_indent && (!is_whitespace(c) && is_in_comment(cl, cc) == -1)) { + /* Increment column for comments because the delimiter (#) should be ignored. */ + if (should_indent && (!is_whitespace(c) && is_in_comment(cl, line_col + 1) == -1)) { should_indent = false; } } @@ -1622,7 +1628,7 @@ Point2 CodeEdit::get_delimiter_start_position(int p_line, int p_column) const { start_position.y = -1; start_position.x = -1; - bool in_region = ((p_line <= 0 || delimiter_cache[p_line - 1].size() < 1) ? -1 : delimiter_cache[p_line - 1].back()->value()) != -1; + bool in_region = ((p_line <= 0 || delimiter_cache[p_line - 1].size() < 1) ? -1 : delimiter_cache[p_line - 1].back()->get()) != -1; /* Check the keys for this line. */ for (const KeyValue<int, int> &E : delimiter_cache[p_line]) { @@ -1746,7 +1752,7 @@ void CodeEdit::set_code_completion_prefixes(const TypedArray<String> &p_prefixes TypedArray<String> CodeEdit::get_code_completion_prefixes() const { TypedArray<String> prefixes; - for (const Set<char32_t>::Element *E = code_completion_prefixes.front(); E; E = E->next()) { + for (const RBSet<char32_t>::Element *E = code_completion_prefixes.front(); E; E = E->next()) { prefixes.push_back(String::chr(E->get())); } return prefixes; @@ -1817,7 +1823,7 @@ 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) { +void CodeEdit::add_code_completion_option(CodeCompletionKind p_type, const String &p_display_text, const String &p_insert_text, const Color &p_text_color, const Ref<Resource> &p_icon, const Variant &p_value) { ScriptLanguage::CodeCompletionOption completion_option; completion_option.kind = (ScriptLanguage::CodeCompletionKind)p_type; completion_option.display = p_display_text; @@ -2195,7 +2201,7 @@ void CodeEdit::_bind_methods() { ClassDB::bind_method(D_METHOD("get_text_for_code_completion"), &CodeEdit::get_text_for_code_completion); ClassDB::bind_method(D_METHOD("request_code_completion", "force"), &CodeEdit::request_code_completion, DEFVAL(false)); - ClassDB::bind_method(D_METHOD("add_code_completion_option", "type", "display_text", "insert_text", "text_color", "icon", "value"), &CodeEdit::add_code_completion_option, DEFVAL(Color(1, 1, 1)), DEFVAL(RES()), DEFVAL(Variant::NIL)); + ClassDB::bind_method(D_METHOD("add_code_completion_option", "type", "display_text", "insert_text", "text_color", "icon", "value"), &CodeEdit::add_code_completion_option, DEFVAL(Color(1, 1, 1)), DEFVAL(Ref<Resource>()), DEFVAL(Variant::NIL)); ClassDB::bind_method(D_METHOD("update_code_completion_options", "force"), &CodeEdit::update_code_completion_options); ClassDB::bind_method(D_METHOD("get_code_completion_options"), &CodeEdit::get_code_completion_options); ClassDB::bind_method(D_METHOD("get_code_completion_option", "index"), &CodeEdit::get_code_completion_option); @@ -2396,7 +2402,7 @@ void CodeEdit::_update_delimiter_cache(int p_from_line, int p_to_line) { } } else { for (int i = start_line; i < end_line; i++) { - delimiter_cache.insert(i, Map<int, int>()); + delimiter_cache.insert(i, RBMap<int, int>()); } } } @@ -2533,7 +2539,7 @@ int CodeEdit::_is_in_delimiter(int p_line, int p_column, DelimiterType p_type) c int region = (p_line <= 0 || delimiter_cache[p_line - 1].size() < 1) ? -1 : delimiter_cache[p_line - 1].back()->value(); bool in_region = region != -1 && delimiters[region].type == p_type; - for (Map<int, int>::Element *E = delimiter_cache[p_line].front(); E; E = E->next()) { + for (RBMap<int, int>::Element *E = delimiter_cache[p_line].front(); E; E = E->next()) { /* If column is specified, loop until the key is larger then the column. */ if (p_column != -1) { if (E->key() > p_column) { @@ -3035,7 +3041,9 @@ void CodeEdit::_text_changed() { lc = get_line_count(); List<int> breakpoints; - breakpointed_lines.get_key_list(&breakpoints); + for (const KeyValue<int, bool> &E : breakpointed_lines) { + breakpoints.push_back(E.key); + } for (const int &line : breakpoints) { if (line < lines_edited_from || (line < lc && is_line_breakpointed(line))) { continue; diff --git a/scene/gui/code_edit.h b/scene/gui/code_edit.h index 596a065f12..0b00735f46 100644 --- a/scene/gui/code_edit.h +++ b/scene/gui/code_edit.h @@ -58,7 +58,7 @@ private: String indent_text = "\t"; bool auto_indent = false; - Set<char32_t> auto_indent_prefixes; + RBSet<char32_t> auto_indent_prefixes; bool indent_using_spaces = false; int _calculate_spaces_till_next_left_indent(int p_column) const; @@ -176,7 +176,7 @@ private: * ] * ] */ - Vector<Map<int, int>> delimiter_cache; + Vector<RBMap<int, int>> delimiter_cache; void _update_delimiter_cache(int p_from_line = 0, int p_to_line = -1); int _is_in_delimiter(int p_line, int p_column, DelimiterType p_type) const; @@ -214,7 +214,7 @@ private: int code_completion_longest_line = 0; Rect2i code_completion_rect; - Set<char32_t> code_completion_prefixes; + RBSet<char32_t> code_completion_prefixes; List<ScriptLanguage::CodeCompletionOption> code_completion_option_submitted; List<ScriptLanguage::CodeCompletionOption> code_completion_option_sources; String code_completion_base; @@ -398,7 +398,7 @@ public: void request_code_completion(bool p_force = false); - void add_code_completion_option(CodeCompletionKind p_type, const String &p_display_text, const String &p_insert_text, const Color &p_text_color = Color(1, 1, 1), const RES &p_icon = RES(), const Variant &p_value = Variant::NIL); + void add_code_completion_option(CodeCompletionKind p_type, const String &p_display_text, const String &p_insert_text, const Color &p_text_color = Color(1, 1, 1), const Ref<Resource> &p_icon = Ref<Resource>(), const Variant &p_value = Variant::NIL); void update_code_completion_options(bool p_forced = false); TypedArray<Dictionary> get_code_completion_options() const; diff --git a/scene/gui/color_picker.cpp b/scene/gui/color_picker.cpp index 48fadb0cf8..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) { diff --git a/scene/gui/control.cpp b/scene/gui/control.cpp index 96d2b29fc1..54fa726260 100644 --- a/scene/gui/control.cpp +++ b/scene/gui/control.cpp @@ -1501,14 +1501,15 @@ void Control::_set_layout_mode(LayoutMode p_mode) { bool list_changed = false; if (p_mode == LayoutMode::LAYOUT_MODE_POSITION || p_mode == LayoutMode::LAYOUT_MODE_ANCHORS) { - if (has_meta("_edit_layout_mode") && (int)get_meta("_edit_layout_mode") != (int)p_mode) { + if ((int)get_meta("_edit_layout_mode", p_mode) != (int)p_mode) { list_changed = true; } set_meta("_edit_layout_mode", (int)p_mode); if (p_mode == LayoutMode::LAYOUT_MODE_POSITION) { - set_meta("_edit_use_custom_anchors", false); + remove_meta("_edit_layout_mode"); + remove_meta("_edit_use_custom_anchors"); set_anchors_and_offsets_preset(LayoutPreset::PRESET_TOP_LEFT, LayoutPresetMode::PRESET_MODE_KEEP_SIZE); set_grow_direction_preset(LayoutPreset::PRESET_TOP_LEFT); } @@ -1589,22 +1590,22 @@ void Control::set_anchor_and_offset(Side p_side, real_t p_anchor, real_t p_pos, void Control::_set_anchors_layout_preset(int p_preset) { bool list_changed = false; - if (has_meta("_edit_layout_mode") && (int)get_meta("_edit_layout_mode") != (int)LayoutMode::LAYOUT_MODE_ANCHORS) { + if (get_meta("_edit_layout_mode", LayoutMode::LAYOUT_MODE_ANCHORS).operator int() != LayoutMode::LAYOUT_MODE_ANCHORS) { list_changed = true; - set_meta("_edit_layout_mode", (int)LayoutMode::LAYOUT_MODE_ANCHORS); + set_meta("_edit_layout_mode", LayoutMode::LAYOUT_MODE_ANCHORS); } if (p_preset == -1) { - if (!has_meta("_edit_use_custom_anchors") || !(bool)get_meta("_edit_use_custom_anchors")) { + if (!get_meta("_edit_use_custom_anchors", false)) { set_meta("_edit_use_custom_anchors", true); notify_property_list_changed(); } return; // Keep settings as is. } - if (!has_meta("_edit_use_custom_anchors") || (bool)get_meta("_edit_use_custom_anchors")) { + if (get_meta("_edit_use_custom_anchors", true)) { list_changed = true; - set_meta("_edit_use_custom_anchors", false); + remove_meta("_edit_use_custom_anchors"); } LayoutPreset preset = (LayoutPreset)p_preset; @@ -1645,7 +1646,7 @@ void Control::_set_anchors_layout_preset(int p_preset) { int Control::_get_anchors_layout_preset() const { // If the custom preset was selected by user, use it. - if (has_meta("_edit_use_custom_anchors") && (bool)get_meta("_edit_use_custom_anchors")) { + if ((bool)get_meta("_edit_use_custom_anchors", false)) { return -1; } @@ -2070,7 +2071,7 @@ Point2 Control::get_global_position() const { Point2 Control::get_screen_position() const { ERR_FAIL_COND_V(!is_inside_tree(), Point2()); - Point2 global_pos = get_viewport()->get_canvas_transform().xform(get_global_position()); + Point2 global_pos = get_global_transform_with_canvas().get_origin(); Window *w = Object::cast_to<Window>(get_viewport()); if (w && !w->is_embedding_subwindows()) { global_pos += w->get_position(); @@ -2946,90 +2947,17 @@ 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) { @@ -3491,14 +3419,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 4240d52b65..65b71d74f8 100644 --- a/scene/gui/control.h +++ b/scene/gui/control.h @@ -147,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 { @@ -290,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; @@ -579,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/file_dialog.cpp b/scene/gui/file_dialog.cpp index 5e74658470..1725816c31 100644 --- a/scene/gui/file_dialog.cpp +++ b/scene/gui/file_dialog.cpp @@ -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); @@ -916,7 +915,7 @@ void FileDialog::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::BOOL, "mode_overrides_title"), "set_mode_overrides_title", "is_mode_overriding_title"); ADD_PROPERTY(PropertyInfo(Variant::INT, "file_mode", PROPERTY_HINT_ENUM, "Open File,Open Files,Open Folder,Open Any,Save"), "set_file_mode", "get_file_mode"); - ADD_PROPERTY(PropertyInfo(Variant::INT, "access", PROPERTY_HINT_ENUM, "Resources,User data,File system"), "set_access", "get_access"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "access", PROPERTY_HINT_ENUM, "Resources,User Data,File System"), "set_access", "get_access"); ADD_PROPERTY(PropertyInfo(Variant::PACKED_STRING_ARRAY, "filters"), "set_filters", "get_filters"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "show_hidden_files"), "set_show_hidden_files", "is_showing_hidden_files"); ADD_PROPERTY(PropertyInfo(Variant::STRING, "current_dir", PROPERTY_HINT_DIR, "", PROPERTY_USAGE_NONE), "set_current_dir", "get_current_dir"); @@ -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 b41a08c6c7..2e326d2949 100644 --- a/scene/gui/file_dialog.h +++ b/scene/gui/file_dialog.h @@ -82,7 +82,7 @@ private: OptionButton *filter = nullptr; AcceptDialog *mkdirerr = nullptr; AcceptDialog *exterr = nullptr; - DirAccess *dir_access = nullptr; + Ref<DirAccess> dir_access; ConfirmationDialog *confirm_save = nullptr; Label *message = nullptr; diff --git a/scene/gui/flow_container.cpp b/scene/gui/flow_container.cpp index 40aca555db..30b694da76 100644 --- a/scene/gui/flow_container.cpp +++ b/scene/gui/flow_container.cpp @@ -44,12 +44,12 @@ 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(); - Map<Control *, Size2i> children_minsize_cache; + HashMap<Control *, Size2i> children_minsize_cache; Vector<_LineData> lines_data; diff --git a/scene/gui/graph_edit.cpp b/scene/gui/graph_edit.cpp index 1394b4192f..6f3a361e82 100644 --- a/scene/gui/graph_edit.cpp +++ b/scene/gui/graph_edit.cpp @@ -523,7 +523,7 @@ void GraphEdit::_update_comment_enclosed_nodes_list(GraphNode *p_node, HashMap<S } } - p_comment_enclosed_nodes.set(p_node->get_name(), enclosed_nodes); + p_comment_enclosed_nodes.insert(p_node->get_name(), enclosed_nodes); } void GraphEdit::_set_drag_comment_enclosed_nodes(GraphNode *p_node, HashMap<StringName, Vector<GraphNode *>> &p_comment_enclosed_nodes, bool p_drag) { @@ -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(); } @@ -1692,10 +1684,10 @@ void GraphEdit::set_warped_panning(bool p_warped) { warped_panning = p_warped; } -int GraphEdit::_set_operations(SET_OPERATIONS p_operation, Set<StringName> &r_u, const Set<StringName> &r_v) { +int GraphEdit::_set_operations(SET_OPERATIONS p_operation, RBSet<StringName> &r_u, const RBSet<StringName> &r_v) { switch (p_operation) { case GraphEdit::IS_EQUAL: { - for (Set<StringName>::Element *E = r_u.front(); E; E = E->next()) { + for (RBSet<StringName>::Element *E = r_u.front(); E; E = E->next()) { if (!r_v.has(E->get())) { return 0; } @@ -1706,7 +1698,7 @@ int GraphEdit::_set_operations(SET_OPERATIONS p_operation, Set<StringName> &r_u, if (r_u.size() == r_v.size() && !r_u.size()) { return 1; } - for (Set<StringName>::Element *E = r_u.front(); E; E = E->next()) { + for (RBSet<StringName>::Element *E = r_u.front(); E; E = E->next()) { if (!r_v.has(E->get())) { return 0; } @@ -1714,7 +1706,7 @@ int GraphEdit::_set_operations(SET_OPERATIONS p_operation, Set<StringName> &r_u, return 1; } break; case GraphEdit::DIFFERENCE: { - for (Set<StringName>::Element *E = r_u.front(); E; E = E->next()) { + for (RBSet<StringName>::Element *E = r_u.front(); E; E = E->next()) { if (r_v.has(E->get())) { r_u.erase(E->get()); } @@ -1722,7 +1714,7 @@ int GraphEdit::_set_operations(SET_OPERATIONS p_operation, Set<StringName> &r_u, return r_u.size(); } break; case GraphEdit::UNION: { - for (Set<StringName>::Element *E = r_v.front(); E; E = E->next()) { + for (RBSet<StringName>::Element *E = r_v.front(); E; E = E->next()) { if (!r_u.has(E->get())) { r_u.insert(E->get()); } @@ -1735,27 +1727,27 @@ int GraphEdit::_set_operations(SET_OPERATIONS p_operation, Set<StringName> &r_u, return -1; } -HashMap<int, Vector<StringName>> GraphEdit::_layering(const Set<StringName> &r_selected_nodes, const HashMap<StringName, Set<StringName>> &r_upper_neighbours) { +HashMap<int, Vector<StringName>> GraphEdit::_layering(const RBSet<StringName> &r_selected_nodes, const HashMap<StringName, RBSet<StringName>> &r_upper_neighbours) { HashMap<int, Vector<StringName>> l; - Set<StringName> p = r_selected_nodes, q = r_selected_nodes, u, z; + RBSet<StringName> p = r_selected_nodes, q = r_selected_nodes, u, z; int current_layer = 0; bool selected = false; while (!_set_operations(GraphEdit::IS_EQUAL, q, u)) { _set_operations(GraphEdit::DIFFERENCE, p, u); - for (const Set<StringName>::Element *E = p.front(); E; E = E->next()) { - Set<StringName> n = r_upper_neighbours[E->get()]; + for (const RBSet<StringName>::Element *E = p.front(); E; E = E->next()) { + RBSet<StringName> n = r_upper_neighbours[E->get()]; if (_set_operations(GraphEdit::IS_SUBSET, n, z)) { Vector<StringName> t; t.push_back(E->get()); if (!l.has(current_layer)) { - l.set(current_layer, Vector<StringName>{}); + l.insert(current_layer, Vector<StringName>{}); } selected = true; t.append_array(l[current_layer]); - l.set(current_layer, t); - Set<StringName> V; + l.insert(current_layer, t); + RBSet<StringName> V; V.insert(E->get()); _set_operations(GraphEdit::UNION, u, V); } @@ -1797,8 +1789,8 @@ Vector<StringName> GraphEdit::_split(const Vector<StringName> &r_layer, const Ha return left; } -void GraphEdit::_horizontal_alignment(Dictionary &r_root, Dictionary &r_align, const HashMap<int, Vector<StringName>> &r_layers, const HashMap<StringName, Set<StringName>> &r_upper_neighbours, const Set<StringName> &r_selected_nodes) { - for (const Set<StringName>::Element *E = r_selected_nodes.front(); E; E = E->next()) { +void GraphEdit::_horizontal_alignment(Dictionary &r_root, Dictionary &r_align, const HashMap<int, Vector<StringName>> &r_layers, const HashMap<StringName, RBSet<StringName>> &r_upper_neighbours, const RBSet<StringName> &r_selected_nodes) { + for (const RBSet<StringName>::Element *E = r_selected_nodes.front(); E; E = E->next()) { r_root[E->get()] = E->get(); r_align[E->get()] = E->get(); } @@ -1837,7 +1829,7 @@ void GraphEdit::_horizontal_alignment(Dictionary &r_root, Dictionary &r_align, c } } -void GraphEdit::_crossing_minimisation(HashMap<int, Vector<StringName>> &r_layers, const HashMap<StringName, Set<StringName>> &r_upper_neighbours) { +void GraphEdit::_crossing_minimisation(HashMap<int, Vector<StringName>> &r_layers, const HashMap<StringName, RBSet<StringName>> &r_upper_neighbours) { if (r_layers.size() == 1) { return; } @@ -1868,15 +1860,15 @@ void GraphEdit::_crossing_minimisation(HashMap<int, Vector<StringName>> &r_layer } d[q] = crossings; } - c.set(p, d); + c.insert(p, d); } - r_layers.set(i, _split(lower_layer, c)); + r_layers.insert(i, _split(lower_layer, c)); } } -void GraphEdit::_calculate_inner_shifts(Dictionary &r_inner_shifts, const Dictionary &r_root, const Dictionary &r_node_names, const Dictionary &r_align, const Set<StringName> &r_block_heads, const HashMap<StringName, Pair<int, int>> &r_port_info) { - for (const Set<StringName>::Element *E = r_block_heads.front(); E; E = E->next()) { +void GraphEdit::_calculate_inner_shifts(Dictionary &r_inner_shifts, const Dictionary &r_root, const Dictionary &r_node_names, const Dictionary &r_align, const RBSet<StringName> &r_block_heads, const HashMap<StringName, Pair<int, int>> &r_port_info) { + for (const RBSet<StringName>::Element *E = r_block_heads.front(); E; E = E->next()) { real_t left = 0; StringName u = E->get(); StringName v = r_align[u]; @@ -2034,7 +2026,7 @@ void GraphEdit::_place_block(StringName p_v, float p_delta, const HashMap<int, V threshold = _calculate_threshold(p_v, w, r_node_name, r_layers, r_root, r_align, r_inner_shift, threshold, r_node_positions); w = r_align[w]; } while (w != p_v); - r_node_positions.set(p_v, pos); + r_node_positions.insert(p_v, pos); } #undef PRED @@ -2048,7 +2040,7 @@ void GraphEdit::arrange_nodes() { } Dictionary node_names; - Set<StringName> selected_nodes; + RBSet<StringName> selected_nodes; for (int i = get_child_count() - 1; i >= 0; i--) { GraphNode *gn = Object::cast_to<GraphNode>(get_child(i)); @@ -2059,7 +2051,7 @@ void GraphEdit::arrange_nodes() { node_names[gn->get_name()] = gn; } - HashMap<StringName, Set<StringName>> upper_neighbours; + HashMap<StringName, RBSet<StringName>> upper_neighbours; HashMap<StringName, Pair<int, int>> port_info; Vector2 origin(FLT_MAX, FLT_MAX); @@ -2074,7 +2066,7 @@ void GraphEdit::arrange_nodes() { if (gn->is_selected()) { selected_nodes.insert(gn->get_name()); - Set<StringName> s; + RBSet<StringName> s; for (List<Connection>::Element *E = connections.front(); E; E = E->next()) { GraphNode *p_from = Object::cast_to<GraphNode>(node_names[E->get().from]); if (E->get().to == gn->get_name() && p_from->is_selected()) { @@ -2090,10 +2082,10 @@ void GraphEdit::arrange_nodes() { ports = p_ports; } } - port_info.set(_connection, ports); + port_info.insert(_connection, ports); } } - upper_neighbours.set(gn->get_name(), s); + upper_neighbours.insert(gn->get_name(), s); } } @@ -2111,13 +2103,13 @@ void GraphEdit::arrange_nodes() { HashMap<StringName, Vector2> new_positions; Vector2 default_position(FLT_MAX, FLT_MAX); Dictionary inner_shift; - Set<StringName> block_heads; + RBSet<StringName> block_heads; - for (const Set<StringName>::Element *E = selected_nodes.front(); E; E = E->next()) { + for (const RBSet<StringName>::Element *E = selected_nodes.front(); E; E = E->next()) { inner_shift[E->get()] = 0.0f; sink[E->get()] = E->get(); shift[E->get()] = FLT_MAX; - new_positions.set(E->get(), default_position); + new_positions.insert(E->get(), default_position); if ((StringName)root[E->get()] == E->get()) { block_heads.insert(E->get()); } @@ -2125,19 +2117,19 @@ void GraphEdit::arrange_nodes() { _calculate_inner_shifts(inner_shift, root, node_names, align, block_heads, port_info); - for (const Set<StringName>::Element *E = block_heads.front(); E; E = E->next()) { + for (const RBSet<StringName>::Element *E = block_heads.front(); E; E = E->next()) { _place_block(E->get(), gap_v, layers, root, align, node_names, inner_shift, sink, shift, new_positions); } origin.y = Object::cast_to<GraphNode>(node_names[layers[0][0]])->get_position_offset().y - (new_positions[layers[0][0]].y + (float)inner_shift[layers[0][0]]); origin.x = Object::cast_to<GraphNode>(node_names[layers[0][0]])->get_position_offset().x; - for (const Set<StringName>::Element *E = block_heads.front(); E; E = E->next()) { + for (const RBSet<StringName>::Element *E = block_heads.front(); E; E = E->next()) { StringName u = E->get(); float start_from = origin.y + new_positions[E->get()].y; do { Vector2 cal_pos; cal_pos.y = start_from + (real_t)inner_shift[u]; - new_positions.set(u, cal_pos); + new_positions.insert(u, cal_pos); u = align[u]; } while (u != E->get()); } @@ -2169,7 +2161,7 @@ void GraphEdit::arrange_nodes() { } cal_pos.x = current_node_start_pos; } - new_positions.set(layer[j], cal_pos); + new_positions.insert(layer[j], cal_pos); } start_from += largest_node_size + gap_h; @@ -2177,7 +2169,7 @@ void GraphEdit::arrange_nodes() { } emit_signal(SNAME("begin_node_move")); - for (const Set<StringName>::Element *E = selected_nodes.front(); E; E = E->next()) { + for (const RBSet<StringName>::Element *E = selected_nodes.front(); E; E = E->next()) { GraphNode *gn = Object::cast_to<GraphNode>(node_names[E->get()]); gn->set_drag(true); Vector2 pos = (new_positions[E->get()]); @@ -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 18b9eeebd4..9e34d5528f 100644 --- a/scene/gui/graph_edit.h +++ b/scene/gui/graph_edit.h @@ -125,7 +125,7 @@ private: VScrollBar *v_scroll = nullptr; float port_grab_distance_horizontal = 0.0; - float port_grab_distance_vertical; + float port_grab_distance_vertical = 0.0; Ref<ViewPanner> panner; bool warped_panning = true; @@ -142,7 +142,7 @@ private: bool connecting_target = false; Vector2 connecting_to; String connecting_target_to; - int connecting_target_index; + int connecting_target_index = 0; bool just_disconnected = false; bool connecting_valid = false; Vector2 click_pos; @@ -155,8 +155,9 @@ private: float zoom = 1.0; float zoom_step = 1.2; - float zoom_min; - float zoom_max; + // Proper values set in constructor. + float zoom_min = 0.0; + float zoom_max = 0.0; void _zoom_minus(); void _zoom_reset(); @@ -206,7 +207,7 @@ private: Array _get_connection_list() const; - bool lines_on_bg; + bool lines_on_bg = false; struct ConnType { union { @@ -227,9 +228,9 @@ private: } }; - Set<ConnType> valid_connection_types; - Set<int> valid_left_disconnect_types; - Set<int> valid_right_disconnect_types; + RBSet<ConnType> valid_connection_types; + RBSet<int> valid_left_disconnect_types; + RBSet<int> valid_right_disconnect_types; HashMap<StringName, Vector<GraphNode *>> comment_enclosed_nodes; void _update_comment_enclosed_nodes_list(GraphNode *p_node, HashMap<StringName, Vector<GraphNode *>> &p_comment_enclosed_nodes); @@ -257,12 +258,12 @@ private: UNION, }; - int _set_operations(SET_OPERATIONS p_operation, Set<StringName> &r_u, const Set<StringName> &r_v); - HashMap<int, Vector<StringName>> _layering(const Set<StringName> &r_selected_nodes, const HashMap<StringName, Set<StringName>> &r_upper_neighbours); + int _set_operations(SET_OPERATIONS p_operation, RBSet<StringName> &r_u, const RBSet<StringName> &r_v); + HashMap<int, Vector<StringName>> _layering(const RBSet<StringName> &r_selected_nodes, const HashMap<StringName, RBSet<StringName>> &r_upper_neighbours); Vector<StringName> _split(const Vector<StringName> &r_layer, const HashMap<StringName, Dictionary> &r_crossings); - void _horizontal_alignment(Dictionary &r_root, Dictionary &r_align, const HashMap<int, Vector<StringName>> &r_layers, const HashMap<StringName, Set<StringName>> &r_upper_neighbours, const Set<StringName> &r_selected_nodes); - void _crossing_minimisation(HashMap<int, Vector<StringName>> &r_layers, const HashMap<StringName, Set<StringName>> &r_upper_neighbours); - void _calculate_inner_shifts(Dictionary &r_inner_shifts, const Dictionary &r_root, const Dictionary &r_node_names, const Dictionary &r_align, const Set<StringName> &r_block_heads, const HashMap<StringName, Pair<int, int>> &r_port_info); + void _horizontal_alignment(Dictionary &r_root, Dictionary &r_align, const HashMap<int, Vector<StringName>> &r_layers, const HashMap<StringName, RBSet<StringName>> &r_upper_neighbours, const RBSet<StringName> &r_selected_nodes); + void _crossing_minimisation(HashMap<int, Vector<StringName>> &r_layers, const HashMap<StringName, RBSet<StringName>> &r_upper_neighbours); + void _calculate_inner_shifts(Dictionary &r_inner_shifts, const Dictionary &r_root, const Dictionary &r_node_names, const Dictionary &r_align, const RBSet<StringName> &r_block_heads, const HashMap<StringName, Pair<int, int>> &r_port_info); float _calculate_threshold(StringName p_v, StringName p_w, const Dictionary &r_node_names, const HashMap<int, Vector<StringName>> &r_layers, const Dictionary &r_root, const Dictionary &r_align, const Dictionary &r_inner_shift, real_t p_current_threshold, const HashMap<StringName, Vector2> &r_node_positions); void _place_block(StringName p_v, float p_delta, const HashMap<int, Vector<StringName>> &r_layers, const Dictionary &r_root, const Dictionary &r_align, const Dictionary &r_node_name, const Dictionary &r_inner_shift, Dictionary &r_sink, Dictionary &r_shift, HashMap<StringName, Vector2> &r_node_positions); diff --git a/scene/gui/graph_node.cpp b/scene/gui/graph_node.cpp index ef0ac75cb4..45f036d8fc 100644 --- a/scene/gui/graph_node.cpp +++ b/scene/gui/graph_node.cpp @@ -93,11 +93,13 @@ bool GraphNode::_set(const StringName &p_name, const Variant &p_value) { si.color_right = p_value; } else if (what == "right_icon") { si.custom_slot_right = p_value; + } else if (what == "draw_stylebox") { + si.draw_stylebox = p_value; } else { return false; } - set_slot(idx, si.enable_left, si.type_left, si.color_left, si.enable_right, si.type_right, si.color_right, si.custom_slot_left, si.custom_slot_right); + set_slot(idx, si.enable_left, si.type_left, si.color_left, si.enable_right, si.type_right, si.color_right, si.custom_slot_left, si.custom_slot_right, si.draw_stylebox); update(); return true; } @@ -144,6 +146,8 @@ bool GraphNode::_get(const StringName &p_name, Variant &r_ret) const { r_ret = si.color_right; } else if (what == "right_icon") { r_ret = si.custom_slot_right; + } else if (what == "draw_stylebox") { + r_ret = si.draw_stylebox; } else { return false; } @@ -175,7 +179,7 @@ void GraphNode::_get_property_list(List<PropertyInfo> *p_list) const { p_list->push_back(PropertyInfo(Variant::INT, base + "right_type")); p_list->push_back(PropertyInfo(Variant::COLOR, base + "right_color")); p_list->push_back(PropertyInfo(Variant::OBJECT, base + "right_icon", PROPERTY_HINT_RESOURCE_TYPE, "Texture2D", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_STORE_IF_NULL)); - + p_list->push_back(PropertyInfo(Variant::BOOL, base + "draw_stylebox")); idx++; } } @@ -185,6 +189,7 @@ void GraphNode::_resort() { Size2i new_size = get_size(); Ref<StyleBox> sb = get_theme_stylebox(SNAME("frame")); + Ref<StyleBox> sb_slot = get_theme_stylebox(SNAME("slot")); int sep = get_theme_constant(SNAME("separation")); @@ -193,7 +198,7 @@ void GraphNode::_resort() { int stretch_min = 0; int stretch_avail = 0; float stretch_ratio_total = 0; - Map<Control *, _MinSizeCache> min_size_cache; + HashMap<Control *, _MinSizeCache> min_size_cache; for (int i = 0; i < get_child_count(); i++) { Control *c = Object::cast_to<Control>(get_child(i)); @@ -204,7 +209,7 @@ void GraphNode::_resort() { continue; } - Size2i size = c->get_combined_minimum_size(); + Size2i size = c->get_combined_minimum_size() + (slot_info[i].draw_stylebox ? sb_slot->get_minimum_size() : Size2()); _MinSizeCache msc; stretch_min += size.height; @@ -312,7 +317,9 @@ void GraphNode::_resort() { int size = to - from; - Rect2 rect(sb->get_margin(SIDE_LEFT), from, w, size); + float margin = sb->get_margin(SIDE_LEFT) + (slot_info[i].draw_stylebox ? sb_slot->get_margin(SIDE_LEFT) : 0); + float width = w - (slot_info[i].draw_stylebox ? sb_slot->get_minimum_size().x : 0); + Rect2 rect(margin, from, width, size); fit_child_in_rect(c, rect); cache_y.push_back(from - sb->get_margin(SIDE_TOP) + size * 0.5); @@ -351,14 +358,14 @@ void GraphNode::_notification(int p_what) { Ref<StyleBox> sb; if (comment) { - sb = get_theme_stylebox(selected ? "commentfocus" : "comment"); + sb = get_theme_stylebox(selected ? SNAME("comment_focus") : SNAME("comment")); } else { - sb = get_theme_stylebox(selected ? "selectedframe" : "frame"); + sb = get_theme_stylebox(selected ? SNAME("selected_frame") : SNAME("frame")); } - //sb=sb->duplicate(); - //sb->call("set_modulate",modulate); + Ref<StyleBox> sb_slot = get_theme_stylebox(SNAME("slot")); + Ref<Texture2D> port = get_theme_icon(SNAME("port")); Ref<Texture2D> close = get_theme_icon(SNAME("close")); Ref<Texture2D> resizer = get_theme_icon(SNAME("resizer")); @@ -389,14 +396,9 @@ void GraphNode::_notification(int p_what) { int w = get_size().width - sb->get_minimum_size().x; - if (show_close) { - w -= close->get_width(); - } - - title_buf->set_width(w); title_buf->draw(get_canvas_item(), Point2(sb->get_margin(SIDE_LEFT) + title_h_offset, -title_buf->get_size().y + title_offset), title_color); if (show_close) { - Vector2 cpos = Point2(w + sb->get_margin(SIDE_LEFT) + close_h_offset, -close->get_height() + close_offset); + Vector2 cpos = Point2(w + sb->get_margin(SIDE_LEFT) + close_h_offset - close->get_width(), -close->get_height() + close_offset); draw_texture(close, cpos, close_color); close_rect.position = cpos; close_rect.size = close->get_size(); @@ -412,7 +414,7 @@ void GraphNode::_notification(int p_what) { continue; } const Slot &s = slot_info[E.key]; - //left + // Left port. if (s.enable_left) { Ref<Texture2D> p = port; if (s.custom_slot_left.is_valid()) { @@ -420,6 +422,7 @@ void GraphNode::_notification(int p_what) { } p->draw(get_canvas_item(), icofs + Point2(edgeofs, cache_y[E.key]), s.color_left); } + // Right port. if (s.enable_right) { Ref<Texture2D> p = port; if (s.custom_slot_right.is_valid()) { @@ -427,6 +430,15 @@ void GraphNode::_notification(int p_what) { } p->draw(get_canvas_item(), icofs + Point2(get_size().x - edgeofs, cache_y[E.key]), s.color_right); } + + // Draw slot stylebox. + if (s.draw_stylebox) { + Control *c = Object::cast_to<Control>(get_child(E.key)); + Rect2 c_rect = c->get_rect(); + c_rect.position.x = sb->get_margin(SIDE_LEFT); + c_rect.size.width = w; + draw_style_box(sb_slot, c_rect); + } } if (resizable) { @@ -483,7 +495,7 @@ void GraphNode::_validate_property(PropertyInfo &property) const { } #endif -void GraphNode::set_slot(int p_idx, bool p_enable_left, int p_type_left, const Color &p_color_left, bool p_enable_right, int p_type_right, const Color &p_color_right, const Ref<Texture2D> &p_custom_left, const Ref<Texture2D> &p_custom_right) { +void GraphNode::set_slot(int p_idx, bool p_enable_left, int p_type_left, const Color &p_color_left, bool p_enable_right, int p_type_right, const Color &p_color_right, const Ref<Texture2D> &p_custom_left, const Ref<Texture2D> &p_custom_right, bool p_draw_stylebox) { ERR_FAIL_COND_MSG(p_idx < 0, vformat("Cannot set slot with p_idx (%d) lesser than zero.", p_idx)); if (!p_enable_left && p_type_left == 0 && p_color_left == Color(1, 1, 1, 1) && @@ -502,6 +514,7 @@ void GraphNode::set_slot(int p_idx, bool p_enable_left, int p_type_left, const C s.color_right = p_color_right; s.custom_slot_left = p_custom_left; s.custom_slot_right = p_custom_right; + s.draw_stylebox = p_draw_stylebox; slot_info[p_idx] = s; update(); connpos_dirty = true; @@ -623,16 +636,39 @@ Color GraphNode::get_slot_color_right(int p_idx) const { return slot_info[p_idx].color_right; } +bool GraphNode::is_slot_draw_stylebox(int p_idx) const { + if (!slot_info.has(p_idx)) { + return false; + } + return slot_info[p_idx].draw_stylebox; +} + +void GraphNode::set_slot_draw_stylebox(int p_idx, bool p_enable) { + ERR_FAIL_COND_MSG(p_idx < 0, vformat("Cannot set draw_stylebox for the slot with p_idx (%d) lesser than zero.", p_idx)); + + slot_info[p_idx].draw_stylebox = p_enable; + update(); + connpos_dirty = true; + + emit_signal(SNAME("slot_updated"), p_idx); +} + Size2 GraphNode::get_minimum_size() const { - int sep = get_theme_constant(SNAME("separation")); Ref<StyleBox> sb = get_theme_stylebox(SNAME("frame")); + Ref<StyleBox> sb_slot = get_theme_stylebox(SNAME("slot")); + + int sep = get_theme_constant(SNAME("separation")); + int title_h_offset = get_theme_constant(SNAME("title_h_offset")); + bool first = true; Size2 minsize; - minsize.x = title_buf->get_size().x; + minsize.x = title_buf->get_size().x + title_h_offset; if (show_close) { + int close_h_offset = get_theme_constant(SNAME("close_h_offset")); Ref<Texture2D> close = get_theme_icon(SNAME("close")); - minsize.x += sep + close->get_width(); + //TODO: Remove this magic number after GraphNode rework. + minsize.x += 12 + close->get_width() + close_h_offset; } for (int i = 0; i < get_child_count(); i++) { @@ -645,6 +681,9 @@ Size2 GraphNode::get_minimum_size() const { } Size2i size = c->get_combined_minimum_size(); + if (slot_info.has(i)) { + size += slot_info[i].draw_stylebox ? sb_slot->get_minimum_size() : Size2(); + } minsize.y += size.y; minsize.x = MAX(minsize.x, size.x); @@ -990,7 +1029,7 @@ void GraphNode::_bind_methods() { ClassDB::bind_method(D_METHOD("set_language", "language"), &GraphNode::set_language); ClassDB::bind_method(D_METHOD("get_language"), &GraphNode::get_language); - ClassDB::bind_method(D_METHOD("set_slot", "idx", "enable_left", "type_left", "color_left", "enable_right", "type_right", "color_right", "custom_left", "custom_right"), &GraphNode::set_slot, DEFVAL(Ref<Texture2D>()), DEFVAL(Ref<Texture2D>())); + ClassDB::bind_method(D_METHOD("set_slot", "idx", "enable_left", "type_left", "color_left", "enable_right", "type_right", "color_right", "custom_left", "custom_right", "enable"), &GraphNode::set_slot, DEFVAL(Ref<Texture2D>()), DEFVAL(Ref<Texture2D>()), DEFVAL(true)); ClassDB::bind_method(D_METHOD("clear_slot", "idx"), &GraphNode::clear_slot); ClassDB::bind_method(D_METHOD("clear_all_slots"), &GraphNode::clear_all_slots); @@ -1012,6 +1051,9 @@ void GraphNode::_bind_methods() { ClassDB::bind_method(D_METHOD("set_slot_color_right", "idx", "color_right"), &GraphNode::set_slot_color_right); ClassDB::bind_method(D_METHOD("get_slot_color_right", "idx"), &GraphNode::get_slot_color_right); + ClassDB::bind_method(D_METHOD("is_slot_draw_stylebox", "idx"), &GraphNode::is_slot_draw_stylebox); + ClassDB::bind_method(D_METHOD("set_slot_draw_stylebox", "idx", "draw_stylebox"), &GraphNode::set_slot_draw_stylebox); + ClassDB::bind_method(D_METHOD("set_position_offset", "offset"), &GraphNode::set_position_offset); ClassDB::bind_method(D_METHOD("get_position_offset"), &GraphNode::get_position_offset); diff --git a/scene/gui/graph_node.h b/scene/gui/graph_node.h index 7eb5f27cff..9481a7452d 100644 --- a/scene/gui/graph_node.h +++ b/scene/gui/graph_node.h @@ -54,6 +54,7 @@ private: Color color_right = Color(1, 1, 1, 1); Ref<Texture2D> custom_slot_left; Ref<Texture2D> custom_slot_right; + bool draw_stylebox = true; }; String title; @@ -85,7 +86,7 @@ private: Vector<ConnCache> conn_input_cache; Vector<ConnCache> conn_output_cache; - Map<int, Slot> slot_info; + HashMap<int, Slot> slot_info; bool connpos_dirty = true; @@ -115,7 +116,7 @@ protected: public: bool has_point(const Point2 &p_point) const override; - void set_slot(int p_idx, bool p_enable_left, int p_type_left, const Color &p_color_left, bool p_enable_right, int p_type_right, const Color &p_color_right, const Ref<Texture2D> &p_custom_left = Ref<Texture2D>(), const Ref<Texture2D> &p_custom_right = Ref<Texture2D>()); + void set_slot(int p_idx, bool p_enable_left, int p_type_left, const Color &p_color_left, bool p_enable_right, int p_type_right, const Color &p_color_right, const Ref<Texture2D> &p_custom_left = Ref<Texture2D>(), const Ref<Texture2D> &p_custom_right = Ref<Texture2D>(), bool p_draw_stylebox = true); void clear_slot(int p_idx); void clear_all_slots(); @@ -137,6 +138,9 @@ public: void set_slot_color_right(int p_idx, const Color &p_color_right); Color get_slot_color_right(int p_idx) const; + bool is_slot_draw_stylebox(int p_idx) const; + void set_slot_draw_stylebox(int p_idx, bool p_enable); + void set_title(const String &p_title); String get_title() const; @@ -185,7 +189,9 @@ public: virtual Vector<int> get_allowed_size_flags_horizontal() const override; virtual Vector<int> get_allowed_size_flags_vertical() const override; - bool is_resizing() const { return resizing; } + bool is_resizing() const { + return resizing; + } GraphNode(); }; diff --git a/scene/gui/grid_container.cpp b/scene/gui/grid_container.cpp index 3c1f4bb93b..b2cf2f66a9 100644 --- a/scene/gui/grid_container.cpp +++ b/scene/gui/grid_container.cpp @@ -33,13 +33,13 @@ void GridContainer::_notification(int p_what) { switch (p_what) { case NOTIFICATION_SORT_CHILDREN: { - Map<int, int> col_minw; // Max of min_width of all controls in each col (indexed by col). - Map<int, int> row_minh; // Max of min_height of all controls in each row (indexed by row). - Set<int> col_expanded; // Columns which have the SIZE_EXPAND flag set. - Set<int> row_expanded; // Rows which have the SIZE_EXPAND flag set. + HashMap<int, int> col_minw; // Max of min_width of all controls in each col (indexed by col). + HashMap<int, int> row_minh; // Max of min_height of all controls in each row (indexed by row). + RBSet<int> col_expanded; // Columns which have the SIZE_EXPAND flag set. + RBSet<int> row_expanded; // Rows which have the SIZE_EXPAND flag set. - int hsep = get_theme_constant(SNAME("hseparation")); - int vsep = get_theme_constant(SNAME("vseparation")); + int hsep = get_theme_constant(SNAME("h_separation")); + int vsep = get_theme_constant(SNAME("v_separation")); int max_col = MIN(get_child_count(), columns); int max_row = ceil((float)get_child_count() / (float)columns); @@ -50,6 +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; @@ -101,7 +104,7 @@ void GridContainer::_notification(int p_what) { // Check if all minwidth constraints are OK if we use the remaining space. can_fit = true; int max_index = col_expanded.front()->get(); - for (Set<int>::Element *E = col_expanded.front(); E; E = E->next()) { + for (RBSet<int>::Element *E = col_expanded.front(); E; E = E->next()) { if (col_minw[E->get()] > col_minw[max_index]) { max_index = E->get(); } @@ -122,7 +125,7 @@ void GridContainer::_notification(int p_what) { // Check if all minheight constraints are OK if we use the remaining space. can_fit = true; int max_index = row_expanded.front()->get(); - for (Set<int>::Element *E = row_expanded.front(); E; E = E->next()) { + for (RBSet<int>::Element *E = row_expanded.front(); E; E = E->next()) { if (row_minh[E->get()] > row_minh[max_index]) { max_index = E->get(); } @@ -211,11 +214,11 @@ void GridContainer::_bind_methods() { } Size2 GridContainer::get_minimum_size() const { - Map<int, int> col_minw; - Map<int, int> row_minh; + HashMap<int, int> col_minw; + HashMap<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..8b22f3722a 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; @@ -603,7 +606,7 @@ void ItemList::gui_input(const Ref<InputEvent> &p_event) { return; } - if (mb.is_valid() && (mb->get_button_index() == MouseButton::LEFT || (allow_rmb_select && mb->get_button_index() == MouseButton::RIGHT)) && mb->is_pressed()) { + if (mb.is_valid() && mb->is_pressed()) { search_string = ""; //any mousepress cancels Vector2 pos = mb->get_position(); Ref<StyleBox> bg = get_theme_stylebox(SNAME("bg")); @@ -628,7 +631,7 @@ void ItemList::gui_input(const Ref<InputEvent> &p_event) { } } - if (closest != -1) { + if (closest != -1 && (mb->get_button_index() == MouseButton::LEFT || (allow_rmb_select && mb->get_button_index() == MouseButton::RIGHT))) { int i = closest; if (select_mode == SELECT_MULTI && items[i].selected && mb->is_command_pressed()) { @@ -642,55 +645,47 @@ void ItemList::gui_input(const Ref<InputEvent> &p_event) { SWAP(from, to); } for (int j = from; j <= to; j++) { + if (!CAN_SELECT(j)) { + continue; + } bool selected = !items[j].selected; select(j, false); if (selected) { emit_signal(SNAME("multi_selected"), j, true); } } + emit_signal(SNAME("item_clicked"), i, get_local_mouse_position(), mb->get_button_index()); - if (mb->get_button_index() == MouseButton::RIGHT) { - emit_signal(SNAME("item_rmb_selected"), i, get_local_mouse_position()); - } } else { if (!mb->is_double_click() && !mb->is_command_pressed() && select_mode == SELECT_MULTI && items[i].selectable && !items[i].disabled && items[i].selected && mb->get_button_index() == MouseButton::LEFT) { defer_select_single = i; return; } - if (items[i].selected && mb->get_button_index() == MouseButton::RIGHT) { - emit_signal(SNAME("item_rmb_selected"), i, get_local_mouse_position()); - } else { - bool selected = items[i].selected; - + if (!items[i].selected || allow_reselect) { select(i, select_mode == SELECT_SINGLE || !mb->is_command_pressed()); - if (!selected || allow_reselect) { - if (select_mode == SELECT_SINGLE) { - emit_signal(SNAME("item_selected"), i); - } else { - emit_signal(SNAME("multi_selected"), i, true); - } + if (select_mode == SELECT_SINGLE) { + emit_signal(SNAME("item_selected"), i); + } else { + emit_signal(SNAME("multi_selected"), i, true); } + } - if (mb->get_button_index() == MouseButton::RIGHT) { - emit_signal(SNAME("item_rmb_selected"), i, get_local_mouse_position()); - } else if (/*select_mode==SELECT_SINGLE &&*/ mb->is_double_click()) { - emit_signal(SNAME("item_activated"), i); - } + emit_signal(SNAME("item_clicked"), i, get_local_mouse_position(), mb->get_button_index()); + + if (mb->get_button_index() == MouseButton::LEFT && mb->is_double_click()) { + emit_signal(SNAME("item_activated"), i); } } return; + } else if (closest != -1) { + emit_signal(SNAME("item_clicked"), closest, get_local_mouse_position(), mb->get_button_index()); + } else { + // Since closest is null, more likely we clicked on empty space, so send signal to interested controls. Allows, for example, implement items deselecting. + emit_signal(SNAME("empty_clicked"), get_local_mouse_position(), mb->get_button_index()); } - if (mb->get_button_index() == MouseButton::RIGHT) { - emit_signal(SNAME("rmb_clicked"), mb->get_position()); - - return; - } - - // Since closest is null, more likely we clicked on empty space, so send signal to interested controls. Allows, for example, implement items deselecting. - emit_signal(SNAME("nothing_selected")); } if (mb.is_valid() && mb->get_button_index() == MouseButton::WHEEL_UP && mb->is_pressed()) { scroll_bar->set_value(scroll_bar->get_value() - scroll_bar->get_page() * mb->get_factor() / 8); @@ -707,7 +702,7 @@ void ItemList::gui_input(const Ref<InputEvent> &p_event) { if (diff < uint64_t(ProjectSettings::get_singleton()->get("gui/timers/incremental_search_max_interval_msec")) * 2) { for (int i = current - 1; i >= 0; i--) { - if (items[i].text.begins_with(search_string)) { + if (CAN_SELECT(i) && items[i].text.begins_with(search_string)) { set_current(i); ensure_current_is_visible(); if (select_mode == SELECT_SINGLE) { @@ -723,7 +718,15 @@ void ItemList::gui_input(const Ref<InputEvent> &p_event) { } if (current >= current_columns) { - set_current(current - current_columns); + int next = current - current_columns; + while (next >= 0 && !CAN_SELECT(next)) { + next = next - current_columns; + } + if (next < 0) { + accept_event(); + return; + } + set_current(next); ensure_current_is_visible(); if (select_mode == SELECT_SINGLE) { emit_signal(SNAME("item_selected"), current); @@ -737,7 +740,7 @@ void ItemList::gui_input(const Ref<InputEvent> &p_event) { if (diff < uint64_t(ProjectSettings::get_singleton()->get("gui/timers/incremental_search_max_interval_msec")) * 2) { for (int i = current + 1; i < items.size(); i++) { - if (items[i].text.begins_with(search_string)) { + if (CAN_SELECT(i) && items[i].text.begins_with(search_string)) { set_current(i); ensure_current_is_visible(); if (select_mode == SELECT_SINGLE) { @@ -752,7 +755,15 @@ void ItemList::gui_input(const Ref<InputEvent> &p_event) { } if (current < items.size() - current_columns) { - set_current(current + current_columns); + int next = current + current_columns; + while (next < items.size() && !CAN_SELECT(next)) { + next = next + current_columns; + } + if (next >= items.size()) { + accept_event(); + return; + } + set_current(next); ensure_current_is_visible(); if (select_mode == SELECT_SINGLE) { emit_signal(SNAME("item_selected"), current); @@ -763,7 +774,7 @@ void ItemList::gui_input(const Ref<InputEvent> &p_event) { search_string = ""; //any mousepress cancels for (int i = 4; i > 0; i--) { - if (current - current_columns * i >= 0) { + if (current - current_columns * i >= 0 && CAN_SELECT(current - current_columns * i)) { set_current(current - current_columns * i); ensure_current_is_visible(); if (select_mode == SELECT_SINGLE) { @@ -777,7 +788,7 @@ void ItemList::gui_input(const Ref<InputEvent> &p_event) { search_string = ""; //any mousepress cancels for (int i = 4; i > 0; i--) { - if (current + current_columns * i < items.size()) { + if (current + current_columns * i < items.size() && CAN_SELECT(current + current_columns * i)) { set_current(current + current_columns * i); ensure_current_is_visible(); if (select_mode == SELECT_SINGLE) { @@ -792,7 +803,16 @@ void ItemList::gui_input(const Ref<InputEvent> &p_event) { search_string = ""; //any mousepress cancels if (current % current_columns != 0) { - set_current(current - 1); + int current_row = current / current_columns; + int next = current - 1; + while (!CAN_SELECT(next)) { + next = next - 1; + } + if (next < 0 || !IS_SAME_ROW(next, current_row)) { + accept_event(); + return; + } + set_current(next); ensure_current_is_visible(); if (select_mode == SELECT_SINGLE) { emit_signal(SNAME("item_selected"), current); @@ -803,7 +823,16 @@ void ItemList::gui_input(const Ref<InputEvent> &p_event) { search_string = ""; //any mousepress cancels if (current % current_columns != (current_columns - 1) && current + 1 < items.size()) { - set_current(current + 1); + int current_row = current / current_columns; + int next = current + 1; + while (!CAN_SELECT(next)) { + next = next + 1; + } + if (items.size() <= next || !IS_SAME_ROW(next, current_row)) { + accept_event(); + return; + } + set_current(next); ensure_current_is_visible(); if (select_mode == SELECT_SINGLE) { emit_signal(SNAME("item_selected"), current); @@ -879,6 +908,9 @@ void ItemList::gui_input(const Ref<InputEvent> &p_event) { if (scroll_bar->get_value() != prev_scroll) { accept_event(); //accept event if scroll changed } + +#undef CAN_SELECT +#undef IS_SAME_ROW } void ItemList::ensure_current_is_visible() { @@ -937,8 +969,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")); @@ -1744,11 +1776,10 @@ void ItemList::_bind_methods() { BIND_ENUM_CONSTANT(SELECT_MULTI); ADD_SIGNAL(MethodInfo("item_selected", PropertyInfo(Variant::INT, "index"))); - ADD_SIGNAL(MethodInfo("item_rmb_selected", PropertyInfo(Variant::INT, "index"), PropertyInfo(Variant::VECTOR2, "at_position"))); + ADD_SIGNAL(MethodInfo("empty_clicked", PropertyInfo(Variant::VECTOR2, "at_position"), PropertyInfo(Variant::INT, "mouse_button_index"))); + ADD_SIGNAL(MethodInfo("item_clicked", PropertyInfo(Variant::INT, "index"), PropertyInfo(Variant::VECTOR2, "at_position"), PropertyInfo(Variant::INT, "mouse_button_index"))); ADD_SIGNAL(MethodInfo("multi_selected", PropertyInfo(Variant::INT, "index"), PropertyInfo(Variant::BOOL, "selected"))); ADD_SIGNAL(MethodInfo("item_activated", PropertyInfo(Variant::INT, "index"))); - ADD_SIGNAL(MethodInfo("rmb_clicked", PropertyInfo(Variant::VECTOR2, "at_position"))); - ADD_SIGNAL(MethodInfo("nothing_selected")); GLOBAL_DEF("gui/timers/incremental_search_max_interval_msec", 2000); ProjectSettings::get_singleton()->set_custom_property_info("gui/timers/incremental_search_max_interval_msec", PropertyInfo(Variant::INT, "gui/timers/incremental_search_max_interval_msec", PROPERTY_HINT_RANGE, "0,10000,1,or_greater")); // No negative numbers diff --git a/scene/gui/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 b3f051bb75..73188d6602 100644 --- a/scene/gui/line_edit.cpp +++ b/scene/gui/line_edit.cpp @@ -406,9 +406,45 @@ void LineEdit::gui_input(const Ref<InputEvent> &p_event) { if (k.is_valid()) { if (!k->is_pressed()) { + if (alt_start && k->get_keycode() == Key::ALT) { + alt_start = false; + if ((alt_code > 0x31 && alt_code < 0xd800) || (alt_code > 0xdfff && alt_code <= 0x10ffff)) { + char32_t ucodestr[2] = { (char32_t)alt_code, 0 }; + insert_text_at_caret(ucodestr); + } + accept_event(); + return; + } return; } + // Alt+ Unicode input: + if (k->is_alt_pressed()) { + if (!alt_start) { + if (k->get_keycode() == Key::KP_ADD) { + alt_start = true; + alt_code = 0; + accept_event(); + return; + } + } else { + if (k->get_keycode() >= Key::KEY_0 && k->get_keycode() <= Key::KEY_9) { + alt_code = alt_code << 4; + alt_code += (uint32_t)(k->get_keycode() - Key::KEY_0); + } + if (k->get_keycode() >= Key::KP_0 && k->get_keycode() <= Key::KP_9) { + alt_code = alt_code << 4; + alt_code += (uint32_t)(k->get_keycode() - Key::KP_0); + } + if (k->get_keycode() >= Key::A && k->get_keycode() <= Key::F) { + alt_code = alt_code << 4; + alt_code += (uint32_t)(k->get_keycode() - Key::A) + 10; + } + accept_event(); + return; + } + } + if (context_menu_enabled) { if (k->is_action("ui_menu", true)) { _ensure_menu(); @@ -1452,7 +1488,7 @@ bool LineEdit::get_draw_control_chars() const { return draw_control_chars; } -void LineEdit::set_structured_text_bidi_override(Control::StructuredTextParser p_parser) { +void LineEdit::set_structured_text_bidi_override(TextServer::StructuredTextParser p_parser) { if (st_parser != p_parser) { st_parser = p_parser; _shape(); @@ -1460,7 +1496,7 @@ void LineEdit::set_structured_text_bidi_override(Control::StructuredTextParser p } } -Control::StructuredTextParser LineEdit::get_structured_text_bidi_override() const { +TextServer::StructuredTextParser LineEdit::get_structured_text_bidi_override() const { return st_parser; } diff --git a/scene/gui/line_edit.h b/scene/gui/line_edit.h index b86ccd6421..0fb178fca4 100644 --- a/scene/gui/line_edit.h +++ b/scene/gui/line_edit.h @@ -77,6 +77,9 @@ private: bool pass = false; bool text_changed_dirty = false; + bool alt_start = false; + uint32_t alt_code = 0; + String undo_text; String text; String placeholder; @@ -106,7 +109,7 @@ private: String language; TextDirection text_direction = TEXT_DIRECTION_AUTO; TextDirection input_direction = TEXT_DIRECTION_LTR; - Control::StructuredTextParser st_parser = STRUCTURED_TEXT_DEFAULT; + TextServer::StructuredTextParser st_parser = TextServer::STRUCTURED_TEXT_DEFAULT; Array st_args; bool draw_control_chars = false; @@ -253,8 +256,8 @@ public: void set_draw_control_chars(bool p_draw_control_chars); bool get_draw_control_chars() const; - void set_structured_text_bidi_override(Control::StructuredTextParser p_parser); - Control::StructuredTextParser get_structured_text_bidi_override() const; + void set_structured_text_bidi_override(TextServer::StructuredTextParser p_parser); + TextServer::StructuredTextParser get_structured_text_bidi_override() const; void set_structured_text_bidi_override_options(Array p_args); Array get_structured_text_bidi_override_options() const; 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 1feee017c2..316fee53fe 100644 --- a/scene/gui/menu_button.cpp +++ b/scene/gui/menu_button.cpp @@ -187,7 +187,7 @@ void MenuButton::_get_property_list(List<PropertyInfo> *p_list) const { pi.usage &= ~(popup->get_item_icon(i).is_null() ? PROPERTY_USAGE_STORAGE : 0); p_list->push_back(pi); - pi = PropertyInfo(Variant::INT, vformat("popup/item_%d/checkable", i), PROPERTY_HINT_ENUM, "No,As checkbox,As radio button"); + pi = PropertyInfo(Variant::INT, vformat("popup/item_%d/checkable", i), PROPERTY_HINT_ENUM, "No,As Checkbox,As Radio Button"); pi.usage &= ~(!popup->is_item_checkable(i) ? PROPERTY_USAGE_STORAGE : 0); p_list->push_back(pi); 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 921b76c52a..7896132626 100644 --- a/scene/gui/option_button.h +++ b/scene/gui/option_button.h @@ -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_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 518ba14dae..12587b7e73 100644 --- a/scene/gui/popup_menu.h +++ b/scene/gui/popup_menu.h @@ -87,6 +87,7 @@ class PopupMenu : public Popup { }; bool close_allowed = false; + bool activated_by_keyboard = false; Timer *minimum_lifetime_timer = nullptr; Timer *submenu_timer = nullptr; @@ -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; @@ -116,7 +117,7 @@ class PopupMenu : public Popup { bool hide_on_multistate_item_selection = false; Vector2 moved; - Map<Ref<Shortcut>, int> shortcut_refcount; + HashMap<Ref<Shortcut>, int> shortcut_refcount; void _ref_shortcut(Ref<Shortcut> p_sc); void _unref_shortcut(Ref<Shortcut> p_sc); diff --git a/scene/gui/progress_bar.cpp b/scene/gui/progress_bar.cpp index 50ffb3ca67..f36682942f 100644 --- a/scene/gui/progress_bar.cpp +++ b/scene/gui/progress_bar.cpp @@ -62,15 +62,42 @@ void ProgressBar::_notification(int p_what) { Color font_color = get_theme_color(SNAME("font_color")); draw_style_box(bg, Rect2(Point2(), get_size())); + float r = get_as_ratio(); - int mp = fg->get_minimum_size().width; - int p = r * (get_size().width - mp); - if (p > 0) { - if (is_layout_rtl()) { - draw_style_box(fg, Rect2(Point2(p, 0), Size2(fg->get_minimum_size().width, get_size().height))); - } else { - draw_style_box(fg, Rect2(Point2(0, 0), Size2(p + fg->get_minimum_size().width, get_size().height))); - } + + switch (mode) { + case FILL_BEGIN_TO_END: + case FILL_END_TO_BEGIN: { + int mp = fg->get_minimum_size().width; + int p = round(r * (get_size().width - mp)); + // We want FILL_BEGIN_TO_END to map to right to left when UI layout is RTL, + // and left to right otherwise. And likewise for FILL_END_TO_BEGIN. + bool right_to_left = is_layout_rtl() ? (mode == FILL_BEGIN_TO_END) : (mode == FILL_END_TO_BEGIN); + if (p > 0) { + if (right_to_left) { + int p_remaining = round((1.0 - r) * (get_size().width - mp)); + draw_style_box(fg, Rect2(Point2(p_remaining, 0), Size2(p + fg->get_minimum_size().width, get_size().height))); + } else { + draw_style_box(fg, Rect2(Point2(0, 0), Size2(p + fg->get_minimum_size().width, get_size().height))); + } + } + } break; + case FILL_TOP_TO_BOTTOM: + case FILL_BOTTOM_TO_TOP: { + int mp = fg->get_minimum_size().height; + int p = round(r * (get_size().height - mp)); + + if (p > 0) { + if (mode == FILL_TOP_TO_BOTTOM) { + draw_style_box(fg, Rect2(Point2(0, 0), Size2(get_size().width, p + fg->get_minimum_size().height))); + } else { + int p_remaining = round((1.0 - r) * (get_size().height - mp)); + draw_style_box(fg, Rect2(Point2(0, p_remaining), Size2(get_size().width, p + fg->get_minimum_size().height))); + } + } + } break; + case FILL_MODE_MAX: + break; } if (percent_visible) { @@ -88,6 +115,16 @@ void ProgressBar::_notification(int p_what) { } } +void ProgressBar::set_fill_mode(int p_fill) { + ERR_FAIL_INDEX(p_fill, FILL_MODE_MAX); + mode = (FillMode)p_fill; + update(); +} + +int ProgressBar::get_fill_mode() { + return mode; +} + void ProgressBar::set_percent_visible(bool p_visible) { percent_visible = p_visible; update(); @@ -98,10 +135,18 @@ bool ProgressBar::is_percent_visible() const { } void ProgressBar::_bind_methods() { + ClassDB::bind_method(D_METHOD("set_fill_mode", "mode"), &ProgressBar::set_fill_mode); + ClassDB::bind_method(D_METHOD("get_fill_mode"), &ProgressBar::get_fill_mode); ClassDB::bind_method(D_METHOD("set_percent_visible", "visible"), &ProgressBar::set_percent_visible); ClassDB::bind_method(D_METHOD("is_percent_visible"), &ProgressBar::is_percent_visible); + ADD_PROPERTY(PropertyInfo(Variant::INT, "fill_mode", PROPERTY_HINT_ENUM, "Begin to End,End to Begin,Top to Bottom,Bottom to Top"), "set_fill_mode", "get_fill_mode"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "percent_visible"), "set_percent_visible", "is_percent_visible"); + + BIND_ENUM_CONSTANT(FILL_BEGIN_TO_END); + BIND_ENUM_CONSTANT(FILL_END_TO_BEGIN); + BIND_ENUM_CONSTANT(FILL_TOP_TO_BOTTOM); + BIND_ENUM_CONSTANT(FILL_BOTTOM_TO_TOP); } ProgressBar::ProgressBar() { diff --git a/scene/gui/progress_bar.h b/scene/gui/progress_bar.h index 2d89163f78..5ba21ad7d5 100644 --- a/scene/gui/progress_bar.h +++ b/scene/gui/progress_bar.h @@ -43,11 +43,27 @@ protected: static void _bind_methods(); public: + enum FillMode { + FILL_BEGIN_TO_END, + FILL_END_TO_BEGIN, + FILL_TOP_TO_BOTTOM, + FILL_BOTTOM_TO_TOP, + FILL_MODE_MAX + }; + + void set_fill_mode(int p_fill); + int get_fill_mode(); + void set_percent_visible(bool p_visible); bool is_percent_visible() const; Size2 get_minimum_size() const override; ProgressBar(); + +private: + FillMode mode = FILL_BEGIN_TO_END; }; +VARIANT_ENUM_CAST(ProgressBar::FillMode); + #endif // PROGRESS_BAR_H diff --git a/scene/gui/range.cpp b/scene/gui/range.cpp index 8e66826e9d..73f19a8eda 100644 --- a/scene/gui/range.cpp +++ b/scene/gui/range.cpp @@ -50,7 +50,7 @@ void Range::_value_changed_notify() { } void Range::Shared::emit_value_changed() { - for (Set<Range *>::Element *E = owners.front(); E; E = E->next()) { + for (RBSet<Range *>::Element *E = owners.front(); E; E = E->next()) { Range *r = E->get(); if (!r->is_inside_tree()) { continue; @@ -70,7 +70,7 @@ void Range::_validate_values() { } void Range::Shared::emit_changed(const char *p_what) { - for (Set<Range *>::Element *E = owners.front(); E; E = E->next()) { + for (RBSet<Range *>::Element *E = owners.front(); E; E = E->next()) { Range *r = E->get(); if (!r->is_inside_tree()) { continue; diff --git a/scene/gui/range.h b/scene/gui/range.h index 46b0d39202..a59bfa9677 100644 --- a/scene/gui/range.h +++ b/scene/gui/range.h @@ -45,7 +45,7 @@ class Range : public Control { bool exp_ratio = false; bool allow_greater = false; bool allow_lesser = false; - Set<Range *> owners; + RBSet<Range *> owners; void emit_value_changed(); void emit_changed(const char *p_what = ""); }; diff --git a/scene/gui/rich_text_label.cpp b/scene/gui/rich_text_label.cpp index 981766e5eb..fa0c2ce12c 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" @@ -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++) { @@ -517,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. @@ -852,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(); @@ -1389,8 +1390,8 @@ float RichTextLabel::_find_click_in_line(ItemFrame *p_frame, int p_line, const V if (p_click.y >= rect.position.y && p_click.y <= rect.position.y + rect.size.y) { switch (it->type) { case ITEM_TABLE: { - int hseparation = get_theme_constant(SNAME("table_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); @@ -1448,7 +1449,7 @@ float RichTextLabel::_find_click_in_line(ItemFrame *p_frame, int p_line, const V } Rect2 rect = Rect2(p_ofs + off - Vector2(0, TS->shaped_text_get_ascent(rid)) - p_frame->padding.position, TS->shaped_text_get_size(rid) + p_frame->padding.position + p_frame->padding.size); if (p_table) { - rect.size.y += get_theme_constant(SNAME("table_vseparation")); + rect.size.y += get_theme_constant(SNAME("table_v_separation")); } if (p_click.y >= rect.position.y && p_click.y <= rect.position.y + rect.size.y) { @@ -1868,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; @@ -1909,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; } @@ -2224,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) { @@ -2814,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); @@ -3153,7 +3177,7 @@ void RichTextLabel::append_text(const String &p_bbcode) { // Find optional parameters. String bbcode_name; - typedef Map<String, String> OptionMap; + typedef HashMap<String, String> OptionMap; OptionMap bbcode_options; if (!split_tag_block.is_empty()) { bbcode_name = split_tag_block[0]; @@ -3439,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) { @@ -3465,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; } } } @@ -3596,9 +3620,9 @@ void RichTextLabel::append_text(const String &p_bbcode) { Ref<Texture2D> texture = ResourceLoader::load(image, "Texture2D"); if (texture.is_valid()) { Color color = Color(1.0, 1.0, 1.0); - OptionMap::Element *color_option = bbcode_options.find("color"); + OptionMap::Iterator color_option = bbcode_options.find("color"); if (color_option) { - color = Color::from_string(color_option->value(), color); + color = Color::from_string(color_option->value, color); } int width = 0; @@ -3612,14 +3636,14 @@ void RichTextLabel::append_text(const String &p_bbcode) { height = bbcode_value.substr(sep + 1).to_int(); } } else { - OptionMap::Element *width_option = bbcode_options.find("width"); + OptionMap::Iterator width_option = bbcode_options.find("width"); if (width_option) { - width = width_option->value().to_int(); + width = width_option->value.to_int(); } - OptionMap::Element *height_option = bbcode_options.find("height"); + OptionMap::Iterator height_option = bbcode_options.find("height"); if (height_option) { - height = height_option->value().to_int(); + height = height_option->value.to_int(); } } @@ -3705,15 +3729,15 @@ void RichTextLabel::append_text(const String &p_bbcode) { } else if (bbcode_name == "fade") { int start_index = 0; - OptionMap::Element *start_option = bbcode_options.find("start"); + OptionMap::Iterator start_option = bbcode_options.find("start"); if (start_option) { - start_index = start_option->value().to_int(); + start_index = start_option->value.to_int(); } int length = 10; - OptionMap::Element *length_option = bbcode_options.find("length"); + OptionMap::Iterator length_option = bbcode_options.find("length"); if (length_option) { - length = length_option->value().to_int(); + length = length_option->value.to_int(); } push_fade(start_index, length); @@ -3721,15 +3745,15 @@ void RichTextLabel::append_text(const String &p_bbcode) { tag_stack.push_front("fade"); } else if (bbcode_name == "shake") { int strength = 5; - OptionMap::Element *strength_option = bbcode_options.find("level"); + OptionMap::Iterator strength_option = bbcode_options.find("level"); if (strength_option) { - strength = strength_option->value().to_int(); + strength = strength_option->value.to_int(); } float rate = 20.0f; - OptionMap::Element *rate_option = bbcode_options.find("rate"); + OptionMap::Iterator rate_option = bbcode_options.find("rate"); if (rate_option) { - rate = rate_option->value().to_float(); + rate = rate_option->value.to_float(); } push_shake(strength, rate); @@ -3738,15 +3762,15 @@ void RichTextLabel::append_text(const String &p_bbcode) { set_process_internal(true); } else if (bbcode_name == "wave") { float amplitude = 20.0f; - OptionMap::Element *amplitude_option = bbcode_options.find("amp"); + OptionMap::Iterator amplitude_option = bbcode_options.find("amp"); if (amplitude_option) { - amplitude = amplitude_option->value().to_float(); + amplitude = amplitude_option->value.to_float(); } float period = 5.0f; - OptionMap::Element *period_option = bbcode_options.find("freq"); + OptionMap::Iterator period_option = bbcode_options.find("freq"); if (period_option) { - period = period_option->value().to_float(); + period = period_option->value.to_float(); } push_wave(period, amplitude); @@ -3755,15 +3779,15 @@ void RichTextLabel::append_text(const String &p_bbcode) { set_process_internal(true); } else if (bbcode_name == "tornado") { float radius = 10.0f; - OptionMap::Element *radius_option = bbcode_options.find("radius"); + OptionMap::Iterator radius_option = bbcode_options.find("radius"); if (radius_option) { - radius = radius_option->value().to_float(); + radius = radius_option->value.to_float(); } float frequency = 1.0f; - OptionMap::Element *frequency_option = bbcode_options.find("freq"); + OptionMap::Iterator frequency_option = bbcode_options.find("freq"); if (frequency_option) { - frequency = frequency_option->value().to_float(); + frequency = frequency_option->value.to_float(); } push_tornado(frequency, radius); @@ -3772,21 +3796,21 @@ void RichTextLabel::append_text(const String &p_bbcode) { set_process_internal(true); } else if (bbcode_name == "rainbow") { float saturation = 0.8f; - OptionMap::Element *saturation_option = bbcode_options.find("sat"); + OptionMap::Iterator saturation_option = bbcode_options.find("sat"); if (saturation_option) { - saturation = saturation_option->value().to_float(); + saturation = saturation_option->value.to_float(); } float value = 0.8f; - OptionMap::Element *value_option = bbcode_options.find("val"); + OptionMap::Iterator value_option = bbcode_options.find("val"); if (value_option) { - value = value_option->value().to_float(); + value = value_option->value.to_float(); } float frequency = 1.0f; - OptionMap::Element *frequency_option = bbcode_options.find("freq"); + OptionMap::Iterator frequency_option = bbcode_options.find("freq"); if (frequency_option) { - frequency = frequency_option->value().to_float(); + frequency = frequency_option->value.to_float(); } push_rainbow(saturation, value, frequency); @@ -4171,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 ""; @@ -4196,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; } @@ -4279,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 @@ -4288,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; } @@ -4423,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); @@ -4485,12 +4582,19 @@ 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); @@ -4533,6 +4637,9 @@ 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); + 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"); @@ -4554,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"); @@ -4733,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); diff --git a/scene/gui/rich_text_label.h b/scene/gui/rich_text_label.h index b710413987..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; @@ -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,11 +605,16 @@ 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); @@ -607,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/tab_bar.cpp b/scene/gui/tab_bar.cpp index ce2dca0ea3..b96ba0ebf9 100644 --- a/scene/gui/tab_bar.cpp +++ b/scene/gui/tab_bar.cpp @@ -49,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); @@ -477,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); @@ -1272,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_container.cpp b/scene/gui/tab_container.cpp index 84a62c71c2..8299d73b68 100644 --- a/scene/gui/tab_container.cpp +++ b/scene/gui/tab_container.cpp @@ -208,8 +208,8 @@ void TabContainer::_on_theme_changed() { 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("hseparation"), 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(); @@ -523,7 +523,7 @@ void TabContainer::move_child_notify(Node *p_child) { Control *c = Object::cast_to<Control>(p_child); if (c && !c->is_set_as_top_level()) { int old_idx = -1; - String tab_name = c->has_meta("_tab_name") ? String(c->get_meta("_tab_name")) : String(c->get_name()); + String tab_name = String(c->get_meta("_tab_name", c->get_name())); // Find the previous tab index of the control. for (int i = 0; i < get_tab_count(); i++) { @@ -556,9 +556,7 @@ void TabContainer::remove_child_notify(Node *p_child) { update(); } - if (p_child->has_meta("_tab_name")) { - p_child->remove_meta("_tab_name"); - } + p_child->remove_meta("_tab_name"); p_child->disconnect("renamed", callable_mp(this, &TabContainer::_refresh_tab_names)); // TabBar won't emit the "tab_changed" signal when not inside the tree. @@ -679,9 +677,7 @@ void TabContainer::set_tab_title(int p_tab, const String &p_title) { tab_bar->set_tab_title(p_tab, p_title); if (p_title == child->get_name()) { - if (child->has_meta("_tab_name")) { - child->remove_meta("_tab_name"); - } + child->remove_meta("_tab_name"); } else { child->set_meta("_tab_name", p_title); } diff --git a/scene/gui/text_edit.cpp b/scene/gui/text_edit.cpp index 86969e3ef4..315ffbd419 100644 --- a/scene/gui/text_edit.cpp +++ b/scene/gui/text_edit.cpp @@ -1428,7 +1428,7 @@ void TextEdit::_notification(int p_what) { } } - if (draw_placeholder) { + if (!draw_placeholder) { line_drawing_cache[line] = cache_entry; } } @@ -1557,6 +1557,47 @@ void TextEdit::unhandled_key_input(const Ref<InputEvent> &p_event) { } } +bool TextEdit::alt_input(const Ref<InputEvent> &p_gui_input) { + Ref<InputEventKey> k = p_gui_input; + if (k.is_valid()) { + if (!k->is_pressed()) { + if (alt_start && k->get_keycode() == Key::ALT) { + alt_start = false; + if ((alt_code > 0x31 && alt_code < 0xd800) || (alt_code > 0xdfff && alt_code <= 0x10ffff)) { + handle_unicode_input(alt_code); + } + return true; + } + return false; + } + + if (k->is_alt_pressed()) { + if (!alt_start) { + if (k->get_keycode() == Key::KP_ADD) { + alt_start = true; + alt_code = 0; + return true; + } + } else { + if (k->get_keycode() >= Key::KEY_0 && k->get_keycode() <= Key::KEY_9) { + alt_code = alt_code << 4; + alt_code += (uint32_t)(k->get_keycode() - Key::KEY_0); + } + if (k->get_keycode() >= Key::KP_0 && k->get_keycode() <= Key::KP_9) { + alt_code = alt_code << 4; + alt_code += (uint32_t)(k->get_keycode() - Key::KP_0); + } + if (k->get_keycode() >= Key::A && k->get_keycode() <= Key::F) { + alt_code = alt_code << 4; + alt_code += (uint32_t)(k->get_keycode() - Key::A) + 10; + } + return true; + } + } + } + return false; +} + void TextEdit::gui_input(const Ref<InputEvent> &p_gui_input) { ERR_FAIL_COND(p_gui_input.is_null()); @@ -1640,7 +1681,7 @@ void TextEdit::gui_input(const Ref<InputEvent> &p_gui_input) { set_caret_column(col); selection.drag_attempt = false; - if (mb->is_shift_pressed() && (caret.column != prev_col || caret.line != prev_line)) { + if (selecting_enabled && mb->is_shift_pressed() && (caret.column != prev_col || caret.line != prev_line)) { if (!selection.active) { selection.active = true; selection.selecting_mode = SelectionMode::SELECTION_MODE_POINTER; @@ -1708,7 +1749,6 @@ void TextEdit::gui_input(const Ref<InputEvent> &p_gui_input) { last_dblclk = OS::get_singleton()->get_ticks_msec(); last_dblclk_pos = mb->get_position(); } - update(); } @@ -1716,7 +1756,7 @@ void TextEdit::gui_input(const Ref<InputEvent> &p_gui_input) { paste_primary_clipboard(); } - if (mb->get_button_index() == MouseButton::RIGHT && context_menu_enabled) { + if (mb->get_button_index() == MouseButton::RIGHT && (context_menu_enabled || is_move_caret_on_right_click_enabled())) { _reset_caret_blink_timer(); Point2i pos = get_line_column_at_pos(mpos); @@ -1741,11 +1781,13 @@ void TextEdit::gui_input(const Ref<InputEvent> &p_gui_input) { } } - _generate_context_menu(); - menu->set_position(get_screen_position() + mpos); - menu->reset_size(); - menu->popup(); - grab_focus(); + if (context_menu_enabled) { + _generate_context_menu(); + menu->set_position(get_screen_position() + mpos); + menu->reset_size(); + menu->popup(); + grab_focus(); + } } } else { if (mb->get_button_index() == MouseButton::LEFT) { @@ -1864,6 +1906,10 @@ void TextEdit::gui_input(const Ref<InputEvent> &p_gui_input) { Ref<InputEventKey> k = p_gui_input; if (k.is_valid()) { + if (alt_input(p_gui_input)) { + accept_event(); + return; + } if (!k->is_pressed()) { return; } @@ -1944,44 +1990,45 @@ void TextEdit::gui_input(const Ref<InputEvent> &p_gui_input) { return; } - // SELECT ALL, SELECT WORD UNDER CARET, CUT, COPY, PASTE. - - if (k->is_action("ui_text_select_all", true)) { - select_all(); - accept_event(); - return; - } - if (k->is_action("ui_text_select_word_under_caret", true)) { - select_word_under_caret(); - accept_event(); - return; - } - if (k->is_action("ui_cut", true)) { - cut(); - accept_event(); - return; - } - if (k->is_action("ui_copy", true)) { - copy(); - accept_event(); - return; - } - if (k->is_action("ui_paste", true)) { - paste(); - accept_event(); - return; - } + if (is_shortcut_keys_enabled()) { + // SELECT ALL, SELECT WORD UNDER CARET, CUT, COPY, PASTE. + if (k->is_action("ui_text_select_all", true)) { + select_all(); + accept_event(); + return; + } + if (k->is_action("ui_text_select_word_under_caret", true)) { + select_word_under_caret(); + accept_event(); + return; + } + if (k->is_action("ui_cut", true)) { + cut(); + accept_event(); + return; + } + if (k->is_action("ui_copy", true)) { + copy(); + accept_event(); + return; + } + if (k->is_action("ui_paste", true)) { + paste(); + accept_event(); + return; + } - // UNDO/REDO. - if (k->is_action("ui_undo", true)) { - undo(); - accept_event(); - return; - } - if (k->is_action("ui_redo", true)) { - redo(); - accept_event(); - return; + // UNDO/REDO. + if (k->is_action("ui_undo", true)) { + undo(); + accept_event(); + return; + } + if (k->is_action("ui_redo", true)) { + redo(); + accept_event(); + return; + } } // MISC. @@ -2313,15 +2360,7 @@ void TextEdit::_move_caret_to_line_start(bool p_select) { } if (caret.column == row_start_col || wi == 0) { // Compute whitespace symbols sequence length. - int current_line_whitespace_len = 0; - while (current_line_whitespace_len < text[caret.line].length()) { - char32_t c = text[caret.line][current_line_whitespace_len]; - if (c != '\t' && c != ' ') { - break; - } - current_line_whitespace_len++; - } - + int current_line_whitespace_len = get_first_non_whitespace_column(caret.line); if (get_caret_column() == current_line_whitespace_len) { set_caret_column(0); } else { @@ -2394,7 +2433,7 @@ void TextEdit::_move_caret_page_down(bool p_select) { } void TextEdit::_do_backspace(bool p_word, bool p_all_to_left) { - if (!editable || (caret.column == 0 && caret.line == 0)) { + if (!editable || (caret.column == 0 && caret.line == 0 && !has_selection())) { return; } @@ -2459,6 +2498,10 @@ void TextEdit::_delete(bool p_word, bool p_all_to_right) { int next_column; if (p_all_to_right) { + if (caret.column == curline_len) { + return; + } + // Delete everything to right of caret next_column = curline_len; next_line = caret.line; @@ -2858,7 +2901,7 @@ String TextEdit::get_language() const { return language; } -void TextEdit::set_structured_text_bidi_override(Control::StructuredTextParser p_parser) { +void TextEdit::set_structured_text_bidi_override(TextServer::StructuredTextParser p_parser) { if (st_parser != p_parser) { st_parser = p_parser; for (int i = 0; i < text.size(); i++) { @@ -2868,7 +2911,7 @@ void TextEdit::set_structured_text_bidi_override(Control::StructuredTextParser p } } -Control::StructuredTextParser TextEdit::get_structured_text_bidi_override() const { +TextServer::StructuredTextParser TextEdit::get_structured_text_bidi_override() const { return st_parser; } @@ -3121,6 +3164,7 @@ void TextEdit::insert_line_at(int p_at, const String &p_text) { ++selection.to_line; } } + update(); } void TextEdit::insert_text_at_caret(const String &p_text) { @@ -3816,7 +3860,7 @@ Point2i TextEdit::get_line_column_at_pos(const Point2i &p_pos, bool p_allow_out_ Point2i TextEdit::get_pos_at_line_column(int p_line, int p_column) const { Rect2i rect = get_rect_at_line_column(p_line, p_column); - return rect.position + Vector2i(0, get_line_height()); + return rect.position.x == -1 ? rect.position : rect.position + Vector2i(0, get_line_height()); } Rect2i TextEdit::get_rect_at_line_column(int p_line, int p_column) const { @@ -4054,12 +4098,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); @@ -4188,13 +4232,18 @@ void TextEdit::select_word_under_caret() { int end = 0; const PackedInt32Array words = TS->shaped_text_get_word_breaks(text.get_line_data(caret.line)->get_rid()); for (int i = 0; i < words.size(); i = i + 2) { - if ((words[i] < caret.column && words[i + 1] > caret.column) || (i == words.size() - 2 && caret.column == words[i + 1])) { + if ((words[i] <= caret.column && words[i + 1] >= caret.column) || (i == words.size() - 2 && caret.column == words[i + 1])) { begin = words[i]; end = words[i + 1]; break; } } + // No word found. + if (begin == 0 && end == 0) { + return; + } + select(caret.line, begin, caret.line, end); /* Move the caret to the end of the word for easier editing. */ set_caret_column(end, false); @@ -4270,10 +4319,12 @@ String TextEdit::get_selected_text() const { } int TextEdit::get_selection_line() const { + ERR_FAIL_COND_V(!selection.active, -1); return selection.selecting_line; } int TextEdit::get_selection_column() const { + ERR_FAIL_COND_V(!selection.active, -1); return selection.selecting_column; } @@ -4475,9 +4526,13 @@ void TextEdit::set_line_as_center_visible(int p_line, int p_wrap_index) { ERR_FAIL_COND(p_wrap_index > get_line_wrap_count(p_line)); int visible_rows = get_visible_line_count(); - Point2i next_line = get_next_visible_line_index_offset_from(p_line, p_wrap_index, -visible_rows / 2); + Point2i next_line = get_next_visible_line_index_offset_from(p_line, p_wrap_index, (-visible_rows / 2) - 1); int first_line = p_line - next_line.x + 1; + if (first_line < 0) { + set_v_scroll(0); + return; + } set_v_scroll(get_scroll_pos_for_line(first_line, next_line.y)); } @@ -4489,6 +4544,12 @@ void TextEdit::set_line_as_last_visible(int p_line, int p_wrap_index) { Point2i next_line = get_next_visible_line_index_offset_from(p_line, p_wrap_index, -get_visible_line_count() - 1); int first_line = p_line - next_line.x + 1; + // Adding _get_visible_lines_offset is not 100% correct as we end up showing almost p_line + 1, however, it provides a + // better user experience. Therefore we need to special case < visible line count, else showing line 0 is impossible. + if (get_visible_line_count_in_range(0, p_line) < get_visible_line_count() + 1) { + set_v_scroll(0); + return; + } set_v_scroll(get_scroll_pos_for_line(first_line, next_line.y) + _get_visible_lines_offset()); } @@ -5898,7 +5959,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; } @@ -5982,8 +6043,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; @@ -6039,6 +6100,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; @@ -6049,6 +6114,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(); diff --git a/scene/gui/text_edit.h b/scene/gui/text_edit.h index b365e9c61c..993203bee6 100644 --- a/scene/gui/text_edit.h +++ b/scene/gui/text_edit.h @@ -247,6 +247,9 @@ private: bool setting_text = false; + bool alt_start = false; + uint32_t alt_code = 0; + // Text properties. String ime_text = ""; Point2 ime_selection; @@ -271,7 +274,7 @@ private: Dictionary opentype_features; String language = ""; - Control::StructuredTextParser st_parser = STRUCTURED_TEXT_DEFAULT; + TextServer::StructuredTextParser st_parser = TextServer::STRUCTURED_TEXT_DEFAULT; Array st_args; void _clear(); @@ -353,7 +356,7 @@ private: Vector<int> last_visible_chars; }; - Map<int, LineDrawingCache> line_drawing_cache; + HashMap<int, LineDrawingCache> line_drawing_cache; int _get_char_pos_for_line(int p_px, int p_line, int p_wrap_index = 0) const; @@ -508,7 +511,6 @@ private: /* Syntax highlighting. */ Ref<SyntaxHighlighter> syntax_highlighter; - Map<int, Dictionary> syntax_highlighting_cache; Dictionary _get_line_syntax_highlighting(int p_line); @@ -625,6 +627,7 @@ public: /* General overrides. */ virtual void unhandled_key_input(const Ref<InputEvent> &p_event) override; virtual void gui_input(const Ref<InputEvent> &p_gui_input) override; + bool alt_input(const Ref<InputEvent> &p_gui_input); virtual Size2 get_minimum_size() const override; virtual bool is_text_field() const override; virtual CursorShape get_cursor_shape(const Point2 &p_pos = Point2i()) const override; @@ -651,8 +654,8 @@ public: void set_language(const String &p_language); String get_language() const; - void set_structured_text_bidi_override(Control::StructuredTextParser p_parser); - Control::StructuredTextParser get_structured_text_bidi_override() const; + void set_structured_text_bidi_override(TextServer::StructuredTextParser p_parser); + TextServer::StructuredTextParser get_structured_text_bidi_override() const; void set_structured_text_bidi_override_options(Array p_args); Array get_structured_text_bidi_override_options() const; diff --git a/scene/gui/tree.cpp b/scene/gui/tree.cpp index ccd24ed2cf..0ca9a66e08 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; } @@ -1411,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")); @@ -1753,19 +1754,16 @@ int Tree::draw_item(const Point2i &p_pos, const Point2 &p_draw_ofs, const Size2 for (int j = p_item->cells[i].buttons.size() - 1; j >= 0; j--) { Ref<Texture2D> b = p_item->cells[i].buttons[j].texture; Size2 s = b->get_size() + cache.button_pressed->get_minimum_size(); - if (s.height < label_h) { - s.height = label_h; - } Point2i o = Point2i(ofs + w - s.width, p_pos.y) - cache.offset + p_draw_ofs; if (cache.click_type == Cache::CLICK_BUTTON && cache.click_item == p_item && cache.click_column == i && cache.click_index == j && !p_item->cells[i].buttons[j].disabled) { - //being pressed + // Being pressed. Point2 od = o; if (rtl) { od.x = get_size().width - od.x - s.x; } - cache.button_pressed->draw(get_canvas_item(), Rect2(od, s)); + cache.button_pressed->draw(get_canvas_item(), Rect2(od.x, od.y, s.width, MAX(s.height, label_h))); } o.y += (label_h - s.height) / 2; @@ -2088,7 +2086,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; @@ -2096,82 +2093,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; @@ -2670,8 +2682,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; @@ -3206,10 +3218,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; @@ -3994,6 +4012,7 @@ void Tree::clear() { selected_item = nullptr; edited_item = nullptr; popup_edited_item = nullptr; + popup_pressing_edited_item = nullptr; update(); }; @@ -4309,12 +4328,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); @@ -4332,6 +4355,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; @@ -4870,7 +4906,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 74ad4f94b8..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; @@ -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; @@ -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(); |