diff options
Diffstat (limited to 'scene/gui')
54 files changed, 1995 insertions, 905 deletions
diff --git a/scene/gui/base_button.cpp b/scene/gui/base_button.cpp index af6a99ca62..ac9034c6fd 100644 --- a/scene/gui/base_button.cpp +++ b/scene/gui/base_button.cpp @@ -60,7 +60,7 @@ void BaseButton::gui_input(const Ref<InputEvent> &p_event) { } Ref<InputEventMouseButton> mouse_button = p_event; - bool ui_accept = p_event->is_action("ui_accept") && !p_event->is_echo(); + bool ui_accept = p_event->is_action("ui_accept", true) && !p_event->is_echo(); bool button_masked = mouse_button.is_valid() && (mouse_button_to_mask(mouse_button->get_button_index()) & button_mask) != MouseButton::NONE; if (button_masked || ui_accept) { @@ -127,6 +127,7 @@ void BaseButton::_notification(int p_what) { status.hovering = false; status.press_attempt = false; status.pressing_inside = false; + status.shortcut_press = false; } break; } } @@ -160,6 +161,7 @@ void BaseButton::on_action_event(Ref<InputEvent> p_event) { if (action_mode == ACTION_MODE_BUTTON_PRESS) { status.press_attempt = false; status.pressing_inside = false; + status.shortcut_press = false; } status.pressed = !status.pressed; _unpress_group(); @@ -185,6 +187,7 @@ void BaseButton::on_action_event(Ref<InputEvent> p_event) { } status.press_attempt = false; status.pressing_inside = false; + status.shortcut_press = false; emit_signal(SNAME("button_up")); } @@ -209,6 +212,7 @@ void BaseButton::set_disabled(bool p_disabled) { } status.press_attempt = false; status.pressing_inside = false; + status.shortcut_press = false; } queue_redraw(); } @@ -218,13 +222,12 @@ bool BaseButton::is_disabled() const { } void BaseButton::set_pressed(bool p_pressed) { - if (!toggle_mode) { - return; - } - if (status.pressed == p_pressed) { + bool prev_pressed = status.pressed; + set_pressed_no_signal(p_pressed); + + if (status.pressed == prev_pressed) { return; } - status.pressed = p_pressed; if (p_pressed) { _unpress_group(); @@ -233,8 +236,6 @@ void BaseButton::set_pressed(bool p_pressed) { } } _toggled(status.pressed); - - queue_redraw(); } void BaseButton::set_pressed_no_signal(bool p_pressed) { @@ -284,7 +285,7 @@ BaseButton::DrawMode BaseButton::get_draw_mode() const { pressing = status.pressed; } - if (pressing) { + if ((shortcut_feedback || !status.shortcut_press) && pressing) { return DRAW_PRESSED; } else { return DRAW_NORMAL; @@ -349,11 +350,8 @@ Ref<Shortcut> BaseButton::get_shortcut() const { void BaseButton::shortcut_input(const Ref<InputEvent> &p_event) { ERR_FAIL_COND(p_event.is_null()); - if (!_is_focus_owner_in_shortcut_context()) { - return; - } - if (!is_disabled() && is_visible_in_tree() && !p_event->is_echo() && shortcut.is_valid() && shortcut->matches_event(p_event)) { + status.shortcut_press = true; on_action_event(p_event); accept_event(); } @@ -389,36 +387,16 @@ Ref<ButtonGroup> BaseButton::get_button_group() const { return button_group; } -void BaseButton::set_shortcut_context(Node *p_node) { - if (p_node != nullptr) { - shortcut_context = p_node->get_instance_id(); - } else { - shortcut_context = ObjectID(); - } +bool BaseButton::_was_pressed_by_mouse() const { + return was_mouse_pressed; } -Node *BaseButton::get_shortcut_context() const { - Object *ctx_obj = ObjectDB::get_instance(shortcut_context); - Node *ctx_node = Object::cast_to<Node>(ctx_obj); - - return ctx_node; +void BaseButton::set_shortcut_feedback(bool p_feedback) { + shortcut_feedback = p_feedback; } -bool BaseButton::_is_focus_owner_in_shortcut_context() const { - if (shortcut_context == ObjectID()) { - // No context, therefore global - always "in" context. - return true; - } - - Node *ctx_node = get_shortcut_context(); - Control *vp_focus = get_viewport() ? get_viewport()->gui_get_focus_owner() : nullptr; - - // If the context is valid and the viewport focus is valid, check if the context is the focus or is a parent of it. - return ctx_node && vp_focus && (ctx_node == vp_focus || ctx_node->is_ancestor_of(vp_focus)); -} - -bool BaseButton::_was_pressed_by_mouse() const { - return was_mouse_pressed; +bool BaseButton::is_shortcut_feedback() const { + return shortcut_feedback; } void BaseButton::_bind_methods() { @@ -446,8 +424,8 @@ void BaseButton::_bind_methods() { ClassDB::bind_method(D_METHOD("set_button_group", "button_group"), &BaseButton::set_button_group); ClassDB::bind_method(D_METHOD("get_button_group"), &BaseButton::get_button_group); - ClassDB::bind_method(D_METHOD("set_shortcut_context", "node"), &BaseButton::set_shortcut_context); - ClassDB::bind_method(D_METHOD("get_shortcut_context"), &BaseButton::get_shortcut_context); + ClassDB::bind_method(D_METHOD("set_shortcut_feedback", "enabled"), &BaseButton::set_shortcut_feedback); + ClassDB::bind_method(D_METHOD("is_shortcut_feedback"), &BaseButton::is_shortcut_feedback); GDVIRTUAL_BIND(_pressed); GDVIRTUAL_BIND(_toggled, "button_pressed"); @@ -465,8 +443,8 @@ void BaseButton::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::INT, "button_mask", PROPERTY_HINT_FLAGS, "Mouse Left, Mouse Right, Mouse Middle"), "set_button_mask", "get_button_mask"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "keep_pressed_outside"), "set_keep_pressed_outside", "is_keep_pressed_outside"); ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "shortcut", PROPERTY_HINT_RESOURCE_TYPE, "Shortcut"), "set_shortcut", "get_shortcut"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "shortcut_feedback"), "set_shortcut_feedback", "is_shortcut_feedback"); ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "button_group", PROPERTY_HINT_RESOURCE_TYPE, "ButtonGroup"), "set_button_group", "get_button_group"); - ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "shortcut_context", PROPERTY_HINT_NODE_TYPE, "Node"), "set_shortcut_context", "get_shortcut_context"); BIND_ENUM_CONSTANT(DRAW_NORMAL); BIND_ENUM_CONSTANT(DRAW_PRESSED); diff --git a/scene/gui/base_button.h b/scene/gui/base_button.h index c83b08aadf..3acf535f54 100644 --- a/scene/gui/base_button.h +++ b/scene/gui/base_button.h @@ -53,6 +53,7 @@ private: bool keep_pressed_outside = false; Ref<Shortcut> shortcut; ObjectID shortcut_context; + bool shortcut_feedback = true; ActionMode action_mode = ACTION_MODE_BUTTON_RELEASE; struct Status { @@ -60,6 +61,7 @@ private: bool hovering = false; bool press_attempt = false; bool pressing_inside = false; + bool shortcut_press = false; bool disabled = false; @@ -81,7 +83,6 @@ protected: virtual void shortcut_input(const Ref<InputEvent> &p_event) override; void _notification(int p_what); - bool _is_focus_owner_in_shortcut_context() const; bool _was_pressed_by_mouse() const; GDVIRTUAL0(_pressed) @@ -132,8 +133,8 @@ public: void set_button_group(const Ref<ButtonGroup> &p_group); Ref<ButtonGroup> get_button_group() const; - void set_shortcut_context(Node *p_node); - Node *get_shortcut_context() const; + void set_shortcut_feedback(bool p_feedback); + bool is_shortcut_feedback() const; BaseButton(); ~BaseButton(); diff --git a/scene/gui/button.cpp b/scene/gui/button.cpp index c2b82e01d1..0e7bc5c306 100644 --- a/scene/gui/button.cpp +++ b/scene/gui/button.cpp @@ -231,7 +231,7 @@ void Button::_notification(int p_what) { _icon = icon; } - Rect2 icon_region = Rect2(); + Rect2 icon_region; HorizontalAlignment icon_align_rtl_checked = icon_alignment; HorizontalAlignment align_rtl_checked = alignment; // Swap icon and text alignment sides if right-to-left layout is set. diff --git a/scene/gui/code_edit.cpp b/scene/gui/code_edit.cpp index 94d296977b..9e0dc049e5 100644 --- a/scene/gui/code_edit.cpp +++ b/scene/gui/code_edit.cpp @@ -138,7 +138,7 @@ void CodeEdit::_notification(int p_what) { code_completion_scroll_rect.position = code_completion_rect.position + Vector2(code_completion_rect.size.width, 0); code_completion_scroll_rect.size = Vector2(scroll_width, code_completion_rect.size.height); - code_completion_line_ofs = CLAMP(code_completion_current_selected - lines / 2, 0, code_completion_options_count - lines); + code_completion_line_ofs = CLAMP((code_completion_force_item_center < 0 ? code_completion_current_selected : code_completion_force_item_center) - lines / 2, 0, code_completion_options_count - lines); RenderingServer::get_singleton()->canvas_item_add_rect(ci, Rect2(Point2(code_completion_rect.position.x, code_completion_rect.position.y + (code_completion_current_selected - code_completion_line_ofs) * row_height), Size2(code_completion_rect.size.width, row_height)), code_completion_selected_color); for (int i = 0; i < lines; i++) { @@ -201,7 +201,7 @@ void CodeEdit::_notification(int p_what) { if (caret_visible && !code_hint.is_empty() && (!code_completion_active || (code_completion_below != code_hint_draw_below))) { const int font_height = font->get_height(font_size); Ref<StyleBox> sb = get_theme_stylebox(SNAME("panel"), SNAME("TooltipPanel")); - Color font_color = get_theme_color(SNAME("font_color"), SNAME("TooltipLabel")); + Color color = get_theme_color(SNAME("font_color"), SNAME("TooltipLabel")); Vector<String> code_hint_lines = code_hint.split("\n"); int line_count = code_hint_lines.size(); @@ -238,17 +238,17 @@ void CodeEdit::_notification(int p_what) { Point2 round_ofs = hint_ofs + sb->get_offset() + Vector2(0, font->get_ascent(font_size) + font_height * i + yofs); round_ofs = round_ofs.round(); - draw_string(font, round_ofs, line.replace(String::chr(0xFFFF), ""), HORIZONTAL_ALIGNMENT_LEFT, -1, font_size, font_color); + draw_string(font, round_ofs, line.replace(String::chr(0xFFFF), ""), HORIZONTAL_ALIGNMENT_LEFT, -1, font_size, color); if (end > 0) { // Draw an underline for the currently edited function parameter. const Vector2 b = hint_ofs + sb->get_offset() + Vector2(begin, font_height + font_height * i + yofs); - draw_line(b, b + Vector2(end - begin, 0), font_color, 2); + draw_line(b, b + Vector2(end - begin, 0), color, 2); // Draw a translucent text highlight as well. const Rect2 highlight_rect = Rect2( b - Vector2(0, font_height), Vector2(end - begin, font_height)); - draw_rect(highlight_rect, font_color * Color(1, 1, 1, 0.2)); + draw_rect(highlight_rect, color * Color(1, 1, 1, 0.2)); } yofs += line_spacing; } @@ -281,16 +281,22 @@ void CodeEdit::gui_input(const Ref<InputEvent> &p_gui_input) { case MouseButton::WHEEL_UP: { if (code_completion_current_selected > 0) { code_completion_current_selected--; + code_completion_force_item_center = -1; queue_redraw(); } } break; case MouseButton::WHEEL_DOWN: { if (code_completion_current_selected < code_completion_options.size() - 1) { code_completion_current_selected++; + code_completion_force_item_center = -1; queue_redraw(); } } break; case MouseButton::LEFT: { + if (code_completion_force_item_center == -1) { + code_completion_force_item_center = code_completion_current_selected; + } + code_completion_current_selected = CLAMP(code_completion_line_ofs + (mb->get_position().y - code_completion_rect.position.y) / get_line_height(), 0, code_completion_options.size() - 1); if (mb->is_double_click()) { confirm_code_completion(); @@ -300,6 +306,7 @@ void CodeEdit::gui_input(const Ref<InputEvent> &p_gui_input) { default: break; } + return; } else if (code_completion_active && code_completion_scroll_rect.has_point(mb->get_position())) { if (mb->get_button_index() != MouseButton::LEFT) { @@ -448,6 +455,7 @@ void CodeEdit::gui_input(const Ref<InputEvent> &p_gui_input) { } else { code_completion_current_selected = code_completion_options.size() - 1; } + code_completion_force_item_center = -1; queue_redraw(); accept_event(); return; @@ -458,30 +466,35 @@ void CodeEdit::gui_input(const Ref<InputEvent> &p_gui_input) { } else { code_completion_current_selected = 0; } + code_completion_force_item_center = -1; queue_redraw(); accept_event(); return; } if (k->is_action("ui_page_up", true)) { code_completion_current_selected = MAX(0, code_completion_current_selected - code_completion_max_lines); + code_completion_force_item_center = -1; queue_redraw(); accept_event(); return; } if (k->is_action("ui_page_down", true)) { code_completion_current_selected = MIN(code_completion_options.size() - 1, code_completion_current_selected + code_completion_max_lines); + code_completion_force_item_center = -1; queue_redraw(); accept_event(); return; } if (k->is_action("ui_home", true)) { code_completion_current_selected = 0; + code_completion_force_item_center = -1; queue_redraw(); accept_event(); return; } if (k->is_action("ui_end", true)) { code_completion_current_selected = code_completion_options.size() - 1; + code_completion_force_item_center = -1; queue_redraw(); accept_event(); return; @@ -681,8 +694,8 @@ void CodeEdit::_backspace_internal(int p_caret) { return; } - if (has_selection()) { - delete_selection(); + if (has_selection(p_caret)) { + delete_selection(p_caret); return; } @@ -1248,37 +1261,41 @@ bool CodeEdit::is_drawing_executing_lines_gutter() const { } void CodeEdit::_main_gutter_draw_callback(int p_line, int p_gutter, const Rect2 &p_region) { - bool shift_pressed = Input::get_singleton()->is_key_pressed(Key::SHIFT); - if (draw_breakpoints && breakpoint_icon.is_valid()) { - bool hovering = p_region.has_point(get_local_mouse_pos()); bool breakpointed = is_line_breakpointed(p_line); + bool hovering = p_region.has_point(get_local_mouse_pos()); + bool shift_pressed = Input::get_singleton()->is_key_pressed(Key::SHIFT); if (breakpointed || (hovering && !is_dragging_cursor() && !shift_pressed)) { int padding = p_region.size.x / 6; + + Color use_color = breakpoint_color; + if (hovering && !shift_pressed) { + use_color = breakpointed ? use_color.lightened(0.3) : use_color.darkened(0.5); + } Rect2 icon_region = p_region; icon_region.position += Point2(padding, padding); icon_region.size -= Point2(padding, padding) * 2; - - // Darken icon when hovering, shift not pressed & not yet breakpointed. - Color use_color = hovering && !breakpointed && !shift_pressed ? breakpoint_color.darkened(0.4) : breakpoint_color; breakpoint_icon->draw_rect(get_canvas_item(), icon_region, false, use_color); } } if (draw_bookmarks && bookmark_icon.is_valid()) { - bool hovering = p_region.has_point(get_local_mouse_pos()); bool bookmarked = is_line_bookmarked(p_line); + bool hovering = p_region.has_point(get_local_mouse_pos()); + bool shift_pressed = Input::get_singleton()->is_key_pressed(Key::SHIFT); if (bookmarked || (hovering && !is_dragging_cursor() && shift_pressed)) { int horizontal_padding = p_region.size.x / 2; int vertical_padding = p_region.size.y / 4; + + Color use_color = bookmark_color; + if (hovering && shift_pressed) { + use_color = bookmarked ? use_color.lightened(0.3) : use_color.darkened(0.5); + } Rect2 icon_region = p_region; icon_region.position += Point2(horizontal_padding, 0); icon_region.size -= Point2(horizontal_padding * 1.1, vertical_padding); - - // Darken icon when hovering, shift pressed & not yet bookmarked. - Color use_color = hovering && !bookmarked && shift_pressed ? bookmark_color.darkened(0.4) : bookmark_color; bookmark_icon->draw_rect(get_canvas_item(), icon_region, false, use_color); } } @@ -1287,10 +1304,10 @@ void CodeEdit::_main_gutter_draw_callback(int p_line, int p_gutter, const Rect2 int horizontal_padding = p_region.size.x / 10; int vertical_padding = p_region.size.y / 4; - Rect2 executing_line_region = p_region; - executing_line_region.position += Point2(horizontal_padding, vertical_padding); - executing_line_region.size -= Point2(horizontal_padding, vertical_padding) * 2; - executing_line_icon->draw_rect(get_canvas_item(), executing_line_region, false, executing_line_color); + Rect2 icon_region = p_region; + icon_region.position += Point2(horizontal_padding, vertical_padding); + icon_region.size -= Point2(horizontal_padding, vertical_padding) * 2; + executing_line_icon->draw_rect(get_canvas_item(), icon_region, false, executing_line_color); } } @@ -1408,7 +1425,10 @@ bool CodeEdit::is_line_numbers_zero_padded() const { } void CodeEdit::_line_number_draw_callback(int p_line, int p_gutter, const Rect2 &p_region) { - String fc = TS->format_number(String::num(p_line + 1).lpad(line_number_digits, line_number_padding)); + String fc = String::num(p_line + 1).lpad(line_number_digits, line_number_padding); + if (is_localizing_numeral_system()) { + fc = TS->format_number(fc); + } Ref<TextLine> tl; tl.instantiate(); tl->add_string(fc, font, font_size); @@ -1974,6 +1994,7 @@ void CodeEdit::set_code_completion_selected_index(int p_index) { } ERR_FAIL_INDEX(p_index, code_completion_options.size()); code_completion_current_selected = p_index; + code_completion_force_item_center = -1; queue_redraw(); } @@ -2135,15 +2156,15 @@ String CodeEdit::get_text_for_symbol_lookup() { StringBuilder lookup_text; const int text_size = get_line_count(); for (int i = 0; i < text_size; i++) { - String text = get_line(i); + String line_text = get_line(i); if (i == line) { - lookup_text += text.substr(0, col); + lookup_text += line_text.substr(0, col); /* Not unicode, represents the cursor. */ lookup_text += String::chr(0xFFFF); - lookup_text += text.substr(col, text.size()); + lookup_text += line_text.substr(col, line_text.size()); } else { - lookup_text += text; + lookup_text += line_text; } if (i != text_size - 1) { @@ -2804,6 +2825,7 @@ void CodeEdit::_update_scroll_selected_line(float p_mouse_y) { percent = CLAMP(percent, 0.0f, 1.0f); code_completion_current_selected = (int)(percent * (code_completion_options.size() - 1)); + code_completion_force_item_center = -1; } void CodeEdit::_filter_code_completion_candidates_impl() { @@ -2855,12 +2877,15 @@ void CodeEdit::_filter_code_completion_candidates_impl() { offset = line_height; } - max_width = MAX(max_width, font->get_string_size(option.display, HORIZONTAL_ALIGNMENT_LEFT, -1, font_size).width + offset); + if (font.is_valid()) { + max_width = MAX(max_width, font->get_string_size(option.display, HORIZONTAL_ALIGNMENT_LEFT, -1, font_size).width + offset); + } code_completion_options.push_back(option); } code_completion_longest_line = MIN(max_width, code_completion_max_width * font_size); code_completion_current_selected = 0; + code_completion_force_item_center = -1; code_completion_active = true; queue_redraw(); return; @@ -2966,7 +2991,9 @@ void CodeEdit::_filter_code_completion_candidates_impl() { if (string_to_complete.length() == 0) { code_completion_options.push_back(option); - max_width = MAX(max_width, font->get_string_size(option.display, HORIZONTAL_ALIGNMENT_LEFT, -1, font_size).width + offset); + if (font.is_valid()) { + max_width = MAX(max_width, font->get_string_size(option.display, HORIZONTAL_ALIGNMENT_LEFT, -1, font_size).width + offset); + } continue; } @@ -3072,7 +3099,9 @@ void CodeEdit::_filter_code_completion_candidates_impl() { option.matches.append_array(ssq_matches); completion_options_subseq.push_back(option); } - max_width = MAX(max_width, font->get_string_size(option.display, HORIZONTAL_ALIGNMENT_LEFT, -1, font_size).width + offset); + if (font.is_valid()) { + max_width = MAX(max_width, font->get_string_size(option.display, HORIZONTAL_ALIGNMENT_LEFT, -1, font_size).width + offset); + } } else if (!*ssq_lower) { // Matched the whole subsequence in s_lower. option.matches.clear(); @@ -3089,7 +3118,9 @@ void CodeEdit::_filter_code_completion_candidates_impl() { option.matches.append_array(ssq_lower_matches); completion_options_subseq_casei.push_back(option); } - max_width = MAX(max_width, font->get_string_size(option.display, HORIZONTAL_ALIGNMENT_LEFT, -1, font_size).width + offset); + if (font.is_valid()) { + max_width = MAX(max_width, font->get_string_size(option.display, HORIZONTAL_ALIGNMENT_LEFT, -1, font_size).width + offset); + } } } @@ -3111,6 +3142,7 @@ void CodeEdit::_filter_code_completion_candidates_impl() { code_completion_longest_line = MIN(max_width, code_completion_max_width * font_size); code_completion_current_selected = 0; + code_completion_force_item_center = -1; code_completion_active = true; queue_redraw(); } diff --git a/scene/gui/code_edit.h b/scene/gui/code_edit.h index 09c7ef80bf..cbbc13480e 100644 --- a/scene/gui/code_edit.h +++ b/scene/gui/code_edit.h @@ -214,6 +214,7 @@ private: Vector<ScriptLanguage::CodeCompletionOption> code_completion_options; int code_completion_line_ofs = 0; int code_completion_current_selected = 0; + int code_completion_force_item_center = -1; int code_completion_longest_line = 0; Rect2i code_completion_rect; Rect2i code_completion_scroll_rect; diff --git a/scene/gui/color_mode.cpp b/scene/gui/color_mode.cpp index ebd86e0937..308fe057c5 100644 --- a/scene/gui/color_mode.cpp +++ b/scene/gui/color_mode.cpp @@ -73,10 +73,10 @@ void ColorModeRGB::slider_draw(int p_which) { Color left_color; Color right_color; Color color = color_picker->get_pick_color(); - const real_t margin = 4 * color_picker->get_theme_default_base_scale(); + const real_t margin = 16 * color_picker->get_theme_default_base_scale(); if (p_which == ColorPicker::SLIDER_COUNT) { - slider->draw_texture_rect(color_picker->get_theme_icon(SNAME("sample_bg"), SNAME("ColorPicker")), Rect2(Point2(0, margin), Size2(size.x, margin)), true); + slider->draw_texture_rect(color_picker->get_theme_icon(SNAME("sample_bg"), SNAME("ColorPicker")), Rect2(Point2(0, 0), Size2(size.x, margin)), true); left_color = color; left_color.a = 0; @@ -97,10 +97,10 @@ void ColorModeRGB::slider_draw(int p_which) { col.set(1, right_color); col.set(2, right_color); col.set(3, left_color); - pos.set(0, Vector2(0, margin)); - pos.set(1, Vector2(size.x, margin)); - pos.set(2, Vector2(size.x, margin * 2)); - pos.set(3, Vector2(0, margin * 2)); + pos.set(0, Vector2(0, 0)); + pos.set(1, Vector2(size.x, 0)); + pos.set(2, Vector2(size.x, margin)); + pos.set(3, Vector2(0, margin)); slider->draw_polygon(pos, col); } @@ -147,10 +147,10 @@ void ColorModeHSV::slider_draw(int p_which) { Color left_color; Color right_color; Color color = color_picker->get_pick_color(); - const real_t margin = 4 * color_picker->get_theme_default_base_scale(); + const real_t margin = 16 * color_picker->get_theme_default_base_scale(); if (p_which == ColorPicker::SLIDER_COUNT) { - slider->draw_texture_rect(color_picker->get_theme_icon(SNAME("sample_bg"), SNAME("ColorPicker")), Rect2(Point2(0, margin), Size2(size.x, margin)), true); + slider->draw_texture_rect(color_picker->get_theme_icon(SNAME("sample_bg"), SNAME("ColorPicker")), Rect2(Point2(0, 0), Size2(size.x, margin)), true); left_color = color; left_color.a = 0; @@ -158,8 +158,7 @@ void ColorModeHSV::slider_draw(int p_which) { right_color.a = 1; } else if (p_which == 0) { Ref<Texture2D> hue = color_picker->get_theme_icon(SNAME("color_hue"), SNAME("ColorPicker")); - slider->draw_set_transform(Point2(), -Math_PI / 2, Size2(1.0, 1.0)); - slider->draw_texture_rect(hue, Rect2(Vector2(margin * -2, 0), Vector2(margin, size.x)), false); + slider->draw_texture_rect(hue, Rect2(Vector2(), Vector2(size.x, margin)), false); return; } else { Color s_col; @@ -174,10 +173,10 @@ void ColorModeHSV::slider_draw(int p_which) { col.set(1, right_color); col.set(2, right_color); col.set(3, left_color); - pos.set(0, Vector2(0, margin)); - pos.set(1, Vector2(size.x, margin)); - pos.set(2, Vector2(size.x, margin * 2)); - pos.set(3, Vector2(0, margin * 2)); + pos.set(0, Vector2(0, 0)); + pos.set(1, Vector2(size.x, 0)); + pos.set(2, Vector2(size.x, margin)); + pos.set(3, Vector2(0, margin)); slider->draw_polygon(pos, col); } @@ -216,10 +215,10 @@ void ColorModeRAW::slider_draw(int p_which) { Color left_color; Color right_color; Color color = color_picker->get_pick_color(); - const real_t margin = 4 * color_picker->get_theme_default_base_scale(); + const real_t margin = 16 * color_picker->get_theme_default_base_scale(); if (p_which == ColorPicker::SLIDER_COUNT) { - slider->draw_texture_rect(color_picker->get_theme_icon(SNAME("sample_bg"), SNAME("ColorPicker")), Rect2(Point2(0, margin), Size2(size.x, margin)), true); + slider->draw_texture_rect(color_picker->get_theme_icon(SNAME("sample_bg"), SNAME("ColorPicker")), Rect2(Point2(0, 0), Size2(size.x, margin)), true); left_color = color; left_color.a = 0; @@ -230,10 +229,10 @@ void ColorModeRAW::slider_draw(int p_which) { col.set(1, right_color); col.set(2, right_color); col.set(3, left_color); - pos.set(0, Vector2(0, margin)); - pos.set(1, Vector2(size.x, margin)); - pos.set(2, Vector2(size.x, margin * 2)); - pos.set(3, Vector2(0, margin * 2)); + pos.set(0, Vector2(0, 0)); + pos.set(1, Vector2(size.x, 0)); + pos.set(2, Vector2(size.x, margin)); + pos.set(3, Vector2(0, margin)); slider->draw_polygon(pos, col); } @@ -245,8 +244,7 @@ bool ColorModeRAW::apply_theme() const { slider->remove_theme_icon_override("grabber"); slider->remove_theme_icon_override("grabber_highlight"); slider->remove_theme_style_override("slider"); - slider->remove_theme_style_override("grabber_area"); - slider->remove_theme_style_override("grabber_area_highlight"); + slider->remove_theme_constant_override("grabber_offset"); } return true; @@ -285,46 +283,67 @@ Color ColorModeOKHSL::get_color() const { } void ColorModeOKHSL::slider_draw(int p_which) { - Vector<Vector2> pos; - pos.resize(4); - Vector<Color> col; - col.resize(4); HSlider *slider = color_picker->get_slider(p_which); Size2 size = slider->get_size(); + const real_t margin = 16 * color_picker->get_theme_default_base_scale(); + + if (p_which == 0) { // H + Ref<Texture2D> hue = color_picker->get_theme_icon(SNAME("color_okhsl_hue"), SNAME("ColorPicker")); + slider->draw_texture_rect(hue, Rect2(Vector2(), Vector2(size.x, margin)), false); + return; + } + + Vector<Vector2> pos; + Vector<Color> col; Color left_color; Color right_color; Color color = color_picker->get_pick_color(); - const real_t margin = 4 * color_picker->get_theme_default_base_scale(); - if (p_which == ColorPicker::SLIDER_COUNT) { - slider->draw_texture_rect(color_picker->get_theme_icon(SNAME("sample_bg"), SNAME("ColorPicker")), Rect2(Point2(0, margin), Size2(size.x, margin)), true); + if (p_which == 2) { // L + pos.resize(6); + col.resize(6); + left_color = Color(0, 0, 0); + Color middle_color; + middle_color.set_ok_hsl(color.get_ok_hsl_h(), color.get_ok_hsl_s(), 0.5); + right_color.set_ok_hsl(color.get_ok_hsl_h(), color.get_ok_hsl_s(), 1); - left_color = color; - left_color.a = 0; - right_color = color; - right_color.a = 1; - } else if (p_which == 0) { - Ref<Texture2D> hue = color_picker->get_theme_icon(SNAME("color_hue"), SNAME("ColorPicker")); - slider->draw_set_transform(Point2(), -Math_PI / 2, Size2(1.0, 1.0)); - slider->draw_texture_rect(hue, Rect2(Vector2(margin * -2, 0), Vector2(margin, size.x)), false); - return; - } else { - Color s_col; - Color v_col; - s_col.set_ok_hsl(color.get_h(), 0, color.get_v()); - left_color = (p_which == 1) ? s_col : Color(0, 0, 0); - s_col.set_ok_hsl(color.get_h(), 1, color.get_v()); - v_col.set_ok_hsl(color.get_h(), color.get_s(), 1); - right_color = (p_which == 1) ? s_col : v_col; + col.set(0, left_color); + col.set(1, middle_color); + col.set(2, right_color); + col.set(3, right_color); + col.set(4, middle_color); + col.set(5, left_color); + pos.set(0, Vector2(0, 0)); + pos.set(1, Vector2(size.x * 0.5, 0)); + pos.set(2, Vector2(size.x, 0)); + pos.set(3, Vector2(size.x, margin)); + pos.set(4, Vector2(size.x * 0.5, margin)); + pos.set(5, Vector2(0, margin)); + } else { // A / S + pos.resize(4); + col.resize(4); + + if (p_which == ColorPicker::SLIDER_COUNT) { + slider->draw_texture_rect(color_picker->get_theme_icon(SNAME("sample_bg"), SNAME("ColorPicker")), Rect2(Point2(0, 0), Size2(size.x, margin)), true); + + left_color = color; + left_color.a = 0; + right_color = color; + right_color.a = 1; + } else { + left_color.set_ok_hsl(color.get_ok_hsl_h(), 0, color.get_ok_hsl_l()); + right_color.set_ok_hsl(color.get_ok_hsl_h(), 1, color.get_ok_hsl_l()); + } + + col.set(0, left_color); + col.set(1, right_color); + col.set(2, right_color); + col.set(3, left_color); + pos.set(0, Vector2(0, 0)); + pos.set(1, Vector2(size.x, 0)); + pos.set(2, Vector2(size.x, margin)); + pos.set(3, Vector2(0, margin)); } - col.set(0, left_color); - col.set(1, right_color); - col.set(2, right_color); - col.set(3, left_color); - pos.set(0, Vector2(0, margin)); - pos.set(1, Vector2(size.x, margin)); - pos.set(2, Vector2(size.x, margin * 2)); - pos.set(3, Vector2(0, margin * 2)); slider->draw_polygon(pos, col); } diff --git a/scene/gui/color_picker.cpp b/scene/gui/color_picker.cpp index 5751c54877..eb9f9039b7 100644 --- a/scene/gui/color_picker.cpp +++ b/scene/gui/color_picker.cpp @@ -44,6 +44,7 @@ #include "thirdparty/misc/ok_color_shader.h" List<Color> ColorPicker::preset_cache; +List<Color> ColorPicker::recent_preset_cache; void ColorPicker::_notification(int p_what) { switch (p_what) { @@ -61,14 +62,31 @@ void ColorPicker::_notification(int p_what) { for (int i = 0; i < preset_cache.size(); i++) { presets.push_back(preset_cache[i]); } + + if (recent_preset_cache.is_empty()) { + PackedColorArray saved_recent_presets = EditorSettings::get_singleton()->get_project_metadata("color_picker", "recent_presets", PackedColorArray()); + for (int i = 0; i < saved_recent_presets.size(); i++) { + recent_preset_cache.push_back(saved_recent_presets[i]); + } + } + + for (int i = 0; i < recent_preset_cache.size(); i++) { + recent_presets.push_back(recent_preset_cache[i]); + } } #endif [[fallthrough]]; } case NOTIFICATION_THEME_CHANGED: { btn_pick->set_icon(get_theme_icon(SNAME("screen_picker"), SNAME("ColorPicker"))); + _update_drop_down_arrow(btn_preset->is_pressed(), btn_preset); + _update_drop_down_arrow(btn_recent_preset->is_pressed(), btn_recent_preset); btn_add_preset->set_icon(get_theme_icon(SNAME("add_preset"))); + btn_pick->set_custom_minimum_size(Size2(28 * get_theme_default_base_scale(), 0)); + btn_shape->set_custom_minimum_size(Size2(28 * get_theme_default_base_scale(), 0)); + btn_mode->set_custom_minimum_size(Size2(28 * get_theme_default_base_scale(), 0)); + uv_edit->set_custom_minimum_size(Size2(get_theme_constant(SNAME("sv_width")), get_theme_constant(SNAME("sv_height")))); w_edit->set_custom_minimum_size(Size2(get_theme_constant(SNAME("h_width")), 0)); @@ -90,12 +108,13 @@ void ColorPicker::_notification(int p_what) { } _update_presets(); + _update_recent_presets(); _update_controls(); } break; case NOTIFICATION_VISIBILITY_CHANGED: { Popup *p = Object::cast_to<Popup>(get_parent()); - if (p) { + if (p && is_visible_in_tree()) { p->set_size(Size2(get_combined_minimum_size().width + get_theme_constant(SNAME("margin")) * 2, get_combined_minimum_size().height + get_theme_constant(SNAME("margin")) * 2)); } } break; @@ -234,18 +253,20 @@ void ColorPicker::_update_controls() { wheel_edit->hide(); w_edit->show(); uv_edit->show(); + btn_shape->show(); break; case SHAPE_HSV_WHEEL: wheel_edit->show(); w_edit->hide(); uv_edit->hide(); - + btn_shape->show(); wheel->set_material(wheel_mat); break; case SHAPE_VHS_CIRCLE: wheel_edit->show(); w_edit->show(); uv_edit->hide(); + btn_shape->show(); wheel->set_material(circle_mat); circle_mat->set_shader(circle_shader); break; @@ -253,15 +274,27 @@ void ColorPicker::_update_controls() { wheel_edit->show(); w_edit->show(); uv_edit->hide(); + btn_shape->show(); wheel->set_material(circle_mat); circle_mat->set_shader(circle_ok_color_shader); break; + case SHAPE_NONE: + wheel_edit->hide(); + w_edit->hide(); + uv_edit->hide(); + btn_shape->hide(); + break; default: { } } } void ColorPicker::_set_pick_color(const Color &p_color, bool p_update_sliders) { + if (text_changed) { + add_recent_preset(color); + text_changed = false; + } + color = p_color; if (color != last_color) { _copy_color_to_hsv(); @@ -330,55 +363,61 @@ void ColorPicker::_value_changed(double) { void ColorPicker::add_mode(ColorMode *p_mode) { modes.push_back(p_mode); - mode_option_button->add_item(RTR(p_mode->get_name())); } void ColorPicker::create_slider(GridContainer *gc, int idx) { - Label *l = memnew(Label()); - l->set_v_size_flags(SIZE_SHRINK_CENTER); - gc->add_child(l); + Label *lbl = memnew(Label()); + lbl->set_v_size_flags(SIZE_SHRINK_CENTER); + gc->add_child(lbl); + + HSlider *slider = memnew(HSlider); + slider->set_v_size_flags(SIZE_SHRINK_CENTER); + slider->set_focus_mode(FOCUS_NONE); + gc->add_child(slider); - HSlider *s = memnew(HSlider); - s->set_v_size_flags(SIZE_SHRINK_CENTER); - s->set_focus_mode(FOCUS_NONE); - gc->add_child(s); + SpinBox *val = memnew(SpinBox); + slider->share(val); + val->set_select_all_on_focus(true); + gc->add_child(val); - SpinBox *v = memnew(SpinBox); - s->share(v); - gc->add_child(v); - v->get_line_edit()->connect("focus_entered", callable_mp(this, &ColorPicker::_focus_enter)); - v->get_line_edit()->connect("focus_exited", callable_mp(this, &ColorPicker::_focus_exit)); + LineEdit *vle = val->get_line_edit(); + vle->connect("text_changed", callable_mp(this, &ColorPicker::_text_changed)); + vle->connect("gui_input", callable_mp(this, &ColorPicker::_line_edit_input)); + vle->set_horizontal_alignment(HORIZONTAL_ALIGNMENT_RIGHT); - s->set_h_size_flags(SIZE_EXPAND_FILL); + val->connect("gui_input", callable_mp(this, &ColorPicker::_slider_or_spin_input)); - s->connect("value_changed", callable_mp(this, &ColorPicker::_value_changed)); - s->connect("draw", callable_mp(this, &ColorPicker::_slider_draw).bind(idx)); + slider->set_h_size_flags(SIZE_EXPAND_FILL); + + slider->connect("value_changed", callable_mp(this, &ColorPicker::_value_changed)); + slider->connect("draw", callable_mp(this, &ColorPicker::_slider_draw).bind(idx)); + slider->connect("gui_input", callable_mp(this, &ColorPicker::_slider_or_spin_input)); if (idx < SLIDER_COUNT) { - sliders[idx] = s; - values[idx] = v; - labels[idx] = l; + sliders[idx] = slider; + values[idx] = val; + labels[idx] = lbl; } else { - alpha_slider = s; - alpha_value = v; - alpha_label = l; + alpha_slider = slider; + alpha_value = val; + alpha_label = lbl; } } -HSlider *ColorPicker::get_slider(int idx) { - if (idx < SLIDER_COUNT) { - return sliders[idx]; +HSlider *ColorPicker::get_slider(int p_idx) { + if (p_idx < SLIDER_COUNT) { + return sliders[p_idx]; } return alpha_slider; } Vector<float> ColorPicker::get_active_slider_values() { - Vector<float> values; + Vector<float> cur_values; for (int i = 0; i < current_slider_count; i++) { - values.push_back(sliders[i]->get_value()); + cur_values.push_back(sliders[i]->get_value()); } - values.push_back(alpha_slider->get_value()); - return values; + cur_values.push_back(alpha_slider->get_value()); + return cur_values; } void ColorPicker::_copy_color_to_hsv() { @@ -401,25 +440,53 @@ void ColorPicker::_copy_hsv_to_color() { } } +void ColorPicker::_select_from_preset_container(const Color &p_color) { + if (preset_group->get_pressed_button()) { + preset_group->get_pressed_button()->set_pressed(false); + } + + for (int i = 1; i < preset_container->get_child_count(); i++) { + ColorPresetButton *current_btn = Object::cast_to<ColorPresetButton>(preset_container->get_child(i)); + if (current_btn && p_color == current_btn->get_preset_color()) { + current_btn->set_pressed(true); + break; + } + } +} + +bool ColorPicker::_select_from_recent_preset_hbc(const Color &p_color) { + for (int i = 0; i < recent_preset_hbc->get_child_count(); i++) { + ColorPresetButton *current_btn = Object::cast_to<ColorPresetButton>(recent_preset_hbc->get_child(i)); + if (current_btn && p_color == current_btn->get_preset_color()) { + current_btn->set_pressed(true); + return true; + } + } + return false; +} + ColorPicker::PickerShapeType ColorPicker::_get_actual_shape() const { return modes[current_mode]->get_shape_override() != SHAPE_MAX ? modes[current_mode]->get_shape_override() : current_shape; } void ColorPicker::_reset_theme() { - Ref<StyleBoxEmpty> style_box_empty(memnew(StyleBoxEmpty)); - + Ref<StyleBoxFlat> style_box_flat(memnew(StyleBoxFlat)); + style_box_flat->set_default_margin(SIDE_TOP, 16 * get_theme_default_base_scale()); + style_box_flat->set_bg_color(Color(0.2, 0.23, 0.31).lerp(Color(0, 0, 0, 1), 0.3).clamp()); for (int i = 0; i < SLIDER_COUNT; i++) { sliders[i]->add_theme_icon_override("grabber", get_theme_icon(SNAME("bar_arrow"), SNAME("ColorPicker"))); sliders[i]->add_theme_icon_override("grabber_highlight", get_theme_icon(SNAME("bar_arrow"), SNAME("ColorPicker"))); - sliders[i]->add_theme_style_override("slider", style_box_empty); - sliders[i]->add_theme_style_override("grabber_area", style_box_empty); - sliders[i]->add_theme_style_override("grabber_area_highlight", style_box_empty); + sliders[i]->add_theme_constant_override("grabber_offset", 8 * get_theme_default_base_scale()); + if (!colorize_sliders) { + sliders[i]->add_theme_style_override("slider", style_box_flat); + } } alpha_slider->add_theme_icon_override("grabber", get_theme_icon(SNAME("bar_arrow"), SNAME("ColorPicker"))); alpha_slider->add_theme_icon_override("grabber_highlight", get_theme_icon(SNAME("bar_arrow"), SNAME("ColorPicker"))); - alpha_slider->add_theme_style_override("slider", style_box_empty); - alpha_slider->add_theme_style_override("grabber_area", style_box_empty); - alpha_slider->add_theme_style_override("grabber_area_highlight", style_box_empty); + alpha_slider->add_theme_constant_override("grabber_offset", 8 * get_theme_default_base_scale()); + if (!colorize_sliders) { + alpha_slider->add_theme_style_override("slider", style_box_flat); + } } void ColorPicker::_html_submitted(const String &p_html) { @@ -484,13 +551,41 @@ void ColorPicker::_update_presets() { cpb->set_custom_minimum_size(Size2(preset_size, preset_size)); } } - // Only load preset buttons when the only child is the add-preset button. - if (preset_container->get_child_count() == 1) { - for (int i = 0; i < preset_cache.size(); i++) { - _add_preset_button(preset_size, preset_cache[i]); + +#ifdef TOOLS_ENABLED + if (Engine::get_singleton()->is_editor_hint()) { + // Only load preset buttons when the only child is the add-preset button. + if (preset_container->get_child_count() == 1) { + for (int i = 0; i < preset_cache.size(); i++) { + _add_preset_button(preset_size, preset_cache[i]); + } + _notification(NOTIFICATION_VISIBILITY_CHANGED); } + } +#endif +} + +void ColorPicker::_update_recent_presets() { +#ifdef TOOLS_ENABLED + if (Engine::get_singleton()->is_editor_hint()) { + int recent_preset_count = recent_preset_hbc->get_child_count(); + for (int i = 0; i < recent_preset_count; i++) { + memdelete(recent_preset_hbc->get_child(0)); + } + + recent_presets.clear(); + for (int i = 0; i < recent_preset_cache.size(); i++) { + recent_presets.push_back(recent_preset_cache[i]); + } + + int preset_size = _get_preset_size(); + for (int i = 0; i < recent_presets.size(); i++) { + _add_recent_preset_button(preset_size, recent_presets[i]); + } + _notification(NOTIFICATION_VISIBILITY_CHANGED); } +#endif } void ColorPicker::_text_type_toggled() { @@ -500,11 +595,13 @@ void ColorPicker::_text_type_toggled() { text_type->set_icon(get_theme_icon(SNAME("Script"), SNAME("EditorIcons"))); c_text->set_editable(false); + c_text->set_h_size_flags(SIZE_EXPAND_FILL); } else { text_type->set_text("#"); text_type->set_icon(nullptr); c_text->set_editable(true); + c_text->set_h_size_flags(SIZE_FILL); } _update_color(); } @@ -515,9 +612,17 @@ Color ColorPicker::get_pick_color() const { void ColorPicker::set_picker_shape(PickerShapeType p_shape) { ERR_FAIL_INDEX(p_shape, SHAPE_MAX); - if (current_shape == p_shape) { + if (p_shape == current_shape) { return; } + if (current_shape != SHAPE_NONE) { + shape_popup->set_item_checked(current_shape, false); + } + if (p_shape != SHAPE_NONE) { + shape_popup->set_item_checked(p_shape, true); + btn_shape->set_icon(shape_popup->get_item_icon(p_shape)); + } + current_shape = p_shape; _copy_color_to_hsv(); @@ -531,45 +636,105 @@ 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("h_separation")) * (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) { - ColorPresetButton *btn_preset = memnew(ColorPresetButton(p_color)); - btn_preset->set_preset_color(p_color); - btn_preset->set_custom_minimum_size(Size2(p_size, p_size)); - btn_preset->connect("gui_input", callable_mp(this, &ColorPicker::_preset_input).bind(p_color)); - btn_preset->set_tooltip_text(vformat(RTR("Color: #%s\nLMB: Apply color\nRMB: Remove preset"), p_color.to_html(p_color.a < 1))); - preset_container->add_child(btn_preset); + ColorPresetButton *btn_preset_new = memnew(ColorPresetButton(p_color, p_size)); + btn_preset_new->set_tooltip_text(vformat(RTR("Color: #%s\nLMB: Apply color\nRMB: Remove preset"), p_color.to_html(p_color.a < 1))); + btn_preset_new->set_drag_forwarding(this); + btn_preset_new->set_button_group(preset_group); + preset_container->add_child(btn_preset_new); + btn_preset_new->set_pressed(true); + btn_preset_new->connect("gui_input", callable_mp(this, &ColorPicker::_preset_input).bind(p_color)); +} + +void ColorPicker::_add_recent_preset_button(int p_size, const Color &p_color) { + ColorPresetButton *btn_preset_new = memnew(ColorPresetButton(p_color, p_size)); + btn_preset_new->set_tooltip_text(vformat(RTR("Color: #%s\nLMB: Apply color"), p_color.to_html(p_color.a < 1))); + btn_preset_new->set_button_group(recent_preset_group); + recent_preset_hbc->add_child(btn_preset_new); + recent_preset_hbc->move_child(btn_preset_new, 0); + btn_preset_new->set_pressed(true); + btn_preset_new->connect("toggled", callable_mp(this, &ColorPicker::_recent_preset_pressed).bind(btn_preset_new)); +} + +void ColorPicker::_show_hide_preset(const bool &p_is_btn_pressed, Button *p_btn_preset, Container *p_preset_container) { + if (p_is_btn_pressed) { + p_preset_container->show(); + } else { + p_preset_container->hide(); + } + _update_drop_down_arrow(p_is_btn_pressed, p_btn_preset); } -void ColorPicker::_set_color_mode(ColorModeType p_mode) { - if (slider_theme_modified) { - _reset_theme(); +void ColorPicker::_update_drop_down_arrow(const bool &p_is_btn_pressed, Button *p_btn_preset) { + if (p_is_btn_pressed) { + p_btn_preset->set_icon(get_theme_icon(SNAME("expanded_arrow"), SNAME("ColorPicker"))); + } else { + p_btn_preset->set_icon(get_theme_icon(SNAME("folded_arrow"), SNAME("ColorPicker"))); } +} - current_mode = p_mode; +void ColorPicker::_set_mode_popup_value(ColorModeType p_mode) { + ERR_FAIL_INDEX(p_mode, MODE_MAX + 1); - if (!is_inside_tree()) { + if (p_mode == MODE_MAX) { + set_colorize_sliders(!colorize_sliders); + } else { + set_color_mode(p_mode); + } +} + +Variant ColorPicker::_get_drag_data_fw(const Point2 &p_point, Control *p_from_control) { + ColorPresetButton *dragged_preset_button = Object::cast_to<ColorPresetButton>(p_from_control); + + if (!dragged_preset_button) { + return Variant(); + } + + ColorPresetButton *drag_preview = memnew(ColorPresetButton(dragged_preset_button->get_preset_color(), _get_preset_size())); + set_drag_preview(drag_preview); + + Dictionary drag_data; + drag_data["type"] = "color_preset"; + drag_data["color_preset"] = dragged_preset_button->get_index(); + + return drag_data; +} + +bool ColorPicker::_can_drop_data_fw(const Point2 &p_point, const Variant &p_data, Control *p_from_control) const { + Dictionary d = p_data; + if (!d.has("type") || String(d["type"]) != "color_preset") { + return false; + } + return true; +} + +void ColorPicker::_drop_data_fw(const Point2 &p_point, const Variant &p_data, Control *p_from_control) { + Dictionary d = p_data; + if (!d.has("type")) { return; } - _update_controls(); - _update_color(); + if (String(d["type"]) == "color_preset") { + int preset_from_id = d["color_preset"]; + int hover_now = p_from_control->get_index(); + + if (preset_from_id == hover_now || hover_now == -1) { + return; + } + preset_container->move_child(preset_container->get_child(preset_from_id), hover_now); + } } void ColorPicker::add_preset(const Color &p_color) { - if (presets.find(p_color)) { - presets.move_to_back(presets.find(p_color)); + List<Color>::Element *e = presets.find(p_color); + if (e) { + presets.move_to_back(e); + preset_cache.move_to_back(preset_cache.find(p_color)); - // Find button to move to the end. - for (int i = 1; i < preset_container->get_child_count(); i++) { - ColorPresetButton *current_btn = Object::cast_to<ColorPresetButton>(preset_container->get_child(i)); - if (current_btn && p_color == current_btn->get_preset_color()) { - preset_container->move_child(current_btn, preset_container->get_child_count() - 1); - break; - } - } + preset_container->move_child(preset_group->get_pressed_button(), preset_container->get_child_count() - 1); } else { presets.push_back(p_color); preset_cache.push_back(p_color); @@ -585,16 +750,38 @@ void ColorPicker::add_preset(const Color &p_color) { #endif } +void ColorPicker::add_recent_preset(const Color &p_color) { + if (!_select_from_recent_preset_hbc(p_color)) { + if (recent_preset_hbc->get_child_count() >= PRESET_COLUMN_COUNT) { + recent_preset_cache.pop_front(); + recent_presets.pop_front(); + recent_preset_hbc->get_child(PRESET_COLUMN_COUNT - 1)->queue_free(); + } + recent_presets.push_back(p_color); + recent_preset_cache.push_back(p_color); + _add_recent_preset_button(_get_preset_size(), p_color); + } + _select_from_preset_container(p_color); + +#ifdef TOOLS_ENABLED + if (Engine::get_singleton()->is_editor_hint()) { + PackedColorArray arr_to_save = get_recent_presets(); + EditorSettings::get_singleton()->set_project_metadata("color_picker", "recent_presets", arr_to_save); + } +#endif +} + void ColorPicker::erase_preset(const Color &p_color) { - if (presets.find(p_color)) { - presets.erase(presets.find(p_color)); + List<Color>::Element *e = presets.find(p_color); + if (e) { + presets.erase(e); preset_cache.erase(preset_cache.find(p_color)); // Find preset button to remove. for (int i = 1; i < preset_container->get_child_count(); i++) { ColorPresetButton *current_btn = Object::cast_to<ColorPresetButton>(preset_container->get_child(i)); if (current_btn && p_color == current_btn->get_preset_color()) { - current_btn->queue_delete(); + current_btn->queue_free(); break; } } @@ -608,6 +795,30 @@ void ColorPicker::erase_preset(const Color &p_color) { } } +void ColorPicker::erase_recent_preset(const Color &p_color) { + List<Color>::Element *e = recent_presets.find(p_color); + if (e) { + recent_presets.erase(e); + recent_preset_cache.erase(recent_preset_cache.find(p_color)); + + // Find recent preset button to remove. + for (int i = 1; i < recent_preset_hbc->get_child_count(); i++) { + ColorPresetButton *current_btn = Object::cast_to<ColorPresetButton>(recent_preset_hbc->get_child(i)); + if (current_btn && p_color == current_btn->get_preset_color()) { + current_btn->queue_free(); + break; + } + } + +#ifdef TOOLS_ENABLED + if (Engine::get_singleton()->is_editor_hint()) { + PackedColorArray arr_to_save = get_recent_presets(); + EditorSettings::get_singleton()->set_project_metadata("color_picker", "recent_presets", arr_to_save); + } +#endif + } +} + PackedColorArray ColorPicker::get_presets() const { PackedColorArray arr; arr.resize(presets.size()); @@ -617,16 +828,84 @@ PackedColorArray ColorPicker::get_presets() const { return arr; } +PackedColorArray ColorPicker::get_recent_presets() const { + PackedColorArray arr; + arr.resize(recent_presets.size()); + for (int i = 0; i < recent_presets.size(); i++) { + arr.set(i, recent_presets[i]); + } + return arr; +} + void ColorPicker::set_color_mode(ColorModeType p_mode) { ERR_FAIL_INDEX(p_mode, MODE_MAX); - mode_option_button->select(p_mode); - _set_color_mode(p_mode); + + if (current_mode == p_mode) { + return; + } + + if (slider_theme_modified) { + _reset_theme(); + } + + mode_popup->set_item_checked(current_mode, false); + mode_popup->set_item_checked(p_mode, true); + + if (p_mode < MODE_BUTTON_COUNT) { + mode_btns[p_mode]->set_pressed(true); + } else if (current_mode < MODE_BUTTON_COUNT) { + mode_btns[current_mode]->set_pressed(false); + } + + current_mode = p_mode; + + if (!is_inside_tree()) { + return; + } + + _update_controls(); + _update_color(); } ColorPicker::ColorModeType ColorPicker::get_color_mode() const { return current_mode; } +void ColorPicker::set_colorize_sliders(bool p_colorize_sliders) { + if (colorize_sliders == p_colorize_sliders) { + return; + } + + colorize_sliders = p_colorize_sliders; + mode_popup->set_item_checked(MODE_MAX + 1, colorize_sliders); + + if (colorize_sliders) { + Ref<StyleBoxEmpty> style_box_empty(memnew(StyleBoxEmpty)); + + if (!slider_theme_modified) { + for (int i = 0; i < SLIDER_COUNT; i++) { + sliders[i]->add_theme_style_override("slider", style_box_empty); + } + } + alpha_slider->add_theme_style_override("slider", style_box_empty); + } else { + Ref<StyleBoxFlat> style_box_flat(memnew(StyleBoxFlat)); + style_box_flat->set_default_margin(SIDE_TOP, 16 * get_theme_default_base_scale()); + style_box_flat->set_bg_color(Color(0.2, 0.23, 0.31).lerp(Color(0, 0, 0, 1), 0.3).clamp()); + + if (!slider_theme_modified) { + for (int i = 0; i < SLIDER_COUNT; i++) { + sliders[i]->add_theme_style_override("slider", style_box_flat); + } + } + alpha_slider->add_theme_style_override("slider", style_box_flat); + } +} + +bool ColorPicker::is_colorizing_sliders() const { + return colorize_sliders; +} + void ColorPicker::set_deferred_mode(bool p_enabled) { deferred_mode_enabled = p_enabled; } @@ -636,7 +915,7 @@ bool ColorPicker::is_deferred_mode() const { } void ColorPicker::_update_text_value() { - bool visible = true; + bool text_visible = true; if (text_is_constructor) { String t = "Color(" + String::num(color.r) + ", " + String::num(color.g) + ", " + String::num(color.b); if (edit_alpha && color.a < 1) { @@ -648,13 +927,13 @@ void ColorPicker::_update_text_value() { } if (color.r > 1 || color.g > 1 || color.b > 1 || color.r < 0 || color.g < 0 || color.b < 0) { - visible = false; + text_visible = false; } else if (!text_is_constructor) { c_text->set_text(color.to_html(edit_alpha && color.a < 1)); } - text_type->set_visible(visible); - c_text->set_visible(visible); + text_type->set_visible(text_visible); + c_text->set_visible(text_visible); } void ColorPicker::_sample_input(const Ref<InputEvent> &p_event) { @@ -680,7 +959,7 @@ void ColorPicker::_sample_draw() { // Draw both old and new colors for easier comparison (only if spawned from a ColorPickerButton). const Rect2 rect_old = Rect2(Point2(), Size2(sample->get_size().width * 0.5, sample->get_size().height * 0.95)); - if (display_old_color && old_color.a < 1.0) { + if (old_color.a < 1.0) { sample->draw_texture_rect(get_theme_icon(SNAME("sample_bg"), SNAME("ColorPicker")), rect_old, true); } @@ -809,7 +1088,9 @@ void ColorPicker::_hsv_draw(int p_which, Control *c) { } else if (p_which == 1) { if (actual_shape == SHAPE_HSV_RECTANGLE) { Ref<Texture2D> hue = get_theme_icon(SNAME("color_hue"), SNAME("ColorPicker")); - c->draw_texture_rect(hue, Rect2(Point2(), c->get_size())); + c->draw_set_transform(Point2(), -Math_PI / 2, Size2(c->get_size().x, -c->get_size().y)); + c->draw_texture_rect(hue, Rect2(Point2(), Size2(1, 1))); + c->draw_set_transform(Point2(), 0, Size2(1, 1)); int y = c->get_size().y - c->get_size().y * (1.0 - h); Color col; col.set_hsv(h, 1, 1); @@ -819,16 +1100,24 @@ void ColorPicker::_hsv_draw(int p_which, Control *c) { Vector<Color> colors; Color col; col.set_ok_hsl(h, s, 1); - points.resize(4); - colors.resize(4); - points.set(0, Vector2()); - points.set(1, Vector2(c->get_size().x, 0)); + Color col2; + col2.set_ok_hsl(h, s, 0.5); + Color col3; + col3.set_ok_hsl(h, s, 0); + points.resize(6); + colors.resize(6); + points.set(0, Vector2(c->get_size().x, 0)); + points.set(1, Vector2(c->get_size().x, c->get_size().y * 0.5)); points.set(2, c->get_size()); points.set(3, Vector2(0, c->get_size().y)); + points.set(4, Vector2(0, c->get_size().y * 0.5)); + points.set(5, Vector2()); colors.set(0, col); - colors.set(1, col); - colors.set(2, Color(0, 0, 0)); - colors.set(3, Color(0, 0, 0)); + colors.set(1, col2); + colors.set(2, col3); + colors.set(3, col3); + colors.set(4, col2); + colors.set(5, col); c->draw_polygon(points, colors); int y = c->get_size().y - c->get_size().y * CLAMP(v, 0, 1); col.set_ok_hsl(h, 1, v); @@ -862,17 +1151,19 @@ void ColorPicker::_hsv_draw(int p_which, Control *c) { } void ColorPicker::_slider_draw(int p_which) { - modes[current_mode]->slider_draw(p_which); + if (colorize_sliders) { + modes[current_mode]->slider_draw(p_which); + } } void ColorPicker::_uv_input(const Ref<InputEvent> &p_event, Control *c) { Ref<InputEventMouseButton> bev = p_event; - PickerShapeType current_picker = _get_actual_shape(); + PickerShapeType actual_shape = _get_actual_shape(); if (bev.is_valid()) { if (bev->is_pressed() && bev->get_button_index() == MouseButton::LEFT) { Vector2 center = c->get_size() / 2.0; - if (current_picker == SHAPE_VHS_CIRCLE || current_picker == SHAPE_OKHSL_CIRCLE) { + if (actual_shape == SHAPE_VHS_CIRCLE || actual_shape == SHAPE_OKHSL_CIRCLE) { real_t dist = center.distance_to(bev->get_position()); if (dist <= center.x) { real_t rad = center.angle_to_point(bev->get_position()); @@ -920,8 +1211,11 @@ void ColorPicker::_uv_input(const Ref<InputEvent> &p_event, Control *c) { if (!deferred_mode_enabled) { emit_signal(SNAME("color_changed"), color); } - } else if (deferred_mode_enabled && !bev->is_pressed() && bev->get_button_index() == MouseButton::LEFT) { - emit_signal(SNAME("color_changed"), color); + } else if (!bev->is_pressed() && bev->get_button_index() == MouseButton::LEFT) { + if (deferred_mode_enabled) { + emit_signal(SNAME("color_changed"), color); + } + add_recent_preset(color); changing_color = false; spinning = false; } else { @@ -938,7 +1232,7 @@ void ColorPicker::_uv_input(const Ref<InputEvent> &p_event, Control *c) { } Vector2 center = c->get_size() / 2.0; - if (current_picker == SHAPE_VHS_CIRCLE || current_picker == SHAPE_OKHSL_CIRCLE) { + if (actual_shape == SHAPE_VHS_CIRCLE || actual_shape == SHAPE_OKHSL_CIRCLE) { real_t dist = center.distance_to(mev->get_position()); real_t rad = center.angle_to_point(mev->get_position()); h = ((rad >= 0) ? rad : (Math_TAU + rad)) / Math_TAU; @@ -993,9 +1287,10 @@ void ColorPicker::_w_input(const Ref<InputEvent> &p_event) { set_pick_color(color); _update_color(); - if (!deferred_mode_enabled) { + if (!bev->is_pressed() && bev->get_button_index() == MouseButton::LEFT) { + add_recent_preset(color); emit_signal(SNAME("color_changed"), color); - } else if (!bev->is_pressed() && bev->get_button_index() == MouseButton::LEFT) { + } else if (!deferred_mode_enabled) { emit_signal(SNAME("color_changed"), color); } } @@ -1024,20 +1319,55 @@ void ColorPicker::_w_input(const Ref<InputEvent> &p_event) { } } +void ColorPicker::_slider_or_spin_input(const Ref<InputEvent> &p_event) { + if (line_edit_mouse_release) { + line_edit_mouse_release = false; + return; + } + Ref<InputEventMouseButton> bev = p_event; + if (bev.is_valid() && !bev->is_pressed() && bev->get_button_index() == MouseButton::LEFT) { + add_recent_preset(color); + } +} + +void ColorPicker::_line_edit_input(const Ref<InputEvent> &p_event) { + Ref<InputEventMouseButton> bev = p_event; + if (bev.is_valid() && !bev->is_pressed() && bev->get_button_index() == MouseButton::LEFT) { + line_edit_mouse_release = true; + } +} + void ColorPicker::_preset_input(const Ref<InputEvent> &p_event, const Color &p_color) { Ref<InputEventMouseButton> bev = p_event; if (bev.is_valid()) { if (bev->is_pressed() && bev->get_button_index() == MouseButton::LEFT) { set_pick_color(p_color); + add_recent_preset(color); emit_signal(SNAME("color_changed"), p_color); - } else if (bev->is_pressed() && bev->get_button_index() == MouseButton::RIGHT && presets_enabled) { + } else if (bev->is_pressed() && bev->get_button_index() == MouseButton::RIGHT && can_add_swatches) { erase_preset(p_color); emit_signal(SNAME("preset_removed"), p_color); } } } +void ColorPicker::_recent_preset_pressed(const bool p_pressed, ColorPresetButton *p_preset) { + if (!p_pressed) { + return; + } + set_pick_color(p_preset->get_preset_color()); + + recent_presets.move_to_back(recent_presets.find(p_preset->get_preset_color())); + List<Color>::Element *e = recent_preset_cache.find(p_preset->get_preset_color()); + if (e) { + recent_preset_cache.move_to_back(e); + } + + recent_preset_hbc->move_child(p_preset, 0); + emit_signal(SNAME("color_changed"), p_preset->get_preset_color()); +} + void ColorPicker::_screen_input(const Ref<InputEvent> &p_event) { if (!is_inside_tree()) { return; @@ -1058,7 +1388,7 @@ void ColorPicker::_screen_input(const Ref<InputEvent> &p_event) { Ref<Image> img = r->get_texture()->get_image(); if (img.is_valid() && !img->is_empty()) { - Vector2 ofs = mev->get_global_position() - r->get_visible_rect().get_position(); + Vector2 ofs = mev->get_global_position(); Color c = img->get_pixel(ofs.x, ofs.y); set_pick_color(c); @@ -1066,6 +1396,10 @@ void ColorPicker::_screen_input(const Ref<InputEvent> &p_event) { } } +void ColorPicker::_text_changed(const String &) { + text_changed = true; +} + void ColorPicker::_add_preset_pressed() { add_preset(color); emit_signal(SNAME("preset_added"), color); @@ -1090,60 +1424,22 @@ void ColorPicker::_screen_pick_pressed() { screen->show(); } screen->move_to_front(); -#ifndef _MSC_VER -#warning show modal no longer works, needs to be converted to a popup -#endif + // TODO: show modal no longer works, needs to be converted to a popup. //screen->show_modal(); } -void ColorPicker::_focus_enter() { - bool has_ctext_focus = c_text->has_focus(); - if (has_ctext_focus) { - c_text->select_all(); - } else { - c_text->select(0, 0); - } - - for (int i = 0; i < current_slider_count; i++) { - if (values[i]->get_line_edit()->has_focus() && !has_ctext_focus) { - values[i]->get_line_edit()->select_all(); - } else { - values[i]->get_line_edit()->select(0, 0); - } - } - if (alpha_value->get_line_edit()->has_focus() && !has_ctext_focus) { - alpha_value->get_line_edit()->select_all(); - } else { - alpha_value->get_line_edit()->select(0, 0); - } -} - -void ColorPicker::_focus_exit() { - for (int i = 0; i < current_slider_count; i++) { - if (!values[i]->get_line_edit()->get_menu()->is_visible()) { - values[i]->get_line_edit()->select(0, 0); - } - } - if (!alpha_value->get_line_edit()->get_menu()->is_visible()) { - alpha_value->get_line_edit()->select(0, 0); - } - - c_text->select(0, 0); -} - void ColorPicker::_html_focus_exit() { if (c_text->is_menu_visible()) { return; } _html_submitted(c_text->get_text()); - _focus_exit(); } -void ColorPicker::set_presets_enabled(bool p_enabled) { - if (presets_enabled == p_enabled) { +void ColorPicker::set_can_add_swatches(bool p_enabled) { + if (can_add_swatches == p_enabled) { return; } - presets_enabled = p_enabled; + can_add_swatches = p_enabled; if (!p_enabled) { btn_add_preset->set_disabled(true); btn_add_preset->set_focus_mode(FOCUS_NONE); @@ -1153,8 +1449,8 @@ void ColorPicker::set_presets_enabled(bool p_enabled) { } } -bool ColorPicker::are_presets_enabled() const { - return presets_enabled; +bool ColorPicker::are_swatches_enabled() const { + return can_add_swatches; } void ColorPicker::set_presets_visible(bool p_visible) { @@ -1162,14 +1458,62 @@ void ColorPicker::set_presets_visible(bool p_visible) { return; } presets_visible = p_visible; - preset_separator->set_visible(p_visible); - preset_container->set_visible(p_visible); + btn_preset->set_visible(p_visible); + btn_recent_preset->set_visible(p_visible); } bool ColorPicker::are_presets_visible() const { return presets_visible; } +void ColorPicker::set_modes_visible(bool p_visible) { + if (color_modes_visible == p_visible) { + return; + } + color_modes_visible = p_visible; + mode_hbc->set_visible(p_visible); +} + +bool ColorPicker::are_modes_visible() const { + return color_modes_visible; +} + +void ColorPicker::set_sampler_visible(bool p_visible) { + if (sampler_visible == p_visible) { + return; + } + sampler_visible = p_visible; + sample_hbc->set_visible(p_visible); +} + +bool ColorPicker::is_sampler_visible() const { + return sampler_visible; +} + +void ColorPicker::set_sliders_visible(bool p_visible) { + if (sliders_visible == p_visible) { + return; + } + sliders_visible = p_visible; + slider_gc->set_visible(p_visible); +} + +bool ColorPicker::are_sliders_visible() const { + return sliders_visible; +} + +void ColorPicker::set_hex_visible(bool p_visible) { + if (hex_visible == p_visible) { + return; + } + hex_visible = p_visible; + hex_hbc->set_visible(p_visible); +} + +bool ColorPicker::is_hex_visible() const { + return hex_visible; +} + void ColorPicker::_bind_methods() { ClassDB::bind_method(D_METHOD("set_pick_color", "color"), &ColorPicker::set_pick_color); ClassDB::bind_method(D_METHOD("get_pick_color"), &ColorPicker::get_pick_color); @@ -1179,22 +1523,42 @@ void ColorPicker::_bind_methods() { ClassDB::bind_method(D_METHOD("get_color_mode"), &ColorPicker::get_color_mode); ClassDB::bind_method(D_METHOD("set_edit_alpha", "show"), &ColorPicker::set_edit_alpha); ClassDB::bind_method(D_METHOD("is_editing_alpha"), &ColorPicker::is_editing_alpha); - ClassDB::bind_method(D_METHOD("set_presets_enabled", "enabled"), &ColorPicker::set_presets_enabled); - ClassDB::bind_method(D_METHOD("are_presets_enabled"), &ColorPicker::are_presets_enabled); + ClassDB::bind_method(D_METHOD("set_can_add_swatches", "enabled"), &ColorPicker::set_can_add_swatches); + ClassDB::bind_method(D_METHOD("are_swatches_enabled"), &ColorPicker::are_swatches_enabled); ClassDB::bind_method(D_METHOD("set_presets_visible", "visible"), &ColorPicker::set_presets_visible); ClassDB::bind_method(D_METHOD("are_presets_visible"), &ColorPicker::are_presets_visible); + ClassDB::bind_method(D_METHOD("set_modes_visible", "visible"), &ColorPicker::set_modes_visible); + ClassDB::bind_method(D_METHOD("are_modes_visible"), &ColorPicker::are_modes_visible); + ClassDB::bind_method(D_METHOD("set_sampler_visible", "visible"), &ColorPicker::set_sampler_visible); + ClassDB::bind_method(D_METHOD("is_sampler_visible"), &ColorPicker::is_sampler_visible); + ClassDB::bind_method(D_METHOD("set_sliders_visible", "visible"), &ColorPicker::set_sliders_visible); + ClassDB::bind_method(D_METHOD("are_sliders_visible"), &ColorPicker::are_sliders_visible); + ClassDB::bind_method(D_METHOD("set_hex_visible", "visible"), &ColorPicker::set_hex_visible); + ClassDB::bind_method(D_METHOD("is_hex_visible"), &ColorPicker::is_hex_visible); ClassDB::bind_method(D_METHOD("add_preset", "color"), &ColorPicker::add_preset); ClassDB::bind_method(D_METHOD("erase_preset", "color"), &ColorPicker::erase_preset); ClassDB::bind_method(D_METHOD("get_presets"), &ColorPicker::get_presets); + ClassDB::bind_method(D_METHOD("add_recent_preset", "color"), &ColorPicker::add_recent_preset); + ClassDB::bind_method(D_METHOD("erase_recent_preset", "color"), &ColorPicker::erase_recent_preset); + ClassDB::bind_method(D_METHOD("get_recent_presets"), &ColorPicker::get_recent_presets); ClassDB::bind_method(D_METHOD("set_picker_shape", "shape"), &ColorPicker::set_picker_shape); ClassDB::bind_method(D_METHOD("get_picker_shape"), &ColorPicker::get_picker_shape); + ClassDB::bind_method(D_METHOD("_get_drag_data_fw"), &ColorPicker::_get_drag_data_fw); + ClassDB::bind_method(D_METHOD("_can_drop_data_fw"), &ColorPicker::_can_drop_data_fw); + ClassDB::bind_method(D_METHOD("_drop_data_fw"), &ColorPicker::_drop_data_fw); + ADD_PROPERTY(PropertyInfo(Variant::COLOR, "color"), "set_pick_color", "get_pick_color"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "edit_alpha"), "set_edit_alpha", "is_editing_alpha"); ADD_PROPERTY(PropertyInfo(Variant::INT, "color_mode", PROPERTY_HINT_ENUM, "RGB,HSV,RAW,OKHSL"), "set_color_mode", "get_color_mode"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "deferred_mode"), "set_deferred_mode", "is_deferred_mode"); - ADD_PROPERTY(PropertyInfo(Variant::INT, "picker_shape", PROPERTY_HINT_ENUM, "HSV Rectangle,HSV Rectangle Wheel,VHS Circle,OKHSL Circle"), "set_picker_shape", "get_picker_shape"); - ADD_PROPERTY(PropertyInfo(Variant::BOOL, "presets_enabled"), "set_presets_enabled", "are_presets_enabled"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "picker_shape", PROPERTY_HINT_ENUM, "HSV Rectangle,HSV Rectangle Wheel,VHS Circle,OKHSL Circle,None"), "set_picker_shape", "get_picker_shape"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "can_add_swatches"), "set_can_add_swatches", "are_swatches_enabled"); + ADD_GROUP("Customization", ""); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "sampler_visible"), "set_sampler_visible", "is_sampler_visible"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "color_modes_visible"), "set_modes_visible", "are_modes_visible"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "sliders_visible"), "set_sliders_visible", "are_sliders_visible"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "hex_visible"), "set_hex_visible", "is_hex_visible"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "presets_visible"), "set_presets_visible", "are_presets_visible"); ADD_SIGNAL(MethodInfo("color_changed", PropertyInfo(Variant::COLOR, "color"))); @@ -1210,13 +1574,14 @@ void ColorPicker::_bind_methods() { BIND_ENUM_CONSTANT(SHAPE_HSV_WHEEL); BIND_ENUM_CONSTANT(SHAPE_VHS_CIRCLE); BIND_ENUM_CONSTANT(SHAPE_OKHSL_CIRCLE); + BIND_ENUM_CONSTANT(SHAPE_NONE); } ColorPicker::ColorPicker() : BoxContainer(true) { HBoxContainer *hb_edit = memnew(HBoxContainer); add_child(hb_edit, false, INTERNAL_MODE_FRONT); - hb_edit->set_v_size_flags(SIZE_EXPAND_FILL); + hb_edit->set_v_size_flags(SIZE_SHRINK_BEGIN); uv_edit = memnew(Control); hb_edit->add_child(uv_edit); @@ -1226,58 +1591,110 @@ ColorPicker::ColorPicker() : uv_edit->set_v_size_flags(SIZE_EXPAND_FILL); uv_edit->connect("draw", callable_mp(this, &ColorPicker::_hsv_draw).bind(0, uv_edit)); - HBoxContainer *hb_smpl = memnew(HBoxContainer); - add_child(hb_smpl, false, INTERNAL_MODE_FRONT); + sample_hbc = memnew(HBoxContainer); + add_child(sample_hbc, false, INTERNAL_MODE_FRONT); + + btn_pick = memnew(Button); + sample_hbc->add_child(btn_pick); + btn_pick->set_toggle_mode(true); + btn_pick->set_tooltip_text(RTR("Pick a color from the editor window.")); + btn_pick->connect("pressed", callable_mp(this, &ColorPicker::_screen_pick_pressed)); sample = memnew(TextureRect); - hb_smpl->add_child(sample); + sample_hbc->add_child(sample); sample->set_h_size_flags(SIZE_EXPAND_FILL); sample->connect("gui_input", callable_mp(this, &ColorPicker::_sample_input)); sample->connect("draw", callable_mp(this, &ColorPicker::_sample_draw)); - btn_pick = memnew(Button); - btn_pick->set_flat(true); - hb_smpl->add_child(btn_pick); - btn_pick->set_toggle_mode(true); - btn_pick->set_tooltip_text(RTR("Pick a color from the editor window.")); - btn_pick->connect("pressed", callable_mp(this, &ColorPicker::_screen_pick_pressed)); + btn_shape = memnew(MenuButton); + btn_shape->set_flat(false); + sample_hbc->add_child(btn_shape); + btn_shape->set_toggle_mode(true); + btn_shape->set_tooltip_text(RTR("Select a picker shape.")); + + current_shape = SHAPE_HSV_RECTANGLE; + + shape_popup = btn_shape->get_popup(); + shape_popup->add_icon_radio_check_item(get_theme_icon(SNAME("shape_rect"), SNAME("ColorPicker")), "HSV Rectangle", SHAPE_HSV_RECTANGLE); + shape_popup->add_icon_radio_check_item(get_theme_icon(SNAME("shape_rect_wheel"), SNAME("ColorPicker")), "HSV Wheel", SHAPE_HSV_WHEEL); + shape_popup->add_icon_radio_check_item(get_theme_icon(SNAME("shape_circle"), SNAME("ColorPicker")), "VHS Circle", SHAPE_VHS_CIRCLE); + shape_popup->add_icon_radio_check_item(get_theme_icon(SNAME("shape_circle"), SNAME("ColorPicker")), "OKHSL Circle", SHAPE_OKHSL_CIRCLE); + shape_popup->set_item_checked(current_shape, true); + shape_popup->connect("id_pressed", callable_mp(this, &ColorPicker::set_picker_shape)); + + btn_shape->set_icon(shape_popup->get_item_icon(current_shape)); + + add_mode(new ColorModeRGB(this)); + add_mode(new ColorModeHSV(this)); + add_mode(new ColorModeRAW(this)); + add_mode(new ColorModeOKHSL(this)); + + mode_hbc = memnew(HBoxContainer); + add_child(mode_hbc, false, INTERNAL_MODE_FRONT); + + mode_group.instantiate(); + + for (int i = 0; i < MODE_BUTTON_COUNT; i++) { + mode_btns[i] = memnew(Button); + mode_hbc->add_child(mode_btns[i]); + mode_btns[i]->set_focus_mode(FOCUS_NONE); + mode_btns[i]->set_h_size_flags(SIZE_EXPAND_FILL); + mode_btns[i]->add_theme_style_override("pressed", get_theme_stylebox("tab_selected", "TabContainer")); + mode_btns[i]->add_theme_style_override("normal", get_theme_stylebox("tab_unselected", "TabContainer")); + mode_btns[i]->add_theme_style_override("hover", get_theme_stylebox("tab_selected", "TabContainer")); + mode_btns[i]->set_toggle_mode(true); + mode_btns[i]->set_text(modes[i]->get_name()); + mode_btns[i]->set_button_group(mode_group); + mode_btns[i]->connect("pressed", callable_mp(this, &ColorPicker::set_color_mode).bind((ColorModeType)i)); + } + mode_btns[0]->set_pressed(true); + + btn_mode = memnew(MenuButton); + btn_mode->set_text("..."); + btn_mode->set_flat(false); + mode_hbc->add_child(btn_mode); + btn_mode->set_toggle_mode(true); + btn_mode->set_tooltip_text(RTR("Select a picker mode.")); + current_mode = MODE_RGB; + + mode_popup = btn_mode->get_popup(); + for (int i = 0; i < modes.size(); i++) { + mode_popup->add_radio_check_item(modes[i]->get_name(), i); + } + mode_popup->add_separator(); + mode_popup->add_check_item("Colorized Sliders", MODE_MAX); + mode_popup->set_item_checked(current_mode, true); + mode_popup->set_item_checked(MODE_MAX + 1, true); + mode_popup->connect("id_pressed", callable_mp(this, &ColorPicker::_set_mode_popup_value)); VBoxContainer *vbl = memnew(VBoxContainer); add_child(vbl, false, INTERNAL_MODE_FRONT); - add_child(memnew(HSeparator), false, INTERNAL_MODE_FRONT); - VBoxContainer *vbr = memnew(VBoxContainer); add_child(vbr, false, INTERNAL_MODE_FRONT); vbr->set_h_size_flags(SIZE_EXPAND_FILL); - GridContainer *gc = memnew(GridContainer); + slider_gc = memnew(GridContainer); - vbr->add_child(gc); - gc->set_h_size_flags(SIZE_EXPAND_FILL); - gc->set_columns(3); + vbr->add_child(slider_gc); + slider_gc->set_h_size_flags(SIZE_EXPAND_FILL); + slider_gc->set_columns(3); for (int i = 0; i < SLIDER_COUNT + 1; i++) { - create_slider(gc, i); + create_slider(slider_gc, i); } alpha_label->set_text("A"); - HBoxContainer *hhb = memnew(HBoxContainer); - vbr->add_child(hhb); + hex_hbc = memnew(HBoxContainer); + hex_hbc->set_alignment(ALIGNMENT_BEGIN); + vbr->add_child(hex_hbc); - mode_option_button = memnew(OptionButton); - - hhb->add_child(mode_option_button); - add_mode(new ColorModeRGB(this)); - add_mode(new ColorModeHSV(this)); - add_mode(new ColorModeRAW(this)); - add_mode(new ColorModeOKHSL(this)); - mode_option_button->connect("item_selected", callable_mp(this, &ColorPicker::_set_color_mode)); + hex_hbc->add_child(memnew(Label("Hex"))); text_type = memnew(Button); - hhb->add_child(text_type); + hex_hbc->add_child(text_type); text_type->set_text("#"); text_type->set_tooltip_text(RTR("Switch between hexadecimal and code values.")); if (Engine::get_singleton()->is_editor_hint()) { @@ -1288,10 +1705,10 @@ ColorPicker::ColorPicker() : } c_text = memnew(LineEdit); - hhb->add_child(c_text); - c_text->set_h_size_flags(SIZE_EXPAND_FILL); + hex_hbc->add_child(c_text); + c_text->set_select_all_on_focus(true); c_text->connect("text_submitted", callable_mp(this, &ColorPicker::_html_submitted)); - c_text->connect("focus_entered", callable_mp(this, &ColorPicker::_focus_enter)); + c_text->connect("text_changed", callable_mp(this, &ColorPicker::_text_changed)); c_text->connect("focus_exited", callable_mp(this, &ColorPicker::_html_focus_exit)); wheel_edit = memnew(AspectRatioContainer); @@ -1328,16 +1745,43 @@ ColorPicker::ColorPicker() : _update_controls(); updating = false; - set_pick_color(Color(1, 1, 1)); - - preset_separator = memnew(HSeparator); - add_child(preset_separator, false, INTERNAL_MODE_FRONT); - preset_container = memnew(GridContainer); preset_container->set_h_size_flags(SIZE_EXPAND_FILL); - preset_container->set_columns(preset_column_count); + preset_container->set_columns(PRESET_COLUMN_COUNT); + preset_container->hide(); + + preset_group.instantiate(); + + btn_preset = memnew(Button); + btn_preset->set_text("Swatches"); + btn_preset->set_flat(true); + btn_preset->set_toggle_mode(true); + btn_preset->set_focus_mode(FOCUS_NONE); + btn_preset->set_text_alignment(HORIZONTAL_ALIGNMENT_LEFT); + btn_preset->connect("toggled", callable_mp(this, &ColorPicker::_show_hide_preset).bind(btn_preset, preset_container)); + add_child(btn_preset, false, INTERNAL_MODE_FRONT); + add_child(preset_container, false, INTERNAL_MODE_FRONT); + recent_preset_hbc = memnew(HBoxContainer); + recent_preset_hbc->set_v_size_flags(SIZE_SHRINK_BEGIN); + recent_preset_hbc->hide(); + + recent_preset_group.instantiate(); + + btn_recent_preset = memnew(Button); + btn_recent_preset->set_text("Recent Colors"); + btn_recent_preset->set_flat(true); + btn_recent_preset->set_toggle_mode(true); + btn_recent_preset->set_focus_mode(FOCUS_NONE); + btn_recent_preset->set_text_alignment(HORIZONTAL_ALIGNMENT_LEFT); + btn_recent_preset->connect("toggled", callable_mp(this, &ColorPicker::_show_hide_preset).bind(btn_recent_preset, recent_preset_hbc)); + add_child(btn_recent_preset, false, INTERNAL_MODE_FRONT); + + add_child(recent_preset_hbc, false, INTERNAL_MODE_FRONT); + + set_pick_color(Color(1, 1, 1)); + btn_add_preset = memnew(Button); btn_add_preset->set_icon_alignment(HORIZONTAL_ALIGNMENT_CENTER); btn_add_preset->set_tooltip_text(RTR("Add current color as a preset.")); @@ -1378,6 +1822,7 @@ void ColorPickerButton::pressed() { popup->reset_size(); picker->_update_presets(); + picker->_update_recent_presets(); Rect2i usable_rect = popup->get_usable_parent_rect(); //let's try different positions to see which one we can use @@ -1484,6 +1929,7 @@ void ColorPickerButton::_update_picker() { picker->connect("color_changed", callable_mp(this, &ColorPickerButton::_color_changed)); popup->connect("about_to_popup", callable_mp(this, &ColorPickerButton::_about_to_popup)); popup->connect("popup_hide", callable_mp(this, &ColorPickerButton::_modal_closed)); + picker->connect("minimum_size_changed", callable_mp((Window *)popup, &Window::reset_size)); picker->set_pick_color(color); picker->set_edit_alpha(edit_alpha); picker->set_display_old_color(true); @@ -1523,6 +1969,13 @@ void ColorPresetButton::_notification(int p_what) { Ref<StyleBoxTexture> sb_texture = sb_raw; if (sb_flat.is_valid()) { + sb_flat->set_border_width(SIDE_BOTTOM, 2); + if (get_draw_mode() == DRAW_PRESSED || get_draw_mode() == DRAW_HOVER_PRESSED) { + sb_flat->set_border_color(Color(1, 1, 1, 1)); + } else { + sb_flat->set_border_color(Color(0, 0, 0, 1)); + } + if (preset_color.a < 1) { // Draw a background pattern when the color is transparent. sb_flat->set_bg_color(Color(1, 1, 1)); @@ -1566,8 +2019,10 @@ Color ColorPresetButton::get_preset_color() const { return preset_color; } -ColorPresetButton::ColorPresetButton(Color p_color) { +ColorPresetButton::ColorPresetButton(Color p_color, int p_size) { preset_color = p_color; + set_toggle_mode(true); + set_custom_minimum_size(Size2(p_size, p_size)); } ColorPresetButton::~ColorPresetButton() { diff --git a/scene/gui/color_picker.h b/scene/gui/color_picker.h index 05b760b109..3208676539 100644 --- a/scene/gui/color_picker.h +++ b/scene/gui/color_picker.h @@ -38,6 +38,7 @@ #include "scene/gui/grid_container.h" #include "scene/gui/label.h" #include "scene/gui/line_edit.h" +#include "scene/gui/menu_button.h" #include "scene/gui/option_button.h" #include "scene/gui/popup.h" #include "scene/gui/separator.h" @@ -63,7 +64,7 @@ public: void set_preset_color(const Color &p_color); Color get_preset_color() const; - ColorPresetButton(Color p_color); + ColorPresetButton(Color p_color, int p_size); ~ColorPresetButton(); }; @@ -85,6 +86,7 @@ public: SHAPE_HSV_WHEEL, SHAPE_VHS_CIRCLE, SHAPE_OKHSL_CIRCLE, + SHAPE_NONE, SHAPE_MAX }; @@ -96,8 +98,10 @@ private: static Ref<Shader> circle_shader; static Ref<Shader> circle_ok_color_shader; static List<Color> preset_cache; + static List<Color> recent_preset_cache; int current_slider_count = SLIDER_COUNT; + static const int MODE_BUTTON_COUNT = 3; bool slider_theme_modified = true; @@ -114,9 +118,24 @@ private: Control *wheel_uv = nullptr; TextureRect *sample = nullptr; GridContainer *preset_container = nullptr; - HSeparator *preset_separator = nullptr; + HBoxContainer *recent_preset_hbc = nullptr; Button *btn_add_preset = nullptr; Button *btn_pick = nullptr; + Button *btn_preset = nullptr; + Button *btn_recent_preset = nullptr; + PopupMenu *shape_popup = nullptr; + PopupMenu *mode_popup = nullptr; + MenuButton *btn_shape = nullptr; + HBoxContainer *mode_hbc = nullptr; + HBoxContainer *sample_hbc = nullptr; + GridContainer *slider_gc = nullptr; + HBoxContainer *hex_hbc = nullptr; + MenuButton *btn_mode = nullptr; + Button *mode_btns[MODE_BUTTON_COUNT]; + Ref<ButtonGroup> mode_group = nullptr; + ColorPresetButton *selected_recent_preset = nullptr; + Ref<ButtonGroup> preset_group; + Ref<ButtonGroup> recent_preset_group; OptionButton *mode_option_button = nullptr; @@ -135,10 +154,13 @@ private: bool text_is_constructor = false; PickerShapeType current_shape = SHAPE_HSV_RECTANGLE; ColorModeType current_mode = MODE_RGB; + bool colorize_sliders = true; - const int preset_column_count = 9; + const int PRESET_COLUMN_COUNT = 9; int prev_preset_size = 0; + int prev_rencet_preset_size = 0; List<Color> presets; + List<Color> recent_presets; Color color; Color old_color; @@ -148,8 +170,14 @@ private: bool updating = true; bool changing_color = false; bool spinning = false; - bool presets_enabled = true; + bool can_add_swatches = true; bool presets_visible = true; + bool color_modes_visible = true; + bool sampler_visible = true; + bool sliders_visible = true; + bool hex_visible = true; + bool line_edit_mouse_release = false; + bool text_changed = false; float h = 0.0; float s = 0.0; @@ -175,18 +203,28 @@ private: void _uv_input(const Ref<InputEvent> &p_event, Control *c); void _w_input(const Ref<InputEvent> &p_event); + void _slider_or_spin_input(const Ref<InputEvent> &p_event); + void _line_edit_input(const Ref<InputEvent> &p_event); void _preset_input(const Ref<InputEvent> &p_event, const Color &p_color); + void _recent_preset_pressed(const bool pressed, ColorPresetButton *p_preset); void _screen_input(const Ref<InputEvent> &p_event); + void _text_changed(const String &p_new_text); void _add_preset_pressed(); void _screen_pick_pressed(); - void _focus_enter(); - void _focus_exit(); void _html_focus_exit(); inline int _get_preset_size(); void _add_preset_button(int p_size, const Color &p_color); + void _add_recent_preset_button(int p_size, const Color &p_color); - void _set_color_mode(ColorModeType p_mode); + void _show_hide_preset(const bool &p_is_btn_pressed, Button *p_btn_preset, Container *p_preset_container); + void _update_drop_down_arrow(const bool &p_is_btn_pressed, Button *p_btn_preset); + + void _set_mode_popup_value(ColorModeType p_mode); + + Variant _get_drag_data_fw(const Point2 &p_point, Control *p_from_control); + bool _can_drop_data_fw(const Point2 &p_point, const Variant &p_data, Control *p_from_control) const; + void _drop_data_fw(const Point2 &p_point, const Variant &p_data, Control *p_from_control); protected: void _notification(int); @@ -218,22 +256,44 @@ public: PickerShapeType get_picker_shape() const; void add_preset(const Color &p_color); + void add_recent_preset(const Color &p_color); void erase_preset(const Color &p_color); + void erase_recent_preset(const Color &p_color); PackedColorArray get_presets() const; + PackedColorArray get_recent_presets() const; void _update_presets(); + void _update_recent_presets(); + + void _select_from_preset_container(const Color &p_color); + bool _select_from_recent_preset_hbc(const Color &p_color); void set_color_mode(ColorModeType p_mode); ColorModeType get_color_mode() const; + void set_colorize_sliders(bool p_colorize_sliders); + bool is_colorizing_sliders() const; + void set_deferred_mode(bool p_enabled); bool is_deferred_mode() const; - void set_presets_enabled(bool p_enabled); - bool are_presets_enabled() const; + void set_can_add_swatches(bool p_enabled); + bool are_swatches_enabled() const; void set_presets_visible(bool p_visible); bool are_presets_visible() const; + void set_modes_visible(bool p_visible); + bool are_modes_visible() const; + + void set_sampler_visible(bool p_visible); + bool is_sampler_visible() const; + + void set_sliders_visible(bool p_visible); + bool are_sliders_visible() const; + + void set_hex_visible(bool p_visible); + bool is_hex_visible() const; + void set_focus_on_line_edit(); ColorPicker(); diff --git a/scene/gui/control.cpp b/scene/gui/control.cpp index dc9294df6d..92ee21a916 100644 --- a/scene/gui/control.cpp +++ b/scene/gui/control.cpp @@ -633,7 +633,7 @@ Rect2 Control::get_parent_anchorable_rect() const { #ifdef TOOLS_ENABLED Node *edited_root = get_tree()->get_edited_scene_root(); if (edited_root && (this == edited_root || edited_root->is_ancestor_of(this))) { - parent_rect.size = Size2(ProjectSettings::get_singleton()->get("display/window/size/viewport_width"), ProjectSettings::get_singleton()->get("display/window/size/viewport_height")); + parent_rect.size = Size2(GLOBAL_GET("display/window/size/viewport_width"), GLOBAL_GET("display/window/size/viewport_height")); } else { parent_rect = get_viewport()->get_visible_rect(); } @@ -1436,13 +1436,6 @@ Rect2 Control::get_screen_rect() const { return r; } -Rect2 Control::get_window_rect() const { - ERR_FAIL_COND_V(!is_inside_tree(), Rect2()); - Rect2 gr = get_global_rect(); - gr.position += get_viewport()->get_visible_rect().position; - return gr; -} - Rect2 Control::get_anchorable_rect() const { return Rect2(Point2(), get_size()); } @@ -1558,13 +1551,11 @@ bool Control::is_minimum_size_adjust_blocked() const { Size2 Control::get_minimum_size() const { Vector2 ms; - if (GDVIRTUAL_CALL(_get_minimum_size, ms)) { - return ms; - } - return Vector2(); + GDVIRTUAL_CALL(_get_minimum_size, ms); + return ms; } -void Control::set_custom_minimum_size(const Size2i &p_custom) { +void Control::set_custom_minimum_size(const Size2 &p_custom) { if (p_custom == data.custom_minimum_size) { return; } @@ -1572,7 +1563,7 @@ void Control::set_custom_minimum_size(const Size2i &p_custom) { update_minimum_size(); } -Size2i Control::get_custom_minimum_size() const { +Size2 Control::get_custom_minimum_size() const { return data.custom_minimum_size; } @@ -1759,6 +1750,34 @@ void Control::warp_mouse(const Point2 &p_position) { get_viewport()->warp_mouse(get_global_transform_with_canvas().xform(p_position)); } +void Control::set_shortcut_context(const Node *p_node) { + if (p_node != nullptr) { + data.shortcut_context = p_node->get_instance_id(); + } else { + data.shortcut_context = ObjectID(); + } +} + +Node *Control::get_shortcut_context() const { + Object *ctx_obj = ObjectDB::get_instance(data.shortcut_context); + Node *ctx_node = Object::cast_to<Node>(ctx_obj); + + return ctx_node; +} + +bool Control::is_focus_owner_in_shortcut_context() const { + if (data.shortcut_context == ObjectID()) { + // No context, therefore global - always "in" context. + return true; + } + + const Node *ctx_node = get_shortcut_context(); + const Control *vp_focus = get_viewport() ? get_viewport()->gui_get_focus_owner() : nullptr; + + // If the context is valid and the viewport focus is valid, check if the context is the focus or is a parent of it. + return ctx_node && vp_focus && (ctx_node == vp_focus || ctx_node->is_ancestor_of(vp_focus)); +} + // Drag and drop handling. void Control::set_drag_forwarding(Object *p_target) { @@ -1778,11 +1797,8 @@ Variant Control::get_drag_data(const Point2 &p_point) { } Variant dd; - if (GDVIRTUAL_CALL(_get_drag_data, p_point, dd)) { - return dd; - } - - return Variant(); + GDVIRTUAL_CALL(_get_drag_data, p_point, dd); + return dd; } bool Control::can_drop_data(const Point2 &p_point, const Variant &p_data) const { @@ -1793,11 +1809,9 @@ bool Control::can_drop_data(const Point2 &p_point, const Variant &p_data) const } } - bool ret; - if (GDVIRTUAL_CALL(_can_drop_data, p_point, p_data, ret)) { - return ret; - } - return false; + bool ret = false; + GDVIRTUAL_CALL(_can_drop_data, p_point, p_data, ret); + return ret; } void Control::drop_data(const Point2 &p_point, const Variant &p_data) { @@ -2704,11 +2718,8 @@ void Control::end_bulk_theme_override() { TypedArray<Vector2i> 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) { TypedArray<Vector2i> ret; - if (GDVIRTUAL_CALL(_structured_text_parser, p_args, p_text, ret)) { - return ret; - } else { - return TypedArray<Vector2i>(); - } + GDVIRTUAL_CALL(_structured_text_parser, p_args, p_text, ret); + return ret; } else { return TS->parse_structured_text(p_parser_type, p_args, p_text); } @@ -2762,6 +2773,20 @@ bool Control::is_layout_rtl() const { return data.is_rtl; } +void Control::set_localize_numeral_system(bool p_enable) { + if (p_enable == data.localize_numeral_system) { + return; + } + + data.localize_numeral_system = p_enable; + + notification(MainLoop::NOTIFICATION_TRANSLATION_CHANGED); +} + +bool Control::is_localizing_numeral_system() const { + return data.localize_numeral_system; +} + void Control::set_auto_translate(bool p_enable) { if (p_enable == data.auto_translate) { return; @@ -2793,10 +2818,8 @@ String Control::get_tooltip(const Point2 &p_pos) const { Control *Control::make_custom_tooltip(const String &p_text) const { Object *ret = nullptr; - if (GDVIRTUAL_CALL(_make_custom_tooltip, p_text, ret)) { - return Object::cast_to<Control>(ret); - } - return nullptr; + GDVIRTUAL_CALL(_make_custom_tooltip, p_text, ret); + return Object::cast_to<Control>(ret); } // Base object overrides. @@ -2885,8 +2908,8 @@ void Control::_notification(int p_notification) { if (data.parent_canvas_item) { data.parent_canvas_item->disconnect("item_rect_changed", callable_mp(this, &Control::_size_changed)); data.parent_canvas_item = nullptr; - } else if (!is_set_as_top_level()) { - //disconnect viewport + } else { + // Disconnect viewport. Viewport *viewport = get_viewport(); ERR_FAIL_COND(!viewport); viewport->disconnect("size_changed", callable_mp(this, &Control::_size_changed)); @@ -2912,7 +2935,7 @@ void Control::_notification(int p_notification) { queue_redraw(); if (data.RI) { - get_viewport()->_gui_set_root_order_dirty(); + get_viewport()->gui_set_root_order_dirty(); } } break; @@ -3133,6 +3156,9 @@ void Control::_bind_methods() { ClassDB::bind_method(D_METHOD("warp_mouse", "position"), &Control::warp_mouse); + ClassDB::bind_method(D_METHOD("set_shortcut_context", "node"), &Control::set_shortcut_context); + ClassDB::bind_method(D_METHOD("get_shortcut_context"), &Control::get_shortcut_context); + ClassDB::bind_method(D_METHOD("update_minimum_size"), &Control::update_minimum_size); ClassDB::bind_method(D_METHOD("set_layout_direction", "direction"), &Control::set_layout_direction); @@ -3142,9 +3168,12 @@ void Control::_bind_methods() { ClassDB::bind_method(D_METHOD("set_auto_translate", "enable"), &Control::set_auto_translate); ClassDB::bind_method(D_METHOD("is_auto_translating"), &Control::is_auto_translating); + ClassDB::bind_method(D_METHOD("set_localize_numeral_system", "enable"), &Control::set_localize_numeral_system); + ClassDB::bind_method(D_METHOD("is_localizing_numeral_system"), &Control::is_localizing_numeral_system); + ADD_GROUP("Layout", ""); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "clip_contents"), "set_clip_contents", "is_clipping_contents"); - ADD_PROPERTY(PropertyInfo(Variant::VECTOR2I, "custom_minimum_size", PROPERTY_HINT_NONE, "suffix:px"), "set_custom_minimum_size", "get_custom_minimum_size"); + ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "custom_minimum_size", PROPERTY_HINT_NONE, "suffix:px"), "set_custom_minimum_size", "get_custom_minimum_size"); ADD_PROPERTY(PropertyInfo(Variant::INT, "layout_direction", PROPERTY_HINT_ENUM, "Inherited,Locale,Left-to-Right,Right-to-Left"), "set_layout_direction", "get_layout_direction"); ADD_PROPERTY(PropertyInfo(Variant::INT, "layout_mode", PROPERTY_HINT_ENUM, "Position,Anchors,Container,Uncontrolled", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_INTERNAL), "_set_layout_mode", "_get_layout_mode"); ADD_PROPERTY_DEFAULT("layout_mode", LayoutMode::LAYOUT_MODE_POSITION); @@ -3186,8 +3215,9 @@ void Control::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::INT, "size_flags_vertical", PROPERTY_HINT_FLAGS, "Fill:1,Expand:2,Shrink Center:4,Shrink End:8"), "set_v_size_flags", "get_v_size_flags"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "size_flags_stretch_ratio", PROPERTY_HINT_RANGE, "0,20,0.01,or_greater"), "set_stretch_ratio", "get_stretch_ratio"); - ADD_GROUP("Auto Translate", ""); + ADD_GROUP("Localization", ""); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "auto_translate"), "set_auto_translate", "is_auto_translating"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "localize_numeral_system"), "set_localize_numeral_system", "is_localizing_numeral_system"); ADD_GROUP("Tooltip", "tooltip_"); ADD_PROPERTY(PropertyInfo(Variant::STRING, "tooltip_text", PROPERTY_HINT_MULTILINE_TEXT), "set_tooltip_text", "get_tooltip_text"); @@ -3206,6 +3236,9 @@ void Control::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::BOOL, "mouse_force_pass_scroll_events"), "set_force_pass_scroll_events", "is_force_pass_scroll_events"); ADD_PROPERTY(PropertyInfo(Variant::INT, "mouse_default_cursor_shape", PROPERTY_HINT_ENUM, "Arrow,I-Beam,Pointing Hand,Cross,Wait,Busy,Drag,Can Drop,Forbidden,Vertical Resize,Horizontal Resize,Secondary Diagonal Resize,Main Diagonal Resize,Move,Vertical Split,Horizontal Split,Help"), "set_default_cursor_shape", "get_default_cursor_shape"); + ADD_GROUP("Input", ""); + ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "shortcut_context", PROPERTY_HINT_NODE_TYPE, "Node"), "set_shortcut_context", "get_shortcut_context"); + ADD_GROUP("Theme", "theme_"); ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "theme", PROPERTY_HINT_RESOURCE_TYPE, "Theme"), "set_theme", "get_theme"); ADD_PROPERTY(PropertyInfo(Variant::STRING, "theme_type_variation", PROPERTY_HINT_ENUM_SUGGESTION), "set_theme_type_variation", "get_theme_type_variation"); diff --git a/scene/gui/control.h b/scene/gui/control.h index ee6443c81c..3e9bb48a4a 100644 --- a/scene/gui/control.h +++ b/scene/gui/control.h @@ -159,6 +159,7 @@ private: } }; + // This Data struct is to avoid namespace pollution in derived classes. struct Data { // Global relations. @@ -200,7 +201,7 @@ private: int h_size_flags = SIZE_FILL; int v_size_flags = SIZE_FILL; real_t expand = 1.0; - Point2i custom_minimum_size; + Point2 custom_minimum_size; // Input events and rendering. @@ -218,6 +219,8 @@ private: NodePath focus_next; NodePath focus_prev; + ObjectID shortcut_context; + // Theming. ThemeOwner *theme_owner = nullptr; @@ -246,6 +249,7 @@ private: bool is_rtl = false; bool auto_translate = true; + bool localize_numeral_system = true; // Extra properties. @@ -442,7 +446,6 @@ public: Rect2 get_rect() const; Rect2 get_global_rect() const; Rect2 get_screen_rect() const; - Rect2 get_window_rect() const; ///< use with care, as it blocks waiting for the rendering server Rect2 get_anchorable_rect() const override; void set_scale(const Vector2 &p_scale); @@ -460,8 +463,8 @@ public: virtual Size2 get_minimum_size() const; virtual Size2 get_combined_minimum_size() const; - void set_custom_minimum_size(const Size2i &p_custom); - Size2i get_custom_minimum_size() const; + void set_custom_minimum_size(const Size2 &p_custom); + Size2 get_custom_minimum_size() const; // Container sizing. @@ -487,6 +490,10 @@ public: void warp_mouse(const Point2 &p_position); + bool is_focus_owner_in_shortcut_context() const; + void set_shortcut_context(const Node *p_node); + Node *get_shortcut_context() const; + // Drag and drop handling. virtual void set_drag_forwarding(Object *p_target); @@ -589,6 +596,9 @@ public: LayoutDirection get_layout_direction() const; virtual bool is_layout_rtl() const; + void set_localize_numeral_system(bool p_enable); + bool is_localizing_numeral_system() const; + void set_auto_translate(bool p_enable); bool is_auto_translating() const; _FORCE_INLINE_ String atr(const String p_string) const { diff --git a/scene/gui/dialogs.cpp b/scene/gui/dialogs.cpp index f5edaf02d8..0d265719ec 100644 --- a/scene/gui/dialogs.cpp +++ b/scene/gui/dialogs.cpp @@ -208,24 +208,24 @@ void AcceptDialog::register_text_enter(Control *p_line_edit) { } void AcceptDialog::_update_child_rects() { - Size2 size = get_size(); + Size2 dlg_size = get_size(); float h_margins = theme_cache.panel_style->get_margin(SIDE_LEFT) + theme_cache.panel_style->get_margin(SIDE_RIGHT); float v_margins = theme_cache.panel_style->get_margin(SIDE_TOP) + theme_cache.panel_style->get_margin(SIDE_BOTTOM); // Fill the entire size of the window with the background. bg_panel->set_position(Point2()); - bg_panel->set_size(size); + bg_panel->set_size(dlg_size); // Place the buttons from the bottom edge to their minimum required size. Size2 buttons_minsize = buttons_hbox->get_combined_minimum_size(); - Size2 buttons_size = Size2(size.x - h_margins, buttons_minsize.y); - Point2 buttons_position = Point2(theme_cache.panel_style->get_margin(SIDE_LEFT), size.y - theme_cache.panel_style->get_margin(SIDE_BOTTOM) - buttons_size.y); + Size2 buttons_size = Size2(dlg_size.x - h_margins, buttons_minsize.y); + Point2 buttons_position = Point2(theme_cache.panel_style->get_margin(SIDE_LEFT), dlg_size.y - theme_cache.panel_style->get_margin(SIDE_BOTTOM) - buttons_size.y); buttons_hbox->set_position(buttons_position); buttons_hbox->set_size(buttons_size); // Place the content from the top to fill the rest of the space (minus the separation). Point2 content_position = Point2(theme_cache.panel_style->get_margin(SIDE_LEFT), theme_cache.panel_style->get_margin(SIDE_TOP)); - Size2 content_size = Size2(size.x - h_margins, size.y - v_margins - buttons_size.y - theme_cache.buttons_separation); + Size2 content_size = Size2(dlg_size.x - h_margins, dlg_size.y - v_margins - buttons_size.y - theme_cache.buttons_separation); for (int i = 0; i < get_child_count(); i++) { Control *c = Object::cast_to<Control>(get_child(i)); @@ -377,7 +377,7 @@ void AcceptDialog::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::STRING, "ok_button_text"), "set_ok_button_text", "get_ok_button_text"); - ADD_GROUP("Dialog", "dialog"); + ADD_GROUP("Dialog", "dialog_"); ADD_PROPERTY(PropertyInfo(Variant::STRING, "dialog_text", PROPERTY_HINT_MULTILINE_TEXT, "", PROPERTY_USAGE_DEFAULT_INTL), "set_text", "get_text"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "dialog_hide_on_ok"), "set_hide_on_ok", "get_hide_on_ok"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "dialog_close_on_escape"), "set_close_on_escape", "get_close_on_escape"); diff --git a/scene/gui/file_dialog.cpp b/scene/gui/file_dialog.cpp index cf7f439aef..11a3803b35 100644 --- a/scene/gui/file_dialog.cpp +++ b/scene/gui/file_dialog.cpp @@ -632,8 +632,11 @@ void FileDialog::update_file_list() { files.pop_front(); } - if (tree->get_root() && tree->get_root()->get_first_child() && tree->get_selected() == nullptr) { - tree->get_root()->get_first_child()->select(0); + if (mode != FILE_MODE_SAVE_FILE) { + // Select the first file from list if nothing is selected. + if (tree->get_root() && tree->get_root()->get_first_child() && tree->get_selected() == nullptr) { + tree->get_root()->get_first_child()->select(0); + } } } @@ -743,10 +746,10 @@ void FileDialog::set_current_path(const String &p_path) { if (pos == -1) { set_current_file(p_path); } else { - String dir = p_path.substr(0, pos); - String file = p_path.substr(pos + 1, p_path.length()); - set_current_dir(dir); - set_current_file(file); + String path_dir = p_path.substr(0, pos); + String path_file = p_path.substr(pos + 1, p_path.length()); + set_current_dir(path_dir); + set_current_file(path_file); } } diff --git a/scene/gui/flow_container.cpp b/scene/gui/flow_container.cpp index b0d15aa7f4..44c5ec62f8 100644 --- a/scene/gui/flow_container.cpp +++ b/scene/gui/flow_container.cpp @@ -152,6 +152,28 @@ void FlowContainer::_resort() { line_data = lines_data[current_line_idx]; } + // The first child of each line adds the offset caused by the alignment, + // but only if the line doesn't contain a child that expands. + if (child_idx_in_line == 0 && Math::is_equal_approx(line_data.stretch_ratio_total, 0)) { + int alignment_ofs = 0; + switch (alignment) { + case ALIGNMENT_CENTER: + alignment_ofs = line_data.stretch_avail / 2; + break; + case ALIGNMENT_END: + alignment_ofs = line_data.stretch_avail; + break; + default: + break; + } + + if (vertical) { /* VERTICAL */ + ofs.y += alignment_ofs; + } else { /* HORIZONTAL */ + ofs.x += alignment_ofs; + } + } + if (vertical) { /* VERTICAL */ if (child->get_h_size_flags() & (SIZE_FILL | SIZE_SHRINK_CENTER | SIZE_SHRINK_END)) { child_size.width = line_data.min_line_height; @@ -282,6 +304,18 @@ int FlowContainer::get_line_count() const { return cached_line_count; } +void FlowContainer::set_alignment(AlignmentMode p_alignment) { + if (alignment == p_alignment) { + return; + } + alignment = p_alignment; + _resort(); +} + +FlowContainer::AlignmentMode FlowContainer::get_alignment() const { + return alignment; +} + void FlowContainer::set_vertical(bool p_vertical) { ERR_FAIL_COND_MSG(is_fixed, "Can't change orientation of " + get_class() + "."); vertical = p_vertical; @@ -300,8 +334,15 @@ FlowContainer::FlowContainer(bool p_vertical) { void FlowContainer::_bind_methods() { ClassDB::bind_method(D_METHOD("get_line_count"), &FlowContainer::get_line_count); + ClassDB::bind_method(D_METHOD("set_alignment", "alignment"), &FlowContainer::set_alignment); + ClassDB::bind_method(D_METHOD("get_alignment"), &FlowContainer::get_alignment); ClassDB::bind_method(D_METHOD("set_vertical", "vertical"), &FlowContainer::set_vertical); ClassDB::bind_method(D_METHOD("is_vertical"), &FlowContainer::is_vertical); + BIND_ENUM_CONSTANT(ALIGNMENT_BEGIN); + BIND_ENUM_CONSTANT(ALIGNMENT_CENTER); + BIND_ENUM_CONSTANT(ALIGNMENT_END); + + ADD_PROPERTY(PropertyInfo(Variant::INT, "alignment", PROPERTY_HINT_ENUM, "Begin,Center,End"), "set_alignment", "get_alignment"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "vertical"), "set_vertical", "is_vertical"); } diff --git a/scene/gui/flow_container.h b/scene/gui/flow_container.h index 536df27ad6..6a61e9b904 100644 --- a/scene/gui/flow_container.h +++ b/scene/gui/flow_container.h @@ -36,11 +36,19 @@ class FlowContainer : public Container { GDCLASS(FlowContainer, Container); +public: + enum AlignmentMode { + ALIGNMENT_BEGIN, + ALIGNMENT_CENTER, + ALIGNMENT_END + }; + private: int cached_size = 0; int cached_line_count = 0; bool vertical = false; + AlignmentMode alignment = ALIGNMENT_BEGIN; struct ThemeCache { int h_separation = 0; @@ -61,6 +69,9 @@ protected: public: int get_line_count() const; + void set_alignment(AlignmentMode p_alignment); + AlignmentMode get_alignment() const; + void set_vertical(bool p_vertical); bool is_vertical() const; @@ -88,4 +99,6 @@ public: FlowContainer(true) { is_fixed = true; } }; +VARIANT_ENUM_CAST(FlowContainer::AlignmentMode); + #endif // FLOW_CONTAINER_H diff --git a/scene/gui/graph_edit.cpp b/scene/gui/graph_edit.cpp index 7295ab9e9d..40792dd43f 100644 --- a/scene/gui/graph_edit.cpp +++ b/scene/gui/graph_edit.cpp @@ -92,8 +92,8 @@ void GraphEditMinimap::update_minimap() { Rect2 GraphEditMinimap::get_camera_rect() { Vector2 camera_center = _convert_from_graph_position(camera_position + camera_size / 2) + minimap_offset; Vector2 camera_viewport = _convert_from_graph_position(camera_size); - Vector2 camera_position = (camera_center - camera_viewport / 2); - return Rect2(camera_position, camera_viewport); + Vector2 camera_pos = (camera_center - camera_viewport / 2); + return Rect2(camera_pos, camera_viewport); } Vector2 GraphEditMinimap::_get_render_size() { @@ -1385,16 +1385,16 @@ void GraphEdit::gui_input(const Ref<InputEvent> &p_ev) { } if (p_ev->is_pressed()) { - if (p_ev->is_action("ui_graph_duplicate")) { + if (p_ev->is_action("ui_graph_duplicate", true)) { emit_signal(SNAME("duplicate_nodes_request")); accept_event(); - } else if (p_ev->is_action("ui_copy")) { + } else if (p_ev->is_action("ui_copy", true)) { emit_signal(SNAME("copy_nodes_request")); accept_event(); - } else if (p_ev->is_action("ui_paste")) { + } else if (p_ev->is_action("ui_paste", true)) { emit_signal(SNAME("paste_nodes_request")); accept_event(); - } else if (p_ev->is_action("ui_graph_delete")) { + } else if (p_ev->is_action("ui_graph_delete", true)) { TypedArray<StringName> nodes; for (int i = 0; i < get_child_count(); i++) { @@ -1475,11 +1475,9 @@ void GraphEdit::force_connection_drag_end() { } bool GraphEdit::is_node_hover_valid(const StringName &p_from, const int p_from_port, const StringName &p_to, const int p_to_port) { - bool valid; - if (GDVIRTUAL_CALL(_is_node_hover_valid, p_from, p_from_port, p_to, p_to_port, valid)) { - return valid; - } - return true; + bool valid = true; + GDVIRTUAL_CALL(_is_node_hover_valid, p_from, p_from_port, p_to, p_to_port, valid); + return valid; } void GraphEdit::set_panning_scheme(PanningScheme p_scheme) { @@ -2408,7 +2406,7 @@ void GraphEdit::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "zoom_step"), "set_zoom_step", "get_zoom_step"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "show_zoom_label"), "set_show_zoom_label", "is_showing_zoom_label"); - ADD_GROUP("Minimap", "minimap"); + ADD_GROUP("Minimap", "minimap_"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "minimap_enabled"), "set_minimap_enabled", "is_minimap_enabled"); ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "minimap_size", PROPERTY_HINT_NONE, "suffix:px"), "set_minimap_size", "get_minimap_size"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "minimap_opacity"), "set_minimap_opacity", "get_minimap_opacity"); diff --git a/scene/gui/graph_node.cpp b/scene/gui/graph_node.cpp index 21c0b5a842..5df4c066e4 100644 --- a/scene/gui/graph_node.cpp +++ b/scene/gui/graph_node.cpp @@ -294,14 +294,14 @@ void GraphNode::_resort() { bool GraphNode::has_point(const Point2 &p_point) const { if (comment) { - Ref<StyleBox> comment = get_theme_stylebox(SNAME("comment")); + Ref<StyleBox> comment_sb = get_theme_stylebox(SNAME("comment")); Ref<Texture2D> resizer = get_theme_icon(SNAME("resizer")); if (Rect2(get_size() - resizer->get_size(), resizer->get_size()).has_point(p_point)) { return true; } - if (Rect2(0, 0, get_size().width, comment->get_margin(SIDE_TOP)).has_point(p_point)) { + if (Rect2(0, 0, get_size().width, comment_sb->get_margin(SIDE_TOP)).has_point(p_point)) { return true; } diff --git a/scene/gui/item_list.cpp b/scene/gui/item_list.cpp index 357f2480bd..82f089735d 100644 --- a/scene/gui/item_list.cpp +++ b/scene/gui/item_list.cpp @@ -728,12 +728,12 @@ void ItemList::gui_input(const Ref<InputEvent> &p_event) { } if (p_event->is_pressed() && items.size() > 0) { - if (p_event->is_action("ui_up")) { + if (p_event->is_action("ui_up", true)) { if (!search_string.is_empty()) { uint64_t now = OS::get_singleton()->get_ticks_msec(); uint64_t diff = now - search_time_msec; - if (diff < uint64_t(ProjectSettings::get_singleton()->get("gui/timers/incremental_search_max_interval_msec")) * 2) { + if (diff < uint64_t(GLOBAL_GET("gui/timers/incremental_search_max_interval_msec")) * 2) { for (int i = current - 1; i >= 0; i--) { if (CAN_SELECT(i) && items[i].text.begins_with(search_string)) { set_current(i); @@ -766,12 +766,12 @@ void ItemList::gui_input(const Ref<InputEvent> &p_event) { } accept_event(); } - } else if (p_event->is_action("ui_down")) { + } else if (p_event->is_action("ui_down", true)) { if (!search_string.is_empty()) { uint64_t now = OS::get_singleton()->get_ticks_msec(); uint64_t diff = now - search_time_msec; - if (diff < uint64_t(ProjectSettings::get_singleton()->get("gui/timers/incremental_search_max_interval_msec")) * 2) { + if (diff < uint64_t(GLOBAL_GET("gui/timers/incremental_search_max_interval_msec")) * 2) { for (int i = current + 1; i < items.size(); i++) { if (CAN_SELECT(i) && items[i].text.begins_with(search_string)) { set_current(i); @@ -803,7 +803,7 @@ void ItemList::gui_input(const Ref<InputEvent> &p_event) { } accept_event(); } - } else if (p_event->is_action("ui_page_up")) { + } else if (p_event->is_action("ui_page_up", true)) { search_string = ""; //any mousepress cancels for (int i = 4; i > 0; i--) { @@ -817,7 +817,7 @@ void ItemList::gui_input(const Ref<InputEvent> &p_event) { break; } } - } else if (p_event->is_action("ui_page_down")) { + } else if (p_event->is_action("ui_page_down", true)) { search_string = ""; //any mousepress cancels for (int i = 4; i > 0; i--) { @@ -832,7 +832,7 @@ void ItemList::gui_input(const Ref<InputEvent> &p_event) { break; } } - } else if (p_event->is_action("ui_left")) { + } else if (p_event->is_action("ui_left", true)) { search_string = ""; //any mousepress cancels if (current % current_columns != 0) { @@ -852,7 +852,7 @@ void ItemList::gui_input(const Ref<InputEvent> &p_event) { } accept_event(); } - } else if (p_event->is_action("ui_right")) { + } else if (p_event->is_action("ui_right", true)) { search_string = ""; //any mousepress cancels if (current % current_columns != (current_columns - 1) && current + 1 < items.size()) { @@ -872,9 +872,9 @@ void ItemList::gui_input(const Ref<InputEvent> &p_event) { } accept_event(); } - } else if (p_event->is_action("ui_cancel")) { + } else if (p_event->is_action("ui_cancel", true)) { search_string = ""; - } else if (p_event->is_action("ui_select") && select_mode == SELECT_MULTI) { + } else if (p_event->is_action("ui_select", true) && select_mode == SELECT_MULTI) { if (current >= 0 && current < items.size()) { if (items[current].selectable && !items[current].disabled && !items[current].selected) { select(current, false); @@ -884,7 +884,7 @@ void ItemList::gui_input(const Ref<InputEvent> &p_event) { emit_signal(SNAME("multi_selected"), current, false); } } - } else if (p_event->is_action("ui_accept")) { + } else if (p_event->is_action("ui_accept", true)) { search_string = ""; //any mousepress cancels if (current >= 0 && current < items.size()) { @@ -896,7 +896,7 @@ void ItemList::gui_input(const Ref<InputEvent> &p_event) { if (k.is_valid() && k->get_unicode()) { uint64_t now = OS::get_singleton()->get_ticks_msec(); uint64_t diff = now - search_time_msec; - uint64_t max_interval = uint64_t(GLOBAL_DEF("gui/timers/incremental_search_max_interval_msec", 2000)); + uint64_t max_interval = uint64_t(GLOBAL_GET("gui/timers/incremental_search_max_interval_msec")); search_time_msec = now; if (diff > max_interval) { @@ -1296,9 +1296,9 @@ void ItemList::_notification(int p_what) { draw_rect.size = adj.size; } - Color modulate = items[i].icon_modulate; + Color icon_modulate = items[i].icon_modulate; if (items[i].disabled) { - modulate.a *= 0.5; + icon_modulate.a *= 0.5; } // If the icon is transposed, we have to switch the size so that it is drawn correctly @@ -1313,7 +1313,7 @@ void ItemList::_notification(int p_what) { if (rtl) { draw_rect.position.x = size.width - draw_rect.position.x - draw_rect.size.x; } - draw_texture_rect_region(items[i].icon, draw_rect, region, modulate, items[i].icon_transposed); + draw_texture_rect_region(items[i].icon, draw_rect, region, icon_modulate, items[i].icon_transposed); } if (items[i].tag_icon.is_valid()) { @@ -1336,9 +1336,9 @@ void ItemList::_notification(int p_what) { max_len = size2.x; } - Color modulate = items[i].selected ? theme_cache.font_selected_color : (items[i].custom_fg != Color() ? items[i].custom_fg : theme_cache.font_color); + Color txt_modulate = items[i].selected ? theme_cache.font_selected_color : (items[i].custom_fg != Color() ? items[i].custom_fg : theme_cache.font_color); if (items[i].disabled) { - modulate.a *= 0.5; + txt_modulate.a *= 0.5; } if (icon_mode == ICON_MODE_TOP && max_text_lines > 0) { @@ -1355,7 +1355,7 @@ void ItemList::_notification(int p_what) { items[i].text_buf->draw_outline(get_canvas_item(), text_ofs, theme_cache.font_outline_size, theme_cache.font_outline_color); } - items[i].text_buf->draw(get_canvas_item(), text_ofs, modulate); + items[i].text_buf->draw(get_canvas_item(), text_ofs, txt_modulate); } else { if (fixed_column_width > 0) { size2.x = MIN(size2.x, fixed_column_width); @@ -1387,7 +1387,7 @@ void ItemList::_notification(int p_what) { } if (width - text_ofs.x > 0) { - items[i].text_buf->draw(get_canvas_item(), text_ofs, modulate); + items[i].text_buf->draw(get_canvas_item(), text_ofs, txt_modulate); } } } @@ -1831,9 +1831,6 @@ void ItemList::_bind_methods() { 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"))); - - 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 } ItemList::ItemList() { diff --git a/scene/gui/label.cpp b/scene/gui/label.cpp index 306ca3d340..2203573bbc 100644 --- a/scene/gui/label.cpp +++ b/scene/gui/label.cpp @@ -102,12 +102,12 @@ void Label::_shape() { const Ref<Font> &font = (settings.is_valid() && settings->get_font().is_valid()) ? settings->get_font() : theme_cache.font; int font_size = settings.is_valid() ? settings->get_font_size() : theme_cache.font_size; ERR_FAIL_COND(font.is_null()); - String text = (uppercase) ? TS->string_to_upper(xl_text, language) : xl_text; + String txt = (uppercase) ? TS->string_to_upper(xl_text, language) : xl_text; if (visible_chars >= 0 && visible_chars_behavior == TextServer::VC_CHARS_BEFORE_SHAPING) { - text = text.substr(0, visible_chars); + txt = txt.substr(0, visible_chars); } if (dirty) { - TS->shaped_text_add_string(text_rid, text, font->get_rids(), font_size, font->get_opentype_features(), language); + TS->shaped_text_add_string(text_rid, txt, font->get_rids(), font_size, font->get_opentype_features(), language); } else { int spans = TS->shaped_get_span_count(text_rid); for (int i = 0; i < spans; i++) { @@ -117,7 +117,7 @@ void Label::_shape() { for (int i = 0; i < TextServer::SPACING_MAX; i++) { TS->shaped_text_set_spacing(text_rid, TextServer::SpacingType(i), font->get_spacing(TextServer::SpacingType(i))); } - TS->shaped_text_set_bidi_override(text_rid, structured_text_parser(st_parser, st_args, text)); + TS->shaped_text_set_bidi_override(text_rid, structured_text_parser(st_parser, st_args, txt)); dirty = false; font_dirty = false; lines_dirty = true; @@ -288,6 +288,36 @@ void Label::_update_theme_item_cache() { theme_cache.font_shadow_outline_size = get_theme_constant(SNAME("shadow_outline_size")); } +PackedStringArray Label::get_configuration_warnings() const { + PackedStringArray warnings = Control::get_configuration_warnings(); + + // Ensure that the font can render all of the required glyphs. + Ref<Font> font; + if (settings.is_valid()) { + font = settings->get_font(); + } + if (font.is_null()) { + font = theme_cache.font; + } + + if (font.is_valid()) { + if (dirty || font_dirty || lines_dirty) { + const_cast<Label *>(this)->_shape(); + } + + const Glyph *glyph = TS->shaped_text_get_glyphs(text_rid); + int64_t glyph_count = TS->shaped_text_get_glyph_count(text_rid); + for (int64_t i = 0; i < glyph_count; i++) { + if (glyph[i].font_rid == RID()) { + warnings.push_back(RTR("The current font does not support rendering one or more characters used in this Label's text.")); + break; + } + } + } + + return warnings; +} + void Label::_notification(int p_what) { switch (p_what) { case NOTIFICATION_TRANSLATION_CHANGED: { @@ -302,6 +332,7 @@ void Label::_notification(int p_what) { dirty = true; queue_redraw(); + update_configuration_warnings(); } break; case NOTIFICATION_LAYOUT_DIRECTION_CHANGED: { @@ -674,6 +705,7 @@ void Label::set_text(const String &p_string) { } queue_redraw(); update_minimum_size(); + update_configuration_warnings(); } void Label::_invalidate() { diff --git a/scene/gui/label.h b/scene/gui/label.h index b79c94a5ba..41d7049b22 100644 --- a/scene/gui/label.h +++ b/scene/gui/label.h @@ -93,6 +93,7 @@ protected: public: virtual Size2 get_minimum_size() const override; + virtual PackedStringArray get_configuration_warnings() const override; void set_horizontal_alignment(HorizontalAlignment p_alignment); HorizontalAlignment get_horizontal_alignment() const; diff --git a/scene/gui/line_edit.cpp b/scene/gui/line_edit.cpp index be94337c89..8a77c39487 100644 --- a/scene/gui/line_edit.cpp +++ b/scene/gui/line_edit.cpp @@ -372,6 +372,11 @@ void LineEdit::gui_input(const Ref<InputEvent> &p_event) { selection.drag_attempt = false; } + if (pending_select_all_on_focus) { + select_all(); + pending_select_all_on_focus = false; + } + show_virtual_keyboard(); } @@ -955,23 +960,47 @@ void LineEdit::_notification(int p_what) { if (ime_text.length() == 0) { // Normal caret. CaretInfo caret = TS->shaped_text_get_carets(text_rid, caret_column); - - if (caret.l_caret == Rect2() && caret.t_caret == Rect2()) { + if (using_placeholder || (caret.l_caret == Rect2() && caret.t_caret == Rect2())) { // No carets, add one at the start. int h = theme_cache.font->get_height(theme_cache.font_size); int y = style->get_offset().y + (y_area - h) / 2; - if (rtl) { - caret.l_dir = TextServer::DIRECTION_RTL; - caret.l_caret = Rect2(Vector2(ofs_max, y), Size2(caret_width, h)); - } else { - caret.l_dir = TextServer::DIRECTION_LTR; - caret.l_caret = Rect2(Vector2(x_ofs, y), Size2(caret_width, h)); + caret.l_dir = (rtl) ? TextServer::DIRECTION_RTL : TextServer::DIRECTION_LTR; + switch (alignment) { + case HORIZONTAL_ALIGNMENT_FILL: + case HORIZONTAL_ALIGNMENT_LEFT: { + if (rtl) { + caret.l_caret = Rect2(Vector2(ofs_max, y), Size2(caret_width, h)); + } else { + caret.l_caret = Rect2(Vector2(style->get_offset().x, y), Size2(caret_width, h)); + } + } break; + case HORIZONTAL_ALIGNMENT_CENTER: { + caret.l_caret = Rect2(Vector2(size.x / 2, y), Size2(caret_width, h)); + } break; + case HORIZONTAL_ALIGNMENT_RIGHT: { + if (rtl) { + caret.l_caret = Rect2(Vector2(style->get_offset().x, y), Size2(caret_width, h)); + } else { + caret.l_caret = Rect2(Vector2(ofs_max, y), Size2(caret_width, h)); + } + } break; } RenderingServer::get_singleton()->canvas_item_add_rect(ci, caret.l_caret, caret_color); } else { if (caret.l_caret != Rect2() && caret.l_dir == TextServer::DIRECTION_AUTO) { // Draw extra marker on top of mid caret. - Rect2 trect = Rect2(caret.l_caret.position.x - 3 * caret_width, caret.l_caret.position.y, 6 * caret_width, caret_width); + Rect2 trect = Rect2(caret.l_caret.position.x - 2.5 * caret_width, caret.l_caret.position.y, 6 * caret_width, caret_width); + trect.position += ofs; + RenderingServer::get_singleton()->canvas_item_add_rect(ci, trect, caret_color); + } else if (caret.l_caret != Rect2() && caret.t_caret != Rect2() && caret.l_dir != caret.t_dir) { + // Draw extra direction marker on top of split caret. + float d = (caret.l_dir == TextServer::DIRECTION_LTR) ? 0.5 : -3; + Rect2 trect = Rect2(caret.l_caret.position.x + d * caret_width, caret.l_caret.position.y + caret.l_caret.size.y - caret_width, 3 * caret_width, caret_width); + trect.position += ofs; + RenderingServer::get_singleton()->canvas_item_add_rect(ci, trect, caret_color); + + d = (caret.t_dir == TextServer::DIRECTION_LTR) ? 0.5 : -3; + trect = Rect2(caret.t_caret.position.x + d * caret_width, caret.t_caret.position.y, 3 * caret_width, caret_width); trect.position += ofs; RenderingServer::get_singleton()->canvas_item_add_rect(ci, trect, caret_color); } @@ -1045,10 +1074,19 @@ void LineEdit::_notification(int p_what) { } } + if (select_all_on_focus) { + if (Input::get_singleton()->is_mouse_button_pressed(MouseButton::LEFT)) { + // Select all when the mouse button is up. + pending_select_all_on_focus = true; + } else { + select_all(); + } + } + if (get_viewport()->get_window_id() != DisplayServer::INVALID_WINDOW_ID && DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_IME)) { DisplayServer::get_singleton()->window_set_ime_active(true, get_viewport()->get_window_id()); - Point2 caret_column = Point2(get_caret_column(), 1) * get_minimum_size().height; - DisplayServer::get_singleton()->window_set_ime_position(get_global_position() + caret_column, get_viewport()->get_window_id()); + Point2 column = Point2(get_caret_column(), 1) * get_minimum_size().height; + DisplayServer::get_singleton()->window_set_ime_position(get_global_position() + column, get_viewport()->get_window_id()); } show_virtual_keyboard(); @@ -2153,6 +2191,18 @@ bool LineEdit::is_flat() const { return flat; } +void LineEdit::set_select_all_on_focus(bool p_enabled) { + select_all_on_focus = p_enabled; +} + +bool LineEdit::is_select_all_on_focus() const { + return select_all_on_focus; +} + +void LineEdit::clear_pending_select_all_on_focus() { + pending_select_all_on_focus = false; +} + void LineEdit::_text_changed() { _emit_text_change(); _clear_redo(); @@ -2164,6 +2214,12 @@ void LineEdit::_emit_text_change() { } void LineEdit::_shape() { + const Ref<Font> &font = theme_cache.font; + int font_size = theme_cache.font_size; + if (font.is_null()) { + return; + } + Size2 old_size = TS->shaped_text_get_size(text_rid); TS->shaped_text_clear(text_rid); @@ -2186,9 +2242,6 @@ void LineEdit::_shape() { } TS->shaped_text_set_preserve_control(text_rid, draw_control_chars); - const Ref<Font> &font = theme_cache.font; - int font_size = theme_cache.font_size; - ERR_FAIL_COND(font.is_null()); TS->shaped_text_add_string(text_rid, t, font->get_rids(), font_size, font->get_opentype_features(), language); for (int i = 0; i < TextServer::SPACING_MAX; i++) { TS->shaped_text_set_spacing(text_rid, TextServer::SpacingType(i), font->get_spacing(TextServer::SpacingType(i))); @@ -2353,6 +2406,8 @@ void LineEdit::_bind_methods() { ClassDB::bind_method(D_METHOD("get_right_icon"), &LineEdit::get_right_icon); ClassDB::bind_method(D_METHOD("set_flat", "enabled"), &LineEdit::set_flat); ClassDB::bind_method(D_METHOD("is_flat"), &LineEdit::is_flat); + ClassDB::bind_method(D_METHOD("set_select_all_on_focus", "enabled"), &LineEdit::set_select_all_on_focus); + ClassDB::bind_method(D_METHOD("is_select_all_on_focus"), &LineEdit::is_select_all_on_focus); ADD_SIGNAL(MethodInfo("text_changed", PropertyInfo(Variant::STRING, "new_text"))); ADD_SIGNAL(MethodInfo("text_change_rejected", PropertyInfo(Variant::STRING, "rejected_substring"))); @@ -2416,6 +2471,7 @@ void LineEdit::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "right_icon", PROPERTY_HINT_RESOURCE_TYPE, "Texture"), "set_right_icon", "get_right_icon"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "flat"), "set_flat", "is_flat"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "draw_control_chars"), "set_draw_control_chars", "get_draw_control_chars"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "select_all_on_focus"), "set_select_all_on_focus", "is_select_all_on_focus"); ADD_GROUP("Caret", "caret_"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "caret_blink"), "set_caret_blink_enabled", "is_caret_blink_enabled"); diff --git a/scene/gui/line_edit.h b/scene/gui/line_edit.h index 861c7f273b..e0a079b623 100644 --- a/scene/gui/line_edit.h +++ b/scene/gui/line_edit.h @@ -174,6 +174,9 @@ private: double caret_blink_timer = 0.0; bool caret_blinking = false; + bool pending_select_all_on_focus = false; + bool select_all_on_focus = false; + struct ThemeCache { Ref<StyleBox> normal; Ref<StyleBox> read_only; @@ -365,6 +368,10 @@ public: void set_flat(bool p_enabled); bool is_flat() const; + void set_select_all_on_focus(bool p_enabled); + bool is_select_all_on_focus() const; + void clear_pending_select_all_on_focus(); // For other controls, e.g. SpinBox. + virtual bool is_text_field() const override; void show_virtual_keyboard(); diff --git a/scene/gui/menu_bar.cpp b/scene/gui/menu_bar.cpp index 75592a1b99..82ef53e317 100644 --- a/scene/gui/menu_bar.cpp +++ b/scene/gui/menu_bar.cpp @@ -41,7 +41,7 @@ void MenuBar::gui_input(const Ref<InputEvent> &p_event) { } MutexLock lock(mutex); - if (p_event->is_action("ui_left") && p_event->is_pressed()) { + if (p_event->is_action("ui_left", true) && p_event->is_pressed()) { int new_sel = selected_menu; int old_sel = (selected_menu < 0) ? 0 : selected_menu; do { @@ -63,7 +63,7 @@ void MenuBar::gui_input(const Ref<InputEvent> &p_event) { _open_popup(selected_menu, true); } return; - } else if (p_event->is_action("ui_right") && p_event->is_pressed()) { + } else if (p_event->is_action("ui_right", true) && p_event->is_pressed()) { int new_sel = selected_menu; int old_sel = (selected_menu < 0) ? menu_cache.size() - 1 : selected_menu; do { @@ -149,10 +149,6 @@ void MenuBar::_open_popup(int p_index, bool p_focus_item) { void MenuBar::shortcut_input(const Ref<InputEvent> &p_event) { ERR_FAIL_COND(p_event.is_null()); - if (!_is_focus_owner_in_shortcut_context()) { - return; - } - if (disable_shortcuts) { return; } @@ -175,34 +171,6 @@ void MenuBar::shortcut_input(const Ref<InputEvent> &p_event) { } } -void MenuBar::set_shortcut_context(Node *p_node) { - if (p_node != nullptr) { - shortcut_context = p_node->get_instance_id(); - } else { - shortcut_context = ObjectID(); - } -} - -Node *MenuBar::get_shortcut_context() const { - Object *ctx_obj = ObjectDB::get_instance(shortcut_context); - Node *ctx_node = Object::cast_to<Node>(ctx_obj); - - return ctx_node; -} - -bool MenuBar::_is_focus_owner_in_shortcut_context() const { - if (shortcut_context == ObjectID()) { - // No context, therefore global - always "in" context. - return true; - } - - Node *ctx_node = get_shortcut_context(); - Control *vp_focus = get_viewport() ? get_viewport()->gui_get_focus_owner() : nullptr; - - // If the context is valid and the viewport focus is valid, check if the context is the focus or is a parent of it. - return ctx_node && vp_focus && (ctx_node == vp_focus || ctx_node->is_ancestor_of(vp_focus)); -} - void MenuBar::_popup_visibility_changed(bool p_visible) { if (!p_visible) { active_menu = -1; @@ -213,19 +181,19 @@ void MenuBar::_popup_visibility_changed(bool p_visible) { } if (switch_on_hover) { - Window *window = Object::cast_to<Window>(get_viewport()); - if (window) { - mouse_pos_adjusted = window->get_position(); - - if (window->is_embedded()) { - Window *window_parent = Object::cast_to<Window>(window->get_parent()->get_viewport()); - while (window_parent) { - if (!window_parent->is_embedded()) { - mouse_pos_adjusted += window_parent->get_position(); + Window *wnd = Object::cast_to<Window>(get_viewport()); + if (wnd) { + mouse_pos_adjusted = wnd->get_position(); + + if (wnd->is_embedded()) { + Window *wnd_parent = Object::cast_to<Window>(wnd->get_parent()->get_viewport()); + while (wnd_parent) { + if (!wnd_parent->is_embedded()) { + mouse_pos_adjusted += wnd_parent->get_position(); break; } - window_parent = Object::cast_to<Window>(window_parent->get_parent()->get_viewport()); + wnd_parent = Object::cast_to<Window>(wnd_parent->get_parent()->get_viewport()); } } @@ -694,16 +662,12 @@ void MenuBar::_bind_methods() { ClassDB::bind_method(D_METHOD("set_menu_hidden", "menu", "hidden"), &MenuBar::set_menu_hidden); ClassDB::bind_method(D_METHOD("is_menu_hidden", "menu"), &MenuBar::is_menu_hidden); - ClassDB::bind_method(D_METHOD("set_shortcut_context", "node"), &MenuBar::set_shortcut_context); - ClassDB::bind_method(D_METHOD("get_shortcut_context"), &MenuBar::get_shortcut_context); - ClassDB::bind_method(D_METHOD("get_menu_popup", "menu"), &MenuBar::get_menu_popup); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "flat"), "set_flat", "is_flat"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "start_index"), "set_start_index", "get_start_index"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "switch_on_hover"), "set_switch_on_hover", "is_switch_on_hover"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "prefer_global_menu"), "set_prefer_global_menu", "is_prefer_global_menu"); - ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "shortcut_context", PROPERTY_HINT_NODE_TYPE, "Node"), "set_shortcut_context", "get_shortcut_context"); ADD_GROUP("BiDi", ""); ADD_PROPERTY(PropertyInfo(Variant::INT, "text_direction", PROPERTY_HINT_ENUM, "Auto,Left-to-Right,Right-to-Left,Inherited"), "set_text_direction", "get_text_direction"); diff --git a/scene/gui/menu_bar.h b/scene/gui/menu_bar.h index f7ef19e98b..c057a7c96f 100644 --- a/scene/gui/menu_bar.h +++ b/scene/gui/menu_bar.h @@ -118,8 +118,6 @@ class MenuBar : public Control { void _clear_menu(); void _update_menu(); - bool _is_focus_owner_in_shortcut_context() const; - protected: virtual void shortcut_input(const Ref<InputEvent> &p_event) override; @@ -170,9 +168,6 @@ public: void set_menu_hidden(int p_menu, bool p_hidden); bool is_menu_hidden(int p_menu) const; - void set_shortcut_context(Node *p_node); - Node *get_shortcut_context() const; - PopupMenu *get_menu_popup(int p_menu) const; virtual void get_translatable_strings(List<String> *p_strings) const override; diff --git a/scene/gui/menu_button.cpp b/scene/gui/menu_button.cpp index fa8df48412..786f23873e 100644 --- a/scene/gui/menu_button.cpp +++ b/scene/gui/menu_button.cpp @@ -36,10 +36,6 @@ void MenuButton::shortcut_input(const Ref<InputEvent> &p_event) { ERR_FAIL_COND(p_event.is_null()); - if (!_is_focus_owner_in_shortcut_context()) { - return; - } - if (disable_shortcuts) { return; } @@ -61,19 +57,19 @@ void MenuButton::_popup_visibility_changed(bool p_visible) { } if (switch_on_hover) { - Window *window = Object::cast_to<Window>(get_viewport()); - if (window) { - mouse_pos_adjusted = window->get_position(); - - if (window->is_embedded()) { - Window *window_parent = Object::cast_to<Window>(window->get_parent()->get_viewport()); - while (window_parent) { - if (!window_parent->is_embedded()) { - mouse_pos_adjusted += window_parent->get_position(); + Window *wnd = Object::cast_to<Window>(get_viewport()); + if (wnd) { + mouse_pos_adjusted = wnd->get_position(); + + if (wnd->is_embedded()) { + Window *wnd_parent = Object::cast_to<Window>(wnd->get_parent()->get_viewport()); + while (wnd_parent) { + if (!wnd_parent->is_embedded()) { + mouse_pos_adjusted += wnd_parent->get_position(); break; } - window_parent = Object::cast_to<Window>(window_parent->get_parent()->get_viewport()); + wnd_parent = Object::cast_to<Window>(wnd_parent->get_parent()->get_viewport()); } } diff --git a/scene/gui/option_button.cpp b/scene/gui/option_button.cpp index 2cbece69f2..6d0bbdd6af 100644 --- a/scene/gui/option_button.cpp +++ b/scene/gui/option_button.cpp @@ -451,7 +451,7 @@ void OptionButton::_queue_refresh_cache() { } cache_refresh_pending = true; - callable_mp(this, &OptionButton::_refresh_size_cache).call_deferredp(nullptr, 0); + callable_mp(this, &OptionButton::_refresh_size_cache).call_deferred(); } void OptionButton::select(int p_idx) { @@ -491,9 +491,9 @@ void OptionButton::show_popup() { return; } - Size2 size = get_size() * get_viewport()->get_canvas_transform().get_scale(); - popup->set_position(get_screen_position() + Size2(0, size.height * get_global_transform().get_scale().y)); - popup->set_size(Size2(size.width, 0)); + Size2 button_size = get_global_transform_with_canvas().get_scale() * get_size(); + popup->set_position(get_screen_position() + Size2(0, button_size.height)); + popup->set_size(Size2i(button_size.width, 0)); // If not triggered by the mouse, start the popup with the checked item (or the first enabled one) focused. if (current != NONE_SELECTED && !popup->is_item_disabled(current)) { diff --git a/scene/gui/popup.cpp b/scene/gui/popup.cpp index ceae3791f3..434eb87e7a 100644 --- a/scene/gui/popup.cpp +++ b/scene/gui/popup.cpp @@ -77,28 +77,36 @@ void Popup::_update_theme_item_cache() { void Popup::_notification(int p_what) { switch (p_what) { case NOTIFICATION_VISIBILITY_CHANGED: { - if (is_visible()) { - _initialize_visible_parents(); - } else { - _deinitialize_visible_parents(); - emit_signal(SNAME("popup_hide")); - popped_up = false; + if (!is_in_edited_scene_root()) { + if (is_visible()) { + _initialize_visible_parents(); + } else { + _deinitialize_visible_parents(); + emit_signal(SNAME("popup_hide")); + popped_up = false; + } } } break; case NOTIFICATION_WM_WINDOW_FOCUS_IN: { - if (has_focus()) { - popped_up = true; + if (!is_in_edited_scene_root()) { + if (has_focus()) { + popped_up = true; + } } } break; case NOTIFICATION_EXIT_TREE: { - _deinitialize_visible_parents(); + if (!is_in_edited_scene_root()) { + _deinitialize_visible_parents(); + } } break; case NOTIFICATION_WM_CLOSE_REQUEST: case NOTIFICATION_APPLICATION_FOCUS_OUT: { - _close_pressed(); + if (!is_in_edited_scene_root()) { + _close_pressed(); + } } break; } } @@ -126,52 +134,62 @@ void Popup::_bind_methods() { ADD_SIGNAL(MethodInfo("popup_hide")); } +void Popup::_validate_property(PropertyInfo &p_property) const { + if ( + p_property.name == "transient" || + p_property.name == "exclusive" || + p_property.name == "popup_window" || + p_property.name == "unfocusable") { + p_property.usage = PROPERTY_USAGE_NO_EDITOR; + } +} + Rect2i Popup::_popup_adjust_rect() const { ERR_FAIL_COND_V(!is_inside_tree(), Rect2()); - Rect2i parent = get_usable_parent_rect(); + Rect2i parent_rect = get_usable_parent_rect(); - if (parent == Rect2i()) { + if (parent_rect == Rect2i()) { return Rect2i(); } Rect2i current(get_position(), get_size()); - if (current.position.x + current.size.x > parent.position.x + parent.size.x) { - current.position.x = parent.position.x + parent.size.x - current.size.x; + if (current.position.x + current.size.x > parent_rect.position.x + parent_rect.size.x) { + current.position.x = parent_rect.position.x + parent_rect.size.x - current.size.x; } - if (current.position.x < parent.position.x) { - current.position.x = parent.position.x; + if (current.position.x < parent_rect.position.x) { + current.position.x = parent_rect.position.x; } - if (current.position.y + current.size.y > parent.position.y + parent.size.y) { - current.position.y = parent.position.y + parent.size.y - current.size.y; + if (current.position.y + current.size.y > parent_rect.position.y + parent_rect.size.y) { + current.position.y = parent_rect.position.y + parent_rect.size.y - current.size.y; } - if (current.position.y < parent.position.y) { - current.position.y = parent.position.y; + if (current.position.y < parent_rect.position.y) { + current.position.y = parent_rect.position.y; } - if (current.size.y > parent.size.y) { - current.size.y = parent.size.y; + if (current.size.y > parent_rect.size.y) { + current.size.y = parent_rect.size.y; } - if (current.size.x > parent.size.x) { - current.size.x = parent.size.x; + if (current.size.x > parent_rect.size.x) { + current.size.x = parent_rect.size.x; } // Early out if max size not set. - Size2i max_size = get_max_size(); - if (max_size <= Size2()) { + Size2i popup_max_size = get_max_size(); + if (popup_max_size <= Size2()) { return current; } - if (current.size.x > max_size.x) { - current.size.x = max_size.x; + if (current.size.x > popup_max_size.x) { + current.size.x = popup_max_size.x; } - if (current.size.y > max_size.y) { - current.size.y = max_size.y; + if (current.size.y > popup_max_size.y) { + current.size.y = popup_max_size.y; } return current; diff --git a/scene/gui/popup.h b/scene/gui/popup.h index 0d6ca25c18..57b811cadb 100644 --- a/scene/gui/popup.h +++ b/scene/gui/popup.h @@ -59,6 +59,7 @@ protected: virtual void _update_theme_item_cache() override; void _notification(int p_what); static void _bind_methods(); + void _validate_property(PropertyInfo &p_property) const; virtual void _parent_focused(); diff --git a/scene/gui/popup_menu.cpp b/scene/gui/popup_menu.cpp index 10e13042a7..ab74979777 100644 --- a/scene/gui/popup_menu.cpp +++ b/scene/gui/popup_menu.cpp @@ -58,20 +58,20 @@ Size2 PopupMenu::_get_contents_minimum_size() const { bool has_check = false; for (int i = 0; i < items.size(); i++) { - Size2 size; + Size2 item_size; Size2 icon_size = items[i].get_icon_size(); - size.height = _get_item_height(i); + item_size.height = _get_item_height(i); icon_w = MAX(icon_size.width, icon_w); - size.width += items[i].indent * theme_cache.indent; + item_size.width += items[i].indent * theme_cache.indent; if (items[i].checkable_type && !items[i].separator) { has_check = true; } - size.width += items[i].text_buf->get_size().x; - size.height += theme_cache.v_separation; + item_size.width += items[i].text_buf->get_size().x; + item_size.height += theme_cache.v_separation; if (items[i].accel != Key::NONE || (items[i].shortcut.is_valid() && items[i].shortcut->has_valid_event())) { int accel_w = theme_cache.h_separation * 2; @@ -80,12 +80,12 @@ Size2 PopupMenu::_get_contents_minimum_size() const { } if (!items[i].submenu.is_empty()) { - size.width += theme_cache.submenu->get_width(); + item_size.width += theme_cache.submenu->get_width(); } - max_w = MAX(max_w, size.width); + max_w = MAX(max_w, item_size.width); - minsize.height += size.height; + minsize.height += item_size.height; } int item_side_padding = theme_cache.item_start_padding + theme_cache.item_end_padding; @@ -273,7 +273,7 @@ void PopupMenu::gui_input(const Ref<InputEvent> &p_event) { ERR_FAIL_COND(p_event.is_null()); if (!items.is_empty()) { - if (p_event->is_action("ui_down") && p_event->is_pressed()) { + if (p_event->is_action("ui_down", true) && p_event->is_pressed()) { int search_from = mouse_over + 1; if (search_from >= items.size()) { search_from = 0; @@ -305,7 +305,7 @@ void PopupMenu::gui_input(const Ref<InputEvent> &p_event) { } } } - } else if (p_event->is_action("ui_up") && p_event->is_pressed()) { + } else if (p_event->is_action("ui_up", true) && p_event->is_pressed()) { int search_from = mouse_over - 1; if (search_from < 0) { search_from = items.size() - 1; @@ -337,7 +337,7 @@ void PopupMenu::gui_input(const Ref<InputEvent> &p_event) { } } } - } else if (p_event->is_action("ui_left") && p_event->is_pressed()) { + } else if (p_event->is_action("ui_left", true) && p_event->is_pressed()) { Node *n = get_parent(); if (n) { if (Object::cast_to<PopupMenu>(n)) { @@ -349,7 +349,7 @@ void PopupMenu::gui_input(const Ref<InputEvent> &p_event) { return; } } - } else if (p_event->is_action("ui_right") && p_event->is_pressed()) { + } else if (p_event->is_action("ui_right", true) && 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, true); set_input_as_handled(); @@ -361,7 +361,7 @@ void PopupMenu::gui_input(const Ref<InputEvent> &p_event) { return; } } - } else if (p_event->is_action("ui_accept") && p_event->is_pressed()) { + } else if (p_event->is_action("ui_accept", true) && 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, true); @@ -472,7 +472,7 @@ void PopupMenu::gui_input(const Ref<InputEvent> &p_event) { if (allow_search && k.is_valid() && k->get_unicode() && k->is_pressed()) { uint64_t now = OS::get_singleton()->get_ticks_msec(); uint64_t diff = now - search_time_msec; - uint64_t max_interval = uint64_t(GLOBAL_DEF("gui/timers/incremental_search_max_interval_msec", 2000)); + uint64_t max_interval = uint64_t(GLOBAL_GET("gui/timers/incremental_search_max_interval_msec")); search_time_msec = now; if (diff > max_interval) { @@ -558,7 +558,7 @@ void PopupMenu::_draw_items() { check_ofs += theme_cache.h_separation; } - Point2 ofs = Point2(); + Point2 ofs; // Loop through all items and draw each. for (int i = 0; i < items.size(); i++) { diff --git a/scene/gui/progress_bar.cpp b/scene/gui/progress_bar.cpp index 8369eaa227..50bcfa6a0c 100644 --- a/scene/gui/progress_bar.cpp +++ b/scene/gui/progress_bar.cpp @@ -103,7 +103,12 @@ void ProgressBar::_notification(int p_what) { } if (show_percentage) { - String txt = TS->format_number(itos(int(get_as_ratio() * 100))) + TS->percent_sign(); + String txt = itos(int(get_as_ratio() * 100)); + if (is_localizing_numeral_system()) { + txt = TS->format_number(txt) + TS->percent_sign(); + } else { + txt += String("%"); + } TextLine tl = TextLine(txt, theme_cache.font, theme_cache.font_size); Vector2 text_pos = (Point2(get_size().width - tl.get_size().x, get_size().height - tl.get_size().y) / 2).round(); diff --git a/scene/gui/range.cpp b/scene/gui/range.cpp index 2d2b3e413d..27002fad38 100644 --- a/scene/gui/range.cpp +++ b/scene/gui/range.cpp @@ -80,6 +80,15 @@ void Range::Shared::emit_changed(const char *p_what) { } void Range::set_value(double p_val) { + double prev_val = shared->val; + set_value_no_signal(p_val); + + if (shared->val != prev_val) { + shared->emit_value_changed(); + } +} + +void Range::set_value_no_signal(double p_val) { if (shared->step > 0) { p_val = Math::round(p_val / shared->step) * shared->step; } @@ -101,8 +110,6 @@ void Range::set_value(double p_val) { } shared->val = p_val; - - shared->emit_value_changed(); } void Range::set_min(double p_min) { @@ -267,6 +274,7 @@ void Range::_bind_methods() { ClassDB::bind_method(D_METHOD("get_page"), &Range::get_page); ClassDB::bind_method(D_METHOD("get_as_ratio"), &Range::get_as_ratio); ClassDB::bind_method(D_METHOD("set_value", "value"), &Range::set_value); + ClassDB::bind_method(D_METHOD("set_value_no_signal", "value"), &Range::set_value_no_signal); ClassDB::bind_method(D_METHOD("set_min", "minimum"), &Range::set_min); ClassDB::bind_method(D_METHOD("set_max", "maximum"), &Range::set_max); ClassDB::bind_method(D_METHOD("set_step", "step"), &Range::set_step); diff --git a/scene/gui/range.h b/scene/gui/range.h index 19452243cf..f804155dec 100644 --- a/scene/gui/range.h +++ b/scene/gui/range.h @@ -72,6 +72,7 @@ protected: public: void set_value(double p_val); + void set_value_no_signal(double p_val); void set_min(double p_min); void set_max(double p_max); void set_step(double p_step); diff --git a/scene/gui/rich_text_effect.cpp b/scene/gui/rich_text_effect.cpp index c9516ed6b9..0dece1c287 100644 --- a/scene/gui/rich_text_effect.cpp +++ b/scene/gui/rich_text_effect.cpp @@ -56,10 +56,8 @@ Variant RichTextEffect::get_bbcode() const { bool RichTextEffect::_process_effect_impl(Ref<CharFXTransform> p_cfx) { bool return_value = false; - if (GDVIRTUAL_CALL(_process_custom_fx, p_cfx, return_value)) { - return return_value; - } - return false; + GDVIRTUAL_CALL(_process_custom_fx, p_cfx, return_value); + return return_value; } RichTextEffect::RichTextEffect() { diff --git a/scene/gui/rich_text_label.cpp b/scene/gui/rich_text_label.cpp index 7ea46a0b4f..642a94b23e 100644 --- a/scene/gui/rich_text_label.cpp +++ b/scene/gui/rich_text_label.cpp @@ -167,17 +167,17 @@ String RichTextLabel::_roman(int p_num, bool p_capitalize) const { }; String s; if (p_capitalize) { - const String M[] = { "", "M", "MM", "MMM" }; - const String C[] = { "", "C", "CC", "CCC", "CD", "D", "DC", "DCC", "DCCC", "CM" }; - const String X[] = { "", "X", "XX", "XXX", "XL", "L", "LX", "LXX", "LXXX", "XC" }; - const String I[] = { "", "I", "II", "III", "IV", "V", "VI", "VII", "VIII", "IX" }; - s = M[p_num / 1000] + C[(p_num % 1000) / 100] + X[(p_num % 100) / 10] + I[p_num % 10]; + const String roman_M[] = { "", "M", "MM", "MMM" }; + const String roman_C[] = { "", "C", "CC", "CCC", "CD", "D", "DC", "DCC", "DCCC", "CM" }; + const String roman_X[] = { "", "X", "XX", "XXX", "XL", "L", "LX", "LXX", "LXXX", "XC" }; + const String roman_I[] = { "", "I", "II", "III", "IV", "V", "VI", "VII", "VIII", "IX" }; + s = roman_M[p_num / 1000] + roman_C[(p_num % 1000) / 100] + roman_X[(p_num % 100) / 10] + roman_I[p_num % 10]; } else { - const String M[] = { "", "m", "mm", "mmm" }; - const String C[] = { "", "c", "cc", "ccc", "cd", "d", "dc", "dcc", "dccc", "cm" }; - const String X[] = { "", "x", "xx", "xxx", "xl", "l", "lx", "lxx", "lxxx", "xc" }; - const String I[] = { "", "i", "ii", "iii", "iv", "v", "vi", "vii", "viii", "ix" }; - s = M[p_num / 1000] + C[(p_num % 1000) / 100] + X[(p_num % 100) / 10] + I[p_num % 10]; + const String roman_M[] = { "", "m", "mm", "mmm" }; + const String roman_C[] = { "", "c", "cc", "ccc", "cd", "d", "dc", "dcc", "dccc", "cm" }; + const String roman_X[] = { "", "x", "xx", "xxx", "xl", "l", "lx", "lxx", "lxxx", "xc" }; + const String roman_I[] = { "", "i", "ii", "iii", "iv", "v", "vi", "vii", "viii", "ix" }; + s = roman_M[p_num / 1000] + roman_C[(p_num % 1000) / 100] + roman_X[(p_num % 100) / 10] + roman_I[p_num % 10]; } return s; } @@ -468,7 +468,7 @@ float RichTextLabel::_shape_line(ItemFrame *p_frame, int p_line, const Ref<Font> } // Shape current paragraph. - String text; + String txt; Item *it_to = (p_line + 1 < (int)p_frame->lines.size()) ? p_frame->lines[p_line + 1].from : nullptr; int remaining_characters = visible_characters - l.char_offset; for (Item *it = l.from; it && it != it_to; it = _get_next_item(it)) { @@ -502,7 +502,7 @@ float RichTextLabel::_shape_line(ItemFrame *p_frame, int p_line, const Ref<Font> font_size = font_size_it->font_size; } l.text_buf->add_string("\n", font, font_size); - text += "\n"; + txt += "\n"; l.char_count++; remaining_characters--; } break; @@ -532,13 +532,13 @@ float RichTextLabel::_shape_line(ItemFrame *p_frame, int p_line, const Ref<Font> remaining_characters -= tx.length(); l.text_buf->add_string(tx, font, font_size, lang, (uint64_t)it); - text += tx; + txt += tx; l.char_count += tx.length(); } break; case ITEM_IMAGE: { ItemImage *img = static_cast<ItemImage *>(it); l.text_buf->add_object((uint64_t)it, img->size, img->inline_align, 1); - text += String::chr(0xfffc); + txt += String::chr(0xfffc); l.char_count++; remaining_characters--; } break; @@ -693,15 +693,15 @@ float RichTextLabel::_shape_line(ItemFrame *p_frame, int p_line, const Ref<Font> } l.text_buf->add_object((uint64_t)it, Size2(table->total_width, table->total_height), table->inline_align, t_char_count); - text += String::chr(0xfffc).repeat(t_char_count); + txt += String::chr(0xfffc).repeat(t_char_count); } break; default: break; } } - //Apply BiDi override. - l.text_buf->set_bidi_override(structured_text_parser(_find_stt(l.from), st_args, text)); + // Apply BiDi override. + l.text_buf->set_bidi_override(structured_text_parser(_find_stt(l.from), st_args, txt)); *r_char_offset = l.char_offset + l.char_count; @@ -752,7 +752,10 @@ int RichTextLabel::_draw_line(ItemFrame *p_frame, int p_line, const Vector2 &p_o prefix = _prefix; break; } else if (list_items[i]->list_type == LIST_NUMBERS) { - segment = TS->format_number(itos(list_index[i]), _find_language(l.from)); + segment = itos(list_index[i]); + if (is_localizing_numeral_system()) { + segment = TS->format_number(segment, _find_language(l.from)); + } } else if (list_items[i]->list_type == LIST_LETTERS) { segment = _letters(list_index[i], list_items[i]->capitalize); } else if (list_items[i]->list_type == LIST_ROMAN) { @@ -987,7 +990,7 @@ int RichTextLabel::_draw_line(ItemFrame *p_frame, int p_line, const Vector2 &p_o font_shadow_color.a = faded_visibility; } - bool visible = (font_outline_color.a != 0) || (font_shadow_color.a != 0); + bool txt_visible = (font_outline_color.a != 0) || (font_shadow_color.a != 0); for (int j = 0; j < fx_stack.size(); j++) { ItemFX *item_fx = fx_stack[j]; @@ -1002,7 +1005,7 @@ int RichTextLabel::_draw_line(ItemFrame *p_frame, int p_line, const Vector2 &p_o if (!custom_effect.is_null()) { charfx->elapsed_time = item_custom->elapsed_time; charfx->range = Vector2i(l.char_offset + glyphs[i].start, l.char_offset + glyphs[i].end); - charfx->visibility = visible; + charfx->visibility = txt_visible; charfx->outline = true; charfx->font = frid; charfx->glyph_index = gl; @@ -1018,7 +1021,7 @@ int RichTextLabel::_draw_line(ItemFrame *p_frame, int p_line, const Vector2 &p_o font_color = charfx->color; frid = charfx->font; gl = charfx->glyph_index; - visible &= charfx->visibility; + txt_visible &= charfx->visibility; } } else if (item_fx->type == ITEM_SHAKE) { ItemShake *item_shake = static_cast<ItemShake *>(item_fx); @@ -1062,7 +1065,7 @@ int RichTextLabel::_draw_line(ItemFrame *p_frame, int p_line, const Vector2 &p_o const Color modulated_outline_color = font_outline_color * Color(1, 1, 1, font_color.a); const Color modulated_shadow_color = font_shadow_color * Color(1, 1, 1, font_color.a); for (int j = 0; j < glyphs[i].repeat; j++) { - if (visible) { + if (txt_visible) { bool skip = (trim_chars && l.char_offset + glyphs[i].end > visible_characters) || (trim_glyphs_ltr && (processed_glyphs_ol >= visible_glyphs)) || (trim_glyphs_rtl && (processed_glyphs_ol < total_glyphs - visible_glyphs)); if (!skip && frid != RID()) { if (modulated_shadow_color.a > 0) { @@ -1087,7 +1090,6 @@ int RichTextLabel::_draw_line(ItemFrame *p_frame, int p_line, const Vector2 &p_o _draw_fbg_boxes(ci, rid, fbg_line_off, it_from, it_to, chr_range.x, chr_range.y, 0); // Draw main text. - Color selection_fg = theme_cache.font_selected_color; Color selection_bg = theme_cache.selection_color; int sel_start = -1; @@ -1205,7 +1207,7 @@ int RichTextLabel::_draw_line(ItemFrame *p_frame, int p_line, const Vector2 &p_o font_color.a = faded_visibility; } - bool visible = (font_color.a != 0); + bool txt_visible = (font_color.a != 0); for (int j = 0; j < fx_stack.size(); j++) { ItemFX *item_fx = fx_stack[j]; @@ -1220,7 +1222,7 @@ int RichTextLabel::_draw_line(ItemFrame *p_frame, int p_line, const Vector2 &p_o if (!custom_effect.is_null()) { charfx->elapsed_time = item_custom->elapsed_time; charfx->range = Vector2i(l.char_offset + glyphs[i].start, l.char_offset + glyphs[i].end); - charfx->visibility = visible; + charfx->visibility = txt_visible; charfx->outline = false; charfx->font = frid; charfx->glyph_index = gl; @@ -1236,7 +1238,7 @@ int RichTextLabel::_draw_line(ItemFrame *p_frame, int p_line, const Vector2 &p_o font_color = charfx->color; frid = charfx->font; gl = charfx->glyph_index; - visible &= charfx->visibility; + txt_visible &= charfx->visibility; } } else if (item_fx->type == ITEM_SHAKE) { ItemShake *item_shake = static_cast<ItemShake *>(item_fx); @@ -1276,19 +1278,19 @@ int RichTextLabel::_draw_line(ItemFrame *p_frame, int p_line, const Vector2 &p_o } } - if (selected) { - font_color = override_selected_font_color ? selection_fg : font_color; + if (selected && use_selected_font_color) { + font_color = theme_cache.font_selected_color; } // Draw glyphs. for (int j = 0; j < glyphs[i].repeat; j++) { bool skip = (trim_chars && l.char_offset + glyphs[i].end > visible_characters) || (trim_glyphs_ltr && (r_processed_glyphs >= visible_glyphs)) || (trim_glyphs_rtl && (r_processed_glyphs < total_glyphs - visible_glyphs)); - if (visible) { + if (txt_visible) { if (!skip) { if (frid != RID()) { - TS->font_draw_glyph(frid, ci, glyphs[i].font_size, p_ofs + fx_offset + off, gl, selected ? selection_fg : font_color); + TS->font_draw_glyph(frid, ci, glyphs[i].font_size, p_ofs + fx_offset + off, gl, font_color); } else if ((glyphs[i].flags & TextServer::GRAPHEME_IS_VIRTUAL) != TextServer::GRAPHEME_IS_VIRTUAL) { - TS->draw_hex_code_box(ci, glyphs[i].font_size, p_ofs + fx_offset + off, gl, selected ? selection_fg : font_color); + TS->draw_hex_code_box(ci, glyphs[i].font_size, p_ofs + fx_offset + off, gl, font_color); } } r_processed_glyphs++; @@ -1688,6 +1690,7 @@ void RichTextLabel::_update_theme_item_cache() { theme_cache.default_color = get_theme_color(SNAME("default_color")); theme_cache.font_selected_color = get_theme_color(SNAME("font_selected_color")); + use_selected_font_color = theme_cache.font_selected_color != Color(0, 0, 0, 0); theme_cache.selection_color = get_theme_color(SNAME("selection_color")); theme_cache.font_outline_color = get_theme_color(SNAME("font_outline_color")); theme_cache.font_shadow_color = get_theme_color(SNAME("font_shadow_color")); @@ -2019,36 +2022,36 @@ void RichTextLabel::gui_input(const Ref<InputEvent> &p_event) { if (k->is_pressed()) { bool handled = false; - if (k->is_action("ui_page_up") && vscroll->is_visible_in_tree()) { + if (k->is_action("ui_page_up", true) && vscroll->is_visible_in_tree()) { vscroll->set_value(vscroll->get_value() - vscroll->get_page()); handled = true; } - if (k->is_action("ui_page_down") && vscroll->is_visible_in_tree()) { + if (k->is_action("ui_page_down", true) && vscroll->is_visible_in_tree()) { vscroll->set_value(vscroll->get_value() + vscroll->get_page()); handled = true; } - if (k->is_action("ui_up") && vscroll->is_visible_in_tree()) { + if (k->is_action("ui_up", true) && vscroll->is_visible_in_tree()) { vscroll->set_value(vscroll->get_value() - theme_cache.normal_font->get_height(theme_cache.normal_font_size)); handled = true; } - if (k->is_action("ui_down") && vscroll->is_visible_in_tree()) { + if (k->is_action("ui_down", true) && vscroll->is_visible_in_tree()) { vscroll->set_value(vscroll->get_value() + theme_cache.normal_font->get_height(theme_cache.normal_font_size)); handled = true; } - if (k->is_action("ui_home") && vscroll->is_visible_in_tree()) { + if (k->is_action("ui_home", true) && vscroll->is_visible_in_tree()) { vscroll->set_value(0); handled = true; } - if (k->is_action("ui_end") && vscroll->is_visible_in_tree()) { + if (k->is_action("ui_end", true) && vscroll->is_visible_in_tree()) { vscroll->set_value(vscroll->get_max()); handled = true; } if (is_shortcut_keys_enabled()) { - if (k->is_action("ui_text_select_all")) { + if (k->is_action("ui_text_select_all", true)) { select_all(); handled = true; } - if (k->is_action("ui_copy")) { + if (k->is_action("ui_copy", true)) { selection_copy(); handled = true; } @@ -2686,6 +2689,7 @@ bool RichTextLabel::_validate_line_caches() { int ctrl_height = get_size().height; // Update fonts. + float old_scroll = vscroll->get_value(); if (main->first_invalid_font_line.load() != (int)main->lines.size()) { for (int i = main->first_invalid_font_line.load(); i < (int)main->lines.size(); i++) { _update_line_font(main, i, theme_cache.normal_font, theme_cache.normal_font_size); @@ -2695,6 +2699,7 @@ bool RichTextLabel::_validate_line_caches() { } if (main->first_resized_line.load() == (int)main->lines.size()) { + vscroll->set_value(old_scroll); return true; } @@ -2733,6 +2738,8 @@ bool RichTextLabel::_validate_line_caches() { vscroll->set_page(text_rect.size.height); if (scroll_follow && scroll_following) { vscroll->set_value(total_height); + } else { + vscroll->set_value(old_scroll); } updating_scroll = false; @@ -2956,7 +2963,7 @@ void RichTextLabel::_remove_item(Item *p_item, const int p_line, const int p_sub memdelete(p_item); } -void RichTextLabel::add_image(const Ref<Texture2D> &p_image, const int p_width, const int p_height, const Color &p_color, InlineAlignment p_alignment) { +void RichTextLabel::add_image(const Ref<Texture2D> &p_image, const int p_width, const int p_height, const Color &p_color, InlineAlignment p_alignment, const Rect2 &p_region) { _stop_thread(); MutexLock data_lock(data_mutex); @@ -2969,7 +2976,15 @@ void RichTextLabel::add_image(const Ref<Texture2D> &p_image, const int p_width, ERR_FAIL_COND(p_image->get_height() == 0); ItemImage *item = memnew(ItemImage); - item->image = p_image; + if (p_region.has_area()) { + Ref<AtlasTexture> atlas_tex = memnew(AtlasTexture); + atlas_tex->set_atlas(p_image); + atlas_tex->set_region(p_region); + item->image = atlas_tex; + } else { + item->image = p_image; + } + item->color = p_color; item->inline_align = p_alignment; @@ -2981,17 +2996,30 @@ void RichTextLabel::add_image(const Ref<Texture2D> &p_image, const int p_width, item->size.height = p_height; } else { // calculate height to keep aspect ratio - item->size.height = p_image->get_height() * p_width / p_image->get_width(); + if (p_region.has_area()) { + item->size.height = p_region.get_size().height * p_width / p_region.get_size().width; + } else { + item->size.height = p_image->get_height() * p_width / p_image->get_width(); + } } } else { if (p_height > 0) { // custom height item->size.height = p_height; // calculate width to keep aspect ratio - item->size.width = p_image->get_width() * p_height / p_image->get_height(); + if (p_region.has_area()) { + item->size.width = p_region.get_size().width * p_height / p_region.get_size().height; + } else { + item->size.width = p_image->get_width() * p_height / p_image->get_height(); + } } else { - // keep original width and height - item->size = p_image->get_size(); + if (p_region.has_area()) { + // if the image has a region, keep the region size + item->size = p_region.get_size(); + } else { + // keep original width and height + item->size = p_image->get_size(); + } } } @@ -3543,14 +3571,6 @@ bool RichTextLabel::is_hint_underlined() const { return underline_hint; } -void RichTextLabel::set_override_selected_font_color(bool p_override_selected_font_color) { - override_selected_font_color = p_override_selected_font_color; -} - -bool RichTextLabel::is_overriding_selected_font_color() const { - return override_selected_font_color; -} - void RichTextLabel::set_offset(int p_pixel) { vscroll->set_value(p_pixel); } @@ -3609,21 +3629,21 @@ void RichTextLabel::append_text(const String &p_bbcode) { brk_pos = p_bbcode.length(); } - String text = brk_pos > pos ? p_bbcode.substr(pos, brk_pos - pos) : ""; + String txt = brk_pos > pos ? p_bbcode.substr(pos, brk_pos - pos) : ""; // Trim the first newline character, it may be added later as needed. if (after_list_close_tag || after_list_open_tag) { - text = text.trim_prefix("\n"); + txt = txt.trim_prefix("\n"); } if (brk_pos == p_bbcode.length()) { // For tags that are not properly closed. - if (text.is_empty() && after_list_open_tag) { - text = "\n"; + if (txt.is_empty() && after_list_open_tag) { + txt = "\n"; } - if (!text.is_empty()) { - add_text(text); + if (!txt.is_empty()) { + add_text(txt); } break; //nothing else to add } @@ -3632,8 +3652,8 @@ void RichTextLabel::append_text(const String &p_bbcode) { if (brk_end == -1) { //no close, add the rest - text += p_bbcode.substr(brk_pos, p_bbcode.length() - brk_pos); - add_text(text); + txt += p_bbcode.substr(brk_pos, p_bbcode.length() - brk_pos); + add_text(txt); break; } @@ -3679,36 +3699,36 @@ void RichTextLabel::append_text(const String &p_bbcode) { } if (!tag_ok) { - text += "[" + tag; - add_text(text); + txt += "[" + tag; + add_text(txt); after_list_open_tag = false; after_list_close_tag = false; pos = brk_end; continue; } - if (text.is_empty() && after_list_open_tag) { - text = "\n"; // Make empty list have at least one item. + if (txt.is_empty() && after_list_open_tag) { + txt = "\n"; // Make empty list have at least one item. } after_list_open_tag = false; if (tag == "/ol" || tag == "/ul") { - if (!text.is_empty()) { + if (!txt.is_empty()) { // Make sure text ends with a newline character, that is, the last item // will wrap at the end of block. - if (!text.ends_with("\n")) { - text += "\n"; + if (!txt.ends_with("\n")) { + txt += "\n"; } } else if (!after_list_close_tag) { - text = "\n"; // Make the innermost list item wrap at the end of lists. + txt = "\n"; // Make the innermost list item wrap at the end of lists. } after_list_close_tag = true; } else { after_list_close_tag = false; } - if (!text.is_empty()) { - add_text(text); + if (!txt.is_empty()) { + add_text(txt); } tag_stack.pop_front(); @@ -3720,15 +3740,15 @@ void RichTextLabel::append_text(const String &p_bbcode) { } if (tag == "ol" || tag.begins_with("ol ") || tag == "ul" || tag.begins_with("ul ")) { - if (text.is_empty() && after_list_open_tag) { - text = "\n"; // Make each list have at least one item at the beginning. + if (txt.is_empty() && after_list_open_tag) { + txt = "\n"; // Make each list have at least one item at the beginning. } after_list_open_tag = true; } else { after_list_open_tag = false; } - if (!text.is_empty()) { - add_text(text); + if (!txt.is_empty()) { + add_text(txt); } after_list_close_tag = false; @@ -3840,6 +3860,10 @@ void RichTextLabel::append_text(const String &p_bbcode) { Color color2 = Color::from_string(subtag_b[1], fallback_color); set_cell_row_background_color(color1, color2); } + if (subtag_b.size() == 1) { + Color color1 = Color::from_string(subtag_a[1], fallback_color); + set_cell_row_background_color(color1, color1); + } } } } @@ -3970,7 +3994,7 @@ void RichTextLabel::append_text(const String &p_bbcode) { HorizontalAlignment alignment = HORIZONTAL_ALIGNMENT_LEFT; Control::TextDirection dir = Control::TEXT_DIRECTION_INHERITED; String lang; - TextServer::StructuredTextParser st_parser = TextServer::STRUCTURED_TEXT_DEFAULT; + TextServer::StructuredTextParser st_parser_type = TextServer::STRUCTURED_TEXT_DEFAULT; for (int i = 0; i < subtag.size(); i++) { Vector<String> subtag_a = subtag[i].split("="); if (subtag_a.size() == 2) { @@ -3996,24 +4020,24 @@ 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 = TextServer::STRUCTURED_TEXT_DEFAULT; + st_parser_type = TextServer::STRUCTURED_TEXT_DEFAULT; } else if (subtag_a[1] == "u" || subtag_a[1] == "uri") { - st_parser = TextServer::STRUCTURED_TEXT_URI; + st_parser_type = TextServer::STRUCTURED_TEXT_URI; } else if (subtag_a[1] == "f" || subtag_a[1] == "file") { - st_parser = TextServer::STRUCTURED_TEXT_FILE; + st_parser_type = TextServer::STRUCTURED_TEXT_FILE; } else if (subtag_a[1] == "e" || subtag_a[1] == "email") { - st_parser = TextServer::STRUCTURED_TEXT_EMAIL; + st_parser_type = TextServer::STRUCTURED_TEXT_EMAIL; } else if (subtag_a[1] == "l" || subtag_a[1] == "list") { - st_parser = TextServer::STRUCTURED_TEXT_LIST; + st_parser_type = TextServer::STRUCTURED_TEXT_LIST; } else if (subtag_a[1] == "n" || subtag_a[1] == "none") { - st_parser = TextServer::STRUCTURED_TEXT_NONE; + st_parser_type = TextServer::STRUCTURED_TEXT_NONE; } else if (subtag_a[1] == "c" || subtag_a[1] == "custom") { - st_parser = TextServer::STRUCTURED_TEXT_CUSTOM; + st_parser_type = TextServer::STRUCTURED_TEXT_CUSTOM; } } } } - push_paragraph(alignment, dir, lang, st_parser); + push_paragraph(alignment, dir, lang, st_parser_type); pos = brk_end + 1; tag_stack.push_front("p"); } else if (tag == "url") { @@ -4044,7 +4068,7 @@ void RichTextLabel::append_text(const String &p_bbcode) { Color color = theme_cache.default_color; Color outline_color = theme_cache.font_outline_color; int outline_size = theme_cache.outline_size; - Rect2 dropcap_margins = Rect2(); + Rect2 dropcap_margins; for (int i = 0; i < subtag.size(); i++) { Vector<String> subtag_a = subtag[i].split("="); @@ -4079,9 +4103,9 @@ void RichTextLabel::append_text(const String &p_bbcode) { end = p_bbcode.length(); } - String txt = p_bbcode.substr(brk_end + 1, end - brk_end - 1); + String dc_txt = p_bbcode.substr(brk_end + 1, end - brk_end - 1); - push_dropcap(txt, f, fs, dropcap_margins, color, outline_size, outline_color); + push_dropcap(dc_txt, f, fs, dropcap_margins, color, outline_size, outline_color); pos = end; tag_stack.push_front(bbcode_name); @@ -4126,6 +4150,18 @@ void RichTextLabel::append_text(const String &p_bbcode) { Ref<Texture2D> texture = ResourceLoader::load(image, "Texture2D"); if (texture.is_valid()) { + Rect2 region; + OptionMap::Iterator region_option = bbcode_options.find("region"); + if (region_option) { + Vector<String> region_values = region_option->value.split(",", false); + if (region_values.size() == 4) { + region.position.x = region_values[0].to_float(); + region.position.y = region_values[1].to_float(); + region.size.x = region_values[2].to_float(); + region.size.y = region_values[3].to_float(); + } + } + Color color = Color(1.0, 1.0, 1.0); OptionMap::Iterator color_option = bbcode_options.find("color"); if (color_option) { @@ -4154,7 +4190,7 @@ void RichTextLabel::append_text(const String &p_bbcode) { } } - add_image(texture, width, height, color, (InlineAlignment)alignment); + add_image(texture, width, height, color, (InlineAlignment)alignment, region); } pos = end; @@ -4668,19 +4704,19 @@ bool RichTextLabel::_search_line(ItemFrame *p_frame, int p_line, const String &p Line &l = p_frame->lines[p_line]; - String text; + String txt; Item *it_to = (p_line + 1 < (int)p_frame->lines.size()) ? p_frame->lines[p_line + 1].from : nullptr; for (Item *it = l.from; it && it != it_to; it = _get_next_item(it)) { switch (it->type) { case ITEM_NEWLINE: { - text += "\n"; + txt += "\n"; } break; case ITEM_TEXT: { ItemText *t = static_cast<ItemText *>(it); - text += t->text; + txt += t->text; } break; case ITEM_IMAGE: { - text += " "; + txt += " "; } break; case ITEM_TABLE: { ItemTable *table = static_cast<ItemTable *>(it); @@ -4696,9 +4732,9 @@ bool RichTextLabel::_search_line(ItemFrame *p_frame, int p_line, const String &p int sp = -1; if (p_reverse_search) { - sp = text.rfindn(p_string, p_char_idx); + sp = txt.rfindn(p_string, p_char_idx); } else { - sp = text.findn(p_string, p_char_idx); + sp = txt.findn(p_string, p_char_idx); } if (sp != -1) { @@ -4802,10 +4838,10 @@ bool RichTextLabel::search(const String &p_string, bool p_from_selection, bool p } String RichTextLabel::_get_line_text(ItemFrame *p_frame, int p_line, Selection p_selection) const { - String text; + String txt; - ERR_FAIL_COND_V(p_frame == nullptr, text); - ERR_FAIL_COND_V(p_line < 0 || p_line >= (int)p_frame->lines.size(), text); + ERR_FAIL_COND_V(p_frame == nullptr, txt); + ERR_FAIL_COND_V(p_line < 0 || p_line >= (int)p_frame->lines.size(), txt); Line &l = p_frame->lines[p_line]; @@ -4825,7 +4861,7 @@ String RichTextLabel::_get_line_text(ItemFrame *p_frame, int p_line, Selection p ERR_CONTINUE(E->type != ITEM_FRAME); // Children should all be frames. ItemFrame *frame = static_cast<ItemFrame *>(E); for (int i = 0; i < (int)frame->lines.size(); i++) { - text += _get_line_text(frame, i, p_selection); + txt += _get_line_text(frame, i, p_selection); } } } @@ -4837,23 +4873,23 @@ String RichTextLabel::_get_line_text(ItemFrame *p_frame, int p_line, Selection p } if (it->type == ITEM_DROPCAP) { const ItemDropcap *dc = static_cast<ItemDropcap *>(it); - text += dc->text; + txt += dc->text; } else if (it->type == ITEM_TEXT) { const ItemText *t = static_cast<ItemText *>(it); - text += t->text; + txt += t->text; } else if (it->type == ITEM_NEWLINE) { - text += "\n"; + txt += "\n"; } else if (it->type == ITEM_IMAGE) { - text += " "; + txt += " "; } } if ((l.from != nullptr) && (p_frame == p_selection.to_frame) && (p_selection.to_item != nullptr) && (p_selection.to_item->index >= l.from->index) && (p_selection.to_item->index < end_idx)) { - text = text.substr(0, p_selection.to_char); + txt = txt.substr(0, p_selection.to_char); } if ((l.from != nullptr) && (p_frame == p_selection.from_frame) && (p_selection.from_item != nullptr) && (p_selection.from_item->index >= l.from->index) && (p_selection.from_item->index < end_idx)) { - text = text.substr(p_selection.from_char, -1); + txt = txt.substr(p_selection.from_char, -1); } - return text; + return txt; } void RichTextLabel::set_context_menu_enabled(bool p_enabled) { @@ -4887,12 +4923,12 @@ String RichTextLabel::get_selected_text() const { return ""; } - String text; + String txt; int to_line = main->first_invalid_line.load(); for (int i = 0; i < to_line; i++) { - text += _get_line_text(main, i, selection); + txt += _get_line_text(main, i, selection); } - return text; + return txt; } void RichTextLabel::deselect() { @@ -4901,10 +4937,10 @@ void RichTextLabel::deselect() { } void RichTextLabel::selection_copy() { - String text = get_selected_text(); + String txt = get_selected_text(); - if (!text.is_empty()) { - DisplayServer::get_singleton()->clipboard_set(text); + if (!txt.is_empty()) { + DisplayServer::get_singleton()->clipboard_set(txt); } } @@ -5018,25 +5054,25 @@ bool RichTextLabel::is_using_bbcode() const { } String RichTextLabel::get_parsed_text() const { - String text = ""; + String txt = ""; Item *it = main; while (it) { if (it->type == ITEM_DROPCAP) { ItemDropcap *dc = static_cast<ItemDropcap *>(it); - text += dc->text; + txt += dc->text; } else if (it->type == ITEM_TEXT) { ItemText *t = static_cast<ItemText *>(it); - text += t->text; + txt += t->text; } else if (it->type == ITEM_NEWLINE) { - text += "\n"; + txt += "\n"; } else if (it->type == ITEM_IMAGE) { - text += " "; + txt += " "; } else if (it->type == ITEM_INDENT || it->type == ITEM_LIST) { - text += "\t"; + txt += "\t"; } it = _get_next_item(it, true); } - return text; + return txt; } void RichTextLabel::set_text_direction(Control::TextDirection p_text_direction) { @@ -5209,7 +5245,7 @@ void RichTextLabel::_bind_methods() { ClassDB::bind_method(D_METHOD("get_parsed_text"), &RichTextLabel::get_parsed_text); ClassDB::bind_method(D_METHOD("add_text", "text"), &RichTextLabel::add_text); ClassDB::bind_method(D_METHOD("set_text", "text"), &RichTextLabel::set_text); - ClassDB::bind_method(D_METHOD("add_image", "image", "width", "height", "color", "inline_align"), &RichTextLabel::add_image, DEFVAL(0), DEFVAL(0), DEFVAL(Color(1.0, 1.0, 1.0)), DEFVAL(INLINE_ALIGNMENT_CENTER)); + ClassDB::bind_method(D_METHOD("add_image", "image", "width", "height", "color", "inline_align", "region"), &RichTextLabel::add_image, DEFVAL(0), DEFVAL(0), DEFVAL(Color(1.0, 1.0, 1.0)), DEFVAL(INLINE_ALIGNMENT_CENTER), DEFVAL(Rect2(0, 0, 0, 0))); ClassDB::bind_method(D_METHOD("newline"), &RichTextLabel::add_newline); ClassDB::bind_method(D_METHOD("remove_line", "line"), &RichTextLabel::remove_line); ClassDB::bind_method(D_METHOD("push_font", "font", "font_size"), &RichTextLabel::push_font); @@ -5261,9 +5297,6 @@ void RichTextLabel::_bind_methods() { ClassDB::bind_method(D_METHOD("set_hint_underline", "enable"), &RichTextLabel::set_hint_underline); ClassDB::bind_method(D_METHOD("is_hint_underlined"), &RichTextLabel::is_hint_underlined); - ClassDB::bind_method(D_METHOD("set_override_selected_font_color", "override"), &RichTextLabel::set_override_selected_font_color); - ClassDB::bind_method(D_METHOD("is_overriding_selected_font_color"), &RichTextLabel::is_overriding_selected_font_color); - ClassDB::bind_method(D_METHOD("set_scroll_active", "active"), &RichTextLabel::set_scroll_active); ClassDB::bind_method(D_METHOD("is_scroll_active"), &RichTextLabel::is_scroll_active); @@ -5363,7 +5396,7 @@ void RichTextLabel::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::BOOL, "shortcut_keys_enabled"), "set_shortcut_keys_enabled", "is_shortcut_keys_enabled"); ADD_GROUP("Markup", ""); - ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "custom_effects", PROPERTY_HINT_ARRAY_TYPE, vformat("%s/%s:%s", Variant::OBJECT, PROPERTY_HINT_RESOURCE_TYPE, "RichTextEffect"), (PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_SCRIPT_VARIABLE)), "set_effects", "get_effects"); + ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "custom_effects", PROPERTY_HINT_ARRAY_TYPE, MAKE_RESOURCE_TYPE_HINT("RichTextEffect"), (PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_SCRIPT_VARIABLE)), "set_effects", "get_effects"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "meta_underlined"), "set_meta_underline", "is_meta_underlined"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "hint_underlined"), "set_hint_underline", "is_hint_underlined"); @@ -5373,7 +5406,6 @@ void RichTextLabel::_bind_methods() { ADD_GROUP("Text Selection", ""); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "selection_enabled"), "set_selection_enabled", "is_selection_enabled"); - ADD_PROPERTY(PropertyInfo(Variant::BOOL, "override_selected_font_color"), "set_override_selected_font_color", "is_overriding_selected_font_color"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "deselect_on_focus_loss_enabled"), "set_deselect_on_focus_loss_enabled", "is_deselect_on_focus_loss_enabled"); ADD_GROUP("Displayed Text", ""); @@ -5622,6 +5654,8 @@ void RichTextLabel::_draw_fbg_boxes(RID p_ci, RID p_rid, Vector2 line_off, Item Vector2i fbg_index = Vector2i(end, start); Color last_color = Color(0, 0, 0, 0); bool draw_box = false; + int hpad = get_theme_constant(SNAME("text_highlight_h_padding")); + int vpad = get_theme_constant(SNAME("text_highlight_v_padding")); // Draw a box based on color tags associated with glyphs for (int i = start; i < end; i++) { Item *it = _get_item_at_pos(it_from, it_to, i); @@ -5651,8 +5685,8 @@ void RichTextLabel::_draw_fbg_boxes(RID p_ci, RID p_rid, Vector2 line_off, Item if (draw_box) { Vector<Vector2> sel = TS->shaped_text_get_selection(p_rid, fbg_index.x, fbg_index.y); for (int j = 0; j < sel.size(); j++) { - Vector2 rect_off = line_off + Vector2(sel[j].x, -TS->shaped_text_get_ascent(p_rid)); - Vector2 rect_size = Vector2(sel[j].y - sel[j].x, TS->shaped_text_get_size(p_rid).y); + Vector2 rect_off = line_off + Vector2(sel[j].x - hpad, -TS->shaped_text_get_ascent(p_rid) - vpad); + Vector2 rect_size = Vector2(sel[j].y - sel[j].x + 2 * hpad, TS->shaped_text_get_size(p_rid).y + 2 * vpad); RenderingServer::get_singleton()->canvas_item_add_rect(p_ci, Rect2(rect_off, rect_size), last_color); } fbg_index = Vector2i(end, start); @@ -5670,8 +5704,8 @@ void RichTextLabel::_draw_fbg_boxes(RID p_ci, RID p_rid, Vector2 line_off, Item if (last_color.a > 0) { Vector<Vector2> sel = TS->shaped_text_get_selection(p_rid, fbg_index.x, end); for (int i = 0; i < sel.size(); i++) { - Vector2 rect_off = line_off + Vector2(sel[i].x, -TS->shaped_text_get_ascent(p_rid)); - Vector2 rect_size = Vector2(sel[i].y - sel[i].x, TS->shaped_text_get_size(p_rid).y); + Vector2 rect_off = line_off + Vector2(sel[i].x - hpad, -TS->shaped_text_get_ascent(p_rid) - vpad); + Vector2 rect_size = Vector2(sel[i].y - sel[i].x + 2 * hpad, TS->shaped_text_get_size(p_rid).y + 2 * vpad); RenderingServer::get_singleton()->canvas_item_add_rect(p_ci, Rect2(rect_off, rect_size), last_color); } } @@ -5693,11 +5727,11 @@ Ref<RichTextEffect> RichTextLabel::_get_custom_effect_by_code(String p_bbcode_id } Dictionary RichTextLabel::parse_expressions_for_values(Vector<String> p_expressions) { - Dictionary d = Dictionary(); + Dictionary d; for (int i = 0; i < p_expressions.size(); i++) { String expression = p_expressions[i]; - Array a = Array(); + Array a; Vector<String> parts = expression.split("=", true); String key = parts[0]; if (parts.size() != 2) { diff --git a/scene/gui/rich_text_label.h b/scene/gui/rich_text_label.h index 71123602ad..d30baaa8d3 100644 --- a/scene/gui/rich_text_label.h +++ b/scene/gui/rich_text_label.h @@ -301,14 +301,14 @@ private: _current_rng = Math::rand(); } - uint64_t offset_random(int index) { - return (_current_rng >> (index % 64)) | - (_current_rng << (64 - (index % 64))); + uint64_t offset_random(int p_index) { + return (_current_rng >> (p_index % 64)) | + (_current_rng << (64 - (p_index % 64))); } - uint64_t offset_previous_random(int index) { - return (_previous_rng >> (index % 64)) | - (_previous_rng << (64 - (index % 64))); + uint64_t offset_previous_random(int p_index) { + return (_previous_rng >> (p_index % 64)) | + (_previous_rng << (64 - (p_index % 64))); } }; @@ -396,7 +396,7 @@ private: int tab_size = 4; bool underline_meta = true; bool underline_hint = true; - bool override_selected_font_color = false; + bool use_selected_font_color = false; HorizontalAlignment default_alignment = HORIZONTAL_ALIGNMENT_LEFT; @@ -569,7 +569,7 @@ private: public: String get_parsed_text() const; void add_text(const String &p_text); - void add_image(const Ref<Texture2D> &p_image, const int p_width = 0, const int p_height = 0, const Color &p_color = Color(1.0, 1.0, 1.0), InlineAlignment p_alignment = INLINE_ALIGNMENT_CENTER); + void add_image(const Ref<Texture2D> &p_image, const int p_width = 0, const int p_height = 0, const Color &p_color = Color(1.0, 1.0, 1.0), InlineAlignment p_alignment = INLINE_ALIGNMENT_CENTER, const Rect2 &p_region = Rect2(0, 0, 0, 0)); void add_newline(); bool remove_line(const int p_line); void push_dropcap(const String &p_string, const Ref<Font> &p_font, int p_size, const Rect2 &p_dropcap_margins = Rect2(), const Color &p_color = Color(1, 1, 1), int p_ol_size = 0, const Color &p_ol_color = Color(0, 0, 0, 0)); diff --git a/scene/gui/scroll_bar.cpp b/scene/gui/scroll_bar.cpp index 6c05b171e3..6899178885 100644 --- a/scene/gui/scroll_bar.cpp +++ b/scene/gui/scroll_bar.cpp @@ -183,35 +183,35 @@ void ScrollBar::gui_input(const Ref<InputEvent> &p_event) { } if (p_event->is_pressed()) { - if (p_event->is_action("ui_left")) { + if (p_event->is_action("ui_left", true)) { if (orientation != HORIZONTAL) { return; } set_value(get_value() - (custom_step >= 0 ? custom_step : get_step())); - } else if (p_event->is_action("ui_right")) { + } else if (p_event->is_action("ui_right", true)) { if (orientation != HORIZONTAL) { return; } set_value(get_value() + (custom_step >= 0 ? custom_step : get_step())); - } else if (p_event->is_action("ui_up")) { + } else if (p_event->is_action("ui_up", true)) { if (orientation != VERTICAL) { return; } set_value(get_value() - (custom_step >= 0 ? custom_step : get_step())); - } else if (p_event->is_action("ui_down")) { + } else if (p_event->is_action("ui_down", true)) { if (orientation != VERTICAL) { return; } set_value(get_value() + (custom_step >= 0 ? custom_step : get_step())); - } else if (p_event->is_action("ui_home")) { + } else if (p_event->is_action("ui_home", true)) { set_value(get_min()); - } else if (p_event->is_action("ui_end")) { + } else if (p_event->is_action("ui_end", true)) { set_value(get_max()); } } @@ -338,7 +338,7 @@ void ScrollBar::_notification(int p_what) { if (scrolling) { if (get_value() != target_scroll) { double target = target_scroll - get_value(); - double dist = sqrt(target * target); + double dist = abs(target); double vel = ((target / dist) * 500) * get_physics_process_delta_time(); if (Math::abs(vel) >= dist) { diff --git a/scene/gui/scroll_bar.h b/scene/gui/scroll_bar.h index 13ca62d7ff..d62acd52af 100644 --- a/scene/gui/scroll_bar.h +++ b/scene/gui/scroll_bar.h @@ -72,7 +72,7 @@ class ScrollBar : public Range { NodePath drag_node_path; bool drag_node_enabled = true; - Vector2 drag_node_speed = Vector2(); + Vector2 drag_node_speed; Vector2 drag_node_accum; Vector2 drag_node_from; Vector2 last_drag_node_accum; diff --git a/scene/gui/scroll_container.cpp b/scene/gui/scroll_container.cpp index a6aa4707ff..531226f938 100644 --- a/scene/gui/scroll_container.cpp +++ b/scene/gui/scroll_container.cpp @@ -107,45 +107,65 @@ void ScrollContainer::gui_input(const Ref<InputEvent> &p_gui_input) { double prev_v_scroll = v_scroll->get_value(); double prev_h_scroll = h_scroll->get_value(); + bool h_scroll_enabled = horizontal_scroll_mode != SCROLL_MODE_DISABLED; + bool v_scroll_enabled = vertical_scroll_mode != SCROLL_MODE_DISABLED; Ref<InputEventMouseButton> mb = p_gui_input; if (mb.is_valid()) { - if (mb->get_button_index() == MouseButton::WHEEL_UP && mb->is_pressed()) { - // only horizontal is enabled, scroll horizontally - if (h_scroll->is_visible() && (!v_scroll->is_visible() || mb->is_shift_pressed())) { - h_scroll->set_value(h_scroll->get_value() - h_scroll->get_page() / 8 * mb->get_factor()); - } else if (v_scroll->is_visible_in_tree()) { - v_scroll->set_value(v_scroll->get_value() - v_scroll->get_page() / 8 * mb->get_factor()); + if (mb->is_pressed()) { + bool scroll_value_modified = false; + + bool v_scroll_hidden = !v_scroll->is_visible() && vertical_scroll_mode != SCROLL_MODE_SHOW_NEVER; + if (mb->get_button_index() == MouseButton::WHEEL_UP) { + // By default, the vertical orientation takes precedence. This is an exception. + if ((h_scroll_enabled && mb->is_shift_pressed()) || v_scroll_hidden) { + h_scroll->set_value(prev_h_scroll - h_scroll->get_page() / 8 * mb->get_factor()); + scroll_value_modified = true; + } else if (v_scroll_enabled) { + v_scroll->set_value(prev_v_scroll - v_scroll->get_page() / 8 * mb->get_factor()); + scroll_value_modified = true; + } } - } - - if (mb->get_button_index() == MouseButton::WHEEL_DOWN && mb->is_pressed()) { - // only horizontal is enabled, scroll horizontally - if (h_scroll->is_visible() && (!v_scroll->is_visible() || mb->is_shift_pressed())) { - h_scroll->set_value(h_scroll->get_value() + h_scroll->get_page() / 8 * mb->get_factor()); - } else if (v_scroll->is_visible()) { - v_scroll->set_value(v_scroll->get_value() + v_scroll->get_page() / 8 * mb->get_factor()); + if (mb->get_button_index() == MouseButton::WHEEL_DOWN) { + if ((h_scroll_enabled && mb->is_shift_pressed()) || v_scroll_hidden) { + h_scroll->set_value(prev_h_scroll + h_scroll->get_page() / 8 * mb->get_factor()); + scroll_value_modified = true; + } else if (v_scroll_enabled) { + v_scroll->set_value(prev_v_scroll + v_scroll->get_page() / 8 * mb->get_factor()); + scroll_value_modified = true; + } } - } - if (mb->get_button_index() == MouseButton::WHEEL_LEFT && mb->is_pressed()) { - if (h_scroll->is_visible_in_tree()) { - h_scroll->set_value(h_scroll->get_value() - h_scroll->get_page() * mb->get_factor() / 8); + bool h_scroll_hidden = !h_scroll->is_visible() && horizontal_scroll_mode != SCROLL_MODE_SHOW_NEVER; + if (mb->get_button_index() == MouseButton::WHEEL_LEFT) { + // By default, the horizontal orientation takes precedence. This is an exception. + if ((v_scroll_enabled && mb->is_shift_pressed()) || h_scroll_hidden) { + v_scroll->set_value(prev_v_scroll - v_scroll->get_page() / 8 * mb->get_factor()); + scroll_value_modified = true; + } else if (h_scroll_enabled) { + h_scroll->set_value(prev_h_scroll - h_scroll->get_page() / 8 * mb->get_factor()); + scroll_value_modified = true; + } } - } - - if (mb->get_button_index() == MouseButton::WHEEL_RIGHT && mb->is_pressed()) { - if (h_scroll->is_visible_in_tree()) { - h_scroll->set_value(h_scroll->get_value() + h_scroll->get_page() * mb->get_factor() / 8); + if (mb->get_button_index() == MouseButton::WHEEL_RIGHT) { + if ((v_scroll_enabled && mb->is_shift_pressed()) || h_scroll_hidden) { + v_scroll->set_value(prev_v_scroll + v_scroll->get_page() / 8 * mb->get_factor()); + scroll_value_modified = true; + } else if (h_scroll_enabled) { + h_scroll->set_value(prev_h_scroll + h_scroll->get_page() / 8 * mb->get_factor()); + scroll_value_modified = true; + } } - } - if (v_scroll->get_value() != prev_v_scroll || h_scroll->get_value() != prev_h_scroll) { - accept_event(); //accept event if scroll changed + if (scroll_value_modified && (v_scroll->get_value() != prev_v_scroll || h_scroll->get_value() != prev_h_scroll)) { + accept_event(); // Accept event if scroll changed. + return; + } } - if (!DisplayServer::get_singleton()->screen_is_touchscreen(DisplayServer::get_singleton()->window_get_current_screen(get_viewport()->get_window_id()))) { + bool screen_is_touchscreen = DisplayServer::get_singleton()->screen_is_touchscreen(DisplayServer::get_singleton()->window_get_current_screen(get_viewport()->get_window_id())); + if (!screen_is_touchscreen) { return; } @@ -161,15 +181,13 @@ void ScrollContainer::gui_input(const Ref<InputEvent> &p_gui_input) { drag_speed = Vector2(); drag_accum = Vector2(); last_drag_accum = Vector2(); - drag_from = Vector2(h_scroll->get_value(), v_scroll->get_value()); - drag_touching = DisplayServer::get_singleton()->screen_is_touchscreen(DisplayServer::get_singleton()->window_get_current_screen(get_viewport()->get_window_id())); + drag_from = Vector2(prev_h_scroll, prev_v_scroll); + drag_touching = true; drag_touching_deaccel = false; beyond_deadzone = false; time_since_motion = 0; - if (drag_touching) { - set_physics_process_internal(true); - time_since_motion = 0; - } + set_physics_process_internal(true); + time_since_motion = 0; } else { if (drag_touching) { @@ -180,6 +198,7 @@ void ScrollContainer::gui_input(const Ref<InputEvent> &p_gui_input) { } } } + return; } Ref<InputEventMouseMotion> mm = p_gui_input; @@ -189,22 +208,22 @@ void ScrollContainer::gui_input(const Ref<InputEvent> &p_gui_input) { Vector2 motion = mm->get_relative(); drag_accum -= motion; - if (beyond_deadzone || (horizontal_scroll_mode != SCROLL_MODE_DISABLED && Math::abs(drag_accum.x) > deadzone) || (vertical_scroll_mode != SCROLL_MODE_DISABLED && Math::abs(drag_accum.y) > deadzone)) { + if (beyond_deadzone || (h_scroll_enabled && Math::abs(drag_accum.x) > deadzone) || (v_scroll_enabled && Math::abs(drag_accum.y) > deadzone)) { if (!beyond_deadzone) { propagate_notification(NOTIFICATION_SCROLL_BEGIN); emit_signal(SNAME("scroll_started")); beyond_deadzone = true; - // resetting drag_accum here ensures smooth scrolling after reaching deadzone + // Resetting drag_accum here ensures smooth scrolling after reaching deadzone. drag_accum = -motion; } Vector2 diff = drag_from + drag_accum; - if (horizontal_scroll_mode != SCROLL_MODE_DISABLED) { + if (h_scroll_enabled) { h_scroll->set_value(diff.x); } else { drag_accum.x = 0; } - if (vertical_scroll_mode != SCROLL_MODE_DISABLED) { + if (v_scroll_enabled) { v_scroll->set_value(diff.y); } else { drag_accum.y = 0; @@ -212,20 +231,26 @@ void ScrollContainer::gui_input(const Ref<InputEvent> &p_gui_input) { time_since_motion = 0; } } + + if (v_scroll->get_value() != prev_v_scroll || h_scroll->get_value() != prev_h_scroll) { + accept_event(); // Accept event if scroll changed. + } + return; } Ref<InputEventPanGesture> pan_gesture = p_gui_input; if (pan_gesture.is_valid()) { - if (h_scroll->is_visible_in_tree()) { - h_scroll->set_value(h_scroll->get_value() + h_scroll->get_page() * pan_gesture->get_delta().x / 8); + if (h_scroll_enabled) { + h_scroll->set_value(prev_h_scroll + h_scroll->get_page() * pan_gesture->get_delta().x / 8); } - if (v_scroll->is_visible_in_tree()) { - v_scroll->set_value(v_scroll->get_value() + v_scroll->get_page() * pan_gesture->get_delta().y / 8); + if (v_scroll_enabled) { + v_scroll->set_value(prev_v_scroll + v_scroll->get_page() * pan_gesture->get_delta().y / 8); } - } - if (v_scroll->get_value() != prev_v_scroll || h_scroll->get_value() != prev_h_scroll) { - accept_event(); //accept event if scroll changed + if (v_scroll->get_value() != prev_v_scroll || h_scroll->get_value() != prev_h_scroll) { + accept_event(); // Accept event if scroll changed. + } + return; } } diff --git a/scene/gui/slider.cpp b/scene/gui/slider.cpp index ff3adfb9ac..6cbacf0dd3 100644 --- a/scene/gui/slider.cpp +++ b/scene/gui/slider.cpp @@ -138,10 +138,10 @@ void Slider::gui_input(const Ref<InputEvent> &p_event) { } set_value(get_value() - (custom_step >= 0 ? custom_step : get_step())); accept_event(); - } else if (p_event->is_action("ui_home") && p_event->is_pressed()) { + } else if (p_event->is_action("ui_home", true) && p_event->is_pressed()) { set_value(get_min()); accept_event(); - } else if (p_event->is_action("ui_end") && p_event->is_pressed()) { + } else if (p_event->is_action("ui_end", true) && p_event->is_pressed()) { set_value(get_max()); accept_event(); } @@ -227,7 +227,7 @@ void Slider::_notification(int p_what) { tick->draw(ci, Point2i((size.width - widget_width) / 2, ofs)); } } - grabber->draw(ci, Point2i(size.width / 2 - grabber->get_size().width / 2, size.height - ratio * areasize - grabber->get_size().height)); + grabber->draw(ci, Point2i(size.width / 2 - grabber->get_size().width / 2 + get_theme_constant(SNAME("grabber_offset")), size.height - ratio * areasize - grabber->get_size().height)); } else { int widget_height = style->get_minimum_size().height + style->get_center_size().height; double areasize = size.width - grabber->get_size().width; @@ -245,7 +245,7 @@ void Slider::_notification(int p_what) { tick->draw(ci, Point2i(ofs, (size.height - widget_height) / 2)); } } - grabber->draw(ci, Point2i(ratio * areasize, size.height / 2 - grabber->get_size().height / 2)); + grabber->draw(ci, Point2i(ratio * areasize, size.height / 2 - grabber->get_size().height / 2 + get_theme_constant(SNAME("grabber_offset")))); } } break; } diff --git a/scene/gui/spin_box.cpp b/scene/gui/spin_box.cpp index fe14049d93..e15b3b7bd4 100644 --- a/scene/gui/spin_box.cpp +++ b/scene/gui/spin_box.cpp @@ -40,7 +40,10 @@ Size2 SpinBox::get_minimum_size() const { } void SpinBox::_value_changed(double p_value) { - String value = TS->format_number(String::num(get_value(), Math::range_step_decimals(get_step()))); + String value = String::num(get_value(), Math::range_step_decimals(get_step())); + if (is_localizing_numeral_system()) { + value = TS->format_number(value); + } if (!line_edit->has_focus()) { if (!prefix.is_empty()) { @@ -168,6 +171,7 @@ void SpinBox::gui_input(const Ref<InputEvent> &p_event) { range_click_timer->stop(); _release_mouse(); drag.allowed = false; + line_edit->clear_pending_select_all_on_focus(); } Ref<InputEventMouseMotion> mm = p_event; @@ -190,6 +194,11 @@ void SpinBox::_line_edit_focus_enter() { int col = line_edit->get_caret_column(); _value_changed(0); // Update the LineEdit's text. line_edit->set_caret_column(col); + + // LineEdit text might change and it clears any selection. Have to re-select here. + if (line_edit->is_select_all_on_focus() && !Input::get_singleton()->is_mouse_button_pressed(MouseButton::LEFT)) { + line_edit->select_all(); + } } void SpinBox::_line_edit_focus_exit() { @@ -308,6 +317,14 @@ bool SpinBox::get_update_on_text_changed() const { return update_on_text_changed; } +void SpinBox::set_select_all_on_focus(bool p_enabled) { + line_edit->set_select_all_on_focus(p_enabled); +} + +bool SpinBox::is_select_all_on_focus() const { + return line_edit->is_select_all_on_focus(); +} + void SpinBox::set_editable(bool p_enabled) { line_edit->set_editable(p_enabled); } @@ -341,6 +358,8 @@ void SpinBox::_bind_methods() { ClassDB::bind_method(D_METHOD("is_editable"), &SpinBox::is_editable); ClassDB::bind_method(D_METHOD("set_update_on_text_changed", "enabled"), &SpinBox::set_update_on_text_changed); ClassDB::bind_method(D_METHOD("get_update_on_text_changed"), &SpinBox::get_update_on_text_changed); + ClassDB::bind_method(D_METHOD("set_select_all_on_focus", "enabled"), &SpinBox::set_select_all_on_focus); + ClassDB::bind_method(D_METHOD("is_select_all_on_focus"), &SpinBox::is_select_all_on_focus); ClassDB::bind_method(D_METHOD("apply"), &SpinBox::apply); ClassDB::bind_method(D_METHOD("get_line_edit"), &SpinBox::get_line_edit); @@ -350,6 +369,7 @@ void SpinBox::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::STRING, "prefix"), "set_prefix", "get_prefix"); ADD_PROPERTY(PropertyInfo(Variant::STRING, "suffix"), "set_suffix", "get_suffix"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "custom_arrow_step"), "set_custom_arrow_step", "get_custom_arrow_step"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "select_all_on_focus"), "set_select_all_on_focus", "is_select_all_on_focus"); } SpinBox::SpinBox() { diff --git a/scene/gui/spin_box.h b/scene/gui/spin_box.h index c2f2ac3f5a..00f5a95699 100644 --- a/scene/gui/spin_box.h +++ b/scene/gui/spin_box.h @@ -101,6 +101,9 @@ public: void set_update_on_text_changed(bool p_enabled); bool get_update_on_text_changed() const; + void set_select_all_on_focus(bool p_enabled); + bool is_select_all_on_focus() const; + void apply(); void set_custom_arrow_step(const double p_custom_arrow_step); double get_custom_arrow_step() const; diff --git a/scene/gui/split_container.cpp b/scene/gui/split_container.cpp index 2ca1d6239e..9830b41389 100644 --- a/scene/gui/split_container.cpp +++ b/scene/gui/split_container.cpp @@ -71,7 +71,7 @@ void SplitContainerDragger::gui_input(const Ref<InputEvent> &p_event) { Vector2i in_parent_pos = get_transform().xform(mm->get_position()); if (!sc->vertical && is_layout_rtl()) { - sc->split_offset = drag_ofs - ((sc->vertical ? in_parent_pos.y : in_parent_pos.x) - drag_from); + sc->split_offset = drag_ofs - (in_parent_pos.x - drag_from); } else { sc->split_offset = drag_ofs + ((sc->vertical ? in_parent_pos.y : in_parent_pos.x) - drag_from); } @@ -194,7 +194,6 @@ void SplitContainer::_compute_middle_sep(bool p_clamp) { // Clamp the split_offset if requested. if (p_clamp) { split_offset -= wished_middle_sep - middle_sep; - p_clamp = false; } } diff --git a/scene/gui/subviewport_container.cpp b/scene/gui/subviewport_container.cpp index 3ad84cbc6d..f3d9a22342 100644 --- a/scene/gui/subviewport_container.cpp +++ b/scene/gui/subviewport_container.cpp @@ -227,6 +227,18 @@ void SubViewportContainer::unhandled_input(const Ref<InputEvent> &p_event) { } } +void SubViewportContainer::add_child_notify(Node *p_child) { + if (Object::cast_to<SubViewport>(p_child)) { + queue_redraw(); + } +} + +void SubViewportContainer::remove_child_notify(Node *p_child) { + if (Object::cast_to<SubViewport>(p_child)) { + queue_redraw(); + } +} + PackedStringArray SubViewportContainer::get_configuration_warnings() const { PackedStringArray warnings = Node::get_configuration_warnings(); diff --git a/scene/gui/subviewport_container.h b/scene/gui/subviewport_container.h index 63a58b5f07..fdd8fe9486 100644 --- a/scene/gui/subviewport_container.h +++ b/scene/gui/subviewport_container.h @@ -44,6 +44,9 @@ protected: void _notification(int p_what); static void _bind_methods(); + virtual void add_child_notify(Node *p_child) override; + virtual void remove_child_notify(Node *p_child) override; + public: void set_stretch(bool p_enable); bool is_stretch_enabled() const; diff --git a/scene/gui/tab_bar.cpp b/scene/gui/tab_bar.cpp index cf6681f809..f87829cf71 100644 --- a/scene/gui/tab_bar.cpp +++ b/scene/gui/tab_bar.cpp @@ -385,9 +385,6 @@ void TabBar::_notification(int p_what) { if (tabs[i].disabled) { sb = theme_cache.tab_disabled_style; col = theme_cache.font_disabled_color; - } else if (i == current) { - sb = theme_cache.tab_selected_style; - col = theme_cache.font_selected_color; } else { sb = theme_cache.tab_unselected_style; col = theme_cache.font_unselected_color; diff --git a/scene/gui/tab_container.cpp b/scene/gui/tab_container.cpp index ab4808d312..9105267ad3 100644 --- a/scene/gui/tab_container.cpp +++ b/scene/gui/tab_container.cpp @@ -345,7 +345,7 @@ Vector<Control *> TabContainer::_get_tab_controls() const { Vector<Control *> controls; for (int i = 0; i < get_child_count(); i++) { Control *control = Object::cast_to<Control>(get_child(i)); - if (!control || control->is_set_as_top_level() || control == tab_bar || control == child_removing) { + if (!control || control->is_set_as_top_level() || control == tab_bar || children_removing.has(control)) { continue; } @@ -584,10 +584,10 @@ void TabContainer::remove_child_notify(Node *p_child) { int idx = get_tab_idx_from_control(c); - // Before this, the tab control has not changed; after this, the tab control has changed. - child_removing = p_child; + // As the child hasn't been removed yet, keep track of it so when the "tab_changed" signal is fired it can be ignored. + children_removing.push_back(c); tab_bar->remove_tab(idx); - child_removing = nullptr; + children_removing.erase(c); _update_margins(); if (get_tab_count() == 0) { diff --git a/scene/gui/tab_container.h b/scene/gui/tab_container.h index b552aa459b..60961e02d3 100644 --- a/scene/gui/tab_container.h +++ b/scene/gui/tab_container.h @@ -46,7 +46,7 @@ class TabContainer : public Container { bool drag_to_rearrange_enabled = false; bool use_hidden_tabs_for_min_size = false; bool theme_changing = false; - Node *child_removing = nullptr; + Vector<Control *> children_removing; struct ThemeCache { int side_margin = 0; diff --git a/scene/gui/text_edit.cpp b/scene/gui/text_edit.cpp index cc0fa200a6..cce9fa4f34 100644 --- a/scene/gui/text_edit.cpp +++ b/scene/gui/text_edit.cpp @@ -151,7 +151,7 @@ void TextEdit::Text::_calculate_line_height() { } void TextEdit::Text::_calculate_max_line_width() { - int width = 0; + int line_width = 0; for (const Line &l : text) { if (l.hidden) { continue; @@ -159,12 +159,12 @@ void TextEdit::Text::_calculate_max_line_width() { // Found another line with the same width...nothing to update. if (l.width == max_width) { - width = max_width; + line_width = max_width; break; } - width = MAX(width, l.width); + line_width = MAX(line_width, l.width); } - max_width = width; + max_width = line_width; } void TextEdit::Text::invalidate_cache(int p_line, int p_column, bool p_text_changed, const String &p_ime_text, const Array &p_bidi_override) { @@ -233,14 +233,14 @@ void TextEdit::Text::invalidate_cache(int p_line, int p_column, bool p_text_chan // Update width. const int old_width = text.write[p_line].width; - int width = get_line_width(p_line); - text.write[p_line].width = width; + int line_width = get_line_width(p_line); + text.write[p_line].width = line_width; // If this line has shrunk, this may no longer the the longest line. - if (old_width == max_width && width < max_width) { + if (old_width == max_width && line_width < max_width) { _calculate_max_line_width(); } else if (!is_hidden(p_line)) { - max_width = MAX(width, max_width); + max_width = MAX(line_width, max_width); } } @@ -464,7 +464,7 @@ void TextEdit::_notification(int p_what) { case NOTIFICATION_INTERNAL_PHYSICS_PROCESS: { if (scrolling && get_v_scroll() != target_v_scroll) { double target_y = target_v_scroll - get_v_scroll(); - double dist = sqrt(target_y * target_y); + double dist = abs(target_y); // To ensure minimap is responsive override the speed setting. double vel = ((target_y / dist) * ((minimap_clicked) ? 3000 : v_scroll_speed)) * get_physics_process_delta_time(); @@ -698,9 +698,9 @@ void TextEdit::_notification(int p_what) { carets_wrap_index.write[i] = wrap_index; } - int first_visible_line = get_first_visible_line() - 1; + int first_vis_line = get_first_visible_line() - 1; int draw_amount = visible_rows + (smooth_scroll_enabled ? 1 : 0); - draw_amount += draw_placeholder ? placeholder_wraped_rows.size() - 1 : get_line_wrap_count(first_visible_line + 1); + draw_amount += draw_placeholder ? placeholder_wraped_rows.size() - 1 : get_line_wrap_count(first_vis_line + 1); // Draw minimap. if (draw_minimap) { @@ -711,13 +711,13 @@ void TextEdit::_notification(int p_what) { // calculate viewport size and y offset int viewport_height = (draw_amount - 1) * minimap_line_height; int control_height = _get_control_height() - viewport_height; - int viewport_offset_y = round(get_scroll_pos_for_line(first_visible_line + 1) * control_height) / ((v_scroll->get_max() <= minimap_visible_lines) ? (minimap_visible_lines - draw_amount) : (v_scroll->get_max() - draw_amount)); + int viewport_offset_y = round(get_scroll_pos_for_line(first_vis_line + 1) * control_height) / ((v_scroll->get_max() <= minimap_visible_lines) ? (minimap_visible_lines - draw_amount) : (v_scroll->get_max() - draw_amount)); // calculate the first line. int num_lines_before = round((viewport_offset_y) / minimap_line_height); - int minimap_line = (v_scroll->get_max() <= minimap_visible_lines) ? -1 : first_visible_line; + int minimap_line = (v_scroll->get_max() <= minimap_visible_lines) ? -1 : first_vis_line; if (minimap_line >= 0) { - minimap_line -= get_next_visible_line_index_offset_from(first_visible_line, 0, -num_lines_before).x; + minimap_line -= get_next_visible_line_index_offset_from(first_vis_line, 0, -num_lines_before).x; minimap_line -= (minimap_line > 0 && smooth_scroll_enabled ? 1 : 0); } int minimap_draw_amount = minimap_visible_lines + get_line_wrap_count(minimap_line + 1); @@ -833,7 +833,7 @@ void TextEdit::_notification(int p_what) { continue; } - // If we've changed colour we are at the start of a new section, therefore we need to go back to the end + // If we've changed color we are at the start of a new section, therefore we need to go back to the end // of the previous section to draw it, we'll also add the character back on. if (color != previous_color) { characters--; @@ -885,7 +885,7 @@ void TextEdit::_notification(int p_what) { // Draw main text. line_drawing_cache.clear(); int row_height = draw_placeholder ? placeholder_line_height + line_spacing : get_line_height(); - int line = first_visible_line; + int line = first_vis_line; for (int i = 0; i < draw_amount; i++) { line++; @@ -1012,14 +1012,14 @@ void TextEdit::_notification(int p_what) { switch (gutter.type) { case GUTTER_TYPE_STRING: { - const String &text = get_line_gutter_text(line, g); - if (text.is_empty()) { + const String &txt = get_line_gutter_text(line, g); + if (txt.is_empty()) { break; } Ref<TextLine> tl; tl.instantiate(); - tl->add_string(text, font, font_size); + tl->add_string(txt, font, font_size); int yofs = ofs_y + (row_height - tl->get_size().y) / 2; if (outline_size > 0 && outline_color.a > 0) { @@ -1219,7 +1219,7 @@ void TextEdit::_notification(int p_what) { int sel_from = (line > get_selection_from_line(c)) ? TS->shaped_text_get_range(rid).x : get_selection_from_column(c); int sel_to = (line < get_selection_to_line(c)) ? TS->shaped_text_get_range(rid).y : get_selection_to_column(c); - if (glyphs[j].start >= sel_from && glyphs[j].end <= sel_to && override_selected_font_color) { + if (glyphs[j].start >= sel_from && glyphs[j].end <= sel_to && use_selected_font_color) { gl_color = font_selected_color; } } @@ -1350,9 +1350,14 @@ void TextEdit::_notification(int p_what) { draw_rect(ts_caret.t_caret, caret_color, overtype_mode); if (ts_caret.l_caret != Rect2() && ts_caret.l_dir != ts_caret.t_dir) { + // Draw split caret (leading part). ts_caret.l_caret.position += Vector2(char_margin + ofs_x, ofs_y); ts_caret.l_caret.size.x = caret_width; - draw_rect(ts_caret.l_caret, caret_color * Color(1, 1, 1, 0.5)); + draw_rect(ts_caret.l_caret, caret_color); + // Draw extra direction marker on top of split caret. + float d = (ts_caret.l_dir == TextServer::DIRECTION_LTR) ? 0.5 : -3; + Rect2 trect = Rect2(ts_caret.l_caret.position.x + d * caret_width, ts_caret.l_caret.position.y + ts_caret.l_caret.size.y - caret_width, 3 * caret_width, caret_width); + RenderingServer::get_singleton()->canvas_item_add_rect(ci, trect, caret_color); } } else { // End of the line. if (gl_size > 0) { @@ -1383,7 +1388,18 @@ void TextEdit::_notification(int p_what) { // Normal caret. if (ts_caret.l_caret != Rect2() && ts_caret.l_dir == TextServer::DIRECTION_AUTO) { // Draw extra marker on top of mid caret. - Rect2 trect = Rect2(ts_caret.l_caret.position.x - 3 * caret_width, ts_caret.l_caret.position.y, 6 * caret_width, caret_width); + Rect2 trect = Rect2(ts_caret.l_caret.position.x - 2.5 * caret_width, ts_caret.l_caret.position.y, 6 * caret_width, caret_width); + trect.position += Vector2(char_margin + ofs_x, ofs_y); + RenderingServer::get_singleton()->canvas_item_add_rect(ci, trect, caret_color); + } else if (ts_caret.l_caret != Rect2() && ts_caret.t_caret != Rect2() && ts_caret.l_dir != ts_caret.t_dir) { + // Draw extra direction marker on top of split caret. + float d = (ts_caret.l_dir == TextServer::DIRECTION_LTR) ? 0.5 : -3; + Rect2 trect = Rect2(ts_caret.l_caret.position.x + d * caret_width, ts_caret.l_caret.position.y + ts_caret.l_caret.size.y - caret_width, 3 * caret_width, caret_width); + trect.position += Vector2(char_margin + ofs_x, ofs_y); + RenderingServer::get_singleton()->canvas_item_add_rect(ci, trect, caret_color); + + d = (ts_caret.t_dir == TextServer::DIRECTION_LTR) ? 0.5 : -3; + trect = Rect2(ts_caret.t_caret.position.x + d * caret_width, ts_caret.t_caret.position.y, 3 * caret_width, caret_width); trect.position += Vector2(char_margin + ofs_x, ofs_y); RenderingServer::get_singleton()->canvas_item_add_rect(ci, trect, caret_color); } @@ -1676,7 +1692,7 @@ void TextEdit::gui_input(const Ref<InputEvent> &p_gui_input) { continue; } - if (mpos.x > left_margin && mpos.x <= (left_margin + gutters[i].width) - 3) { + if (mpos.x >= left_margin && mpos.x <= left_margin + gutters[i].width) { emit_signal(SNAME("gutter_clicked"), row, i); return; } @@ -1838,6 +1854,12 @@ void TextEdit::gui_input(const Ref<InputEvent> &p_gui_input) { } else { if (mb->get_button_index() == MouseButton::LEFT) { if (selection_drag_attempt && is_mouse_over_selection()) { + remove_secondary_carets(); + + Point2i pos = get_line_column_at_pos(get_local_mouse_pos()); + set_caret_line(pos.y, false, true, 0, 0); + set_caret_column(pos.x, true, 0); + deselect(); } dragging_minimap = false; @@ -1917,7 +1939,7 @@ void TextEdit::gui_input(const Ref<InputEvent> &p_gui_input) { continue; } - if (mpos.x > left_margin && mpos.x <= (left_margin + gutters[i].width) - 3) { + if (mpos.x >= left_margin && mpos.x < left_margin + gutters[i].width) { // We are in this gutter i's horizontal area. current_hovered_gutter = Vector2i(i, hovered_row); break; @@ -2035,7 +2057,8 @@ void TextEdit::gui_input(const Ref<InputEvent> &p_gui_input) { } if (is_shortcut_keys_enabled()) { - // SELECT ALL, SELECT WORD UNDER CARET, CUT, COPY, PASTE. + // SELECT ALL, SELECT WORD UNDER CARET, ADD SELECTION FOR NEXT OCCURRENCE, + // CLEAR CARETS AND SELECTIONS, CUT, COPY, PASTE. if (k->is_action("ui_text_select_all", true)) { select_all(); accept_event(); @@ -2046,6 +2069,18 @@ void TextEdit::gui_input(const Ref<InputEvent> &p_gui_input) { accept_event(); return; } + if (k->is_action("ui_text_add_selection_for_next_occurrence", true)) { + add_selection_for_next_occurrence(); + accept_event(); + return; + } + if (k->is_action("ui_text_clear_carets_and_selection", true)) { + // Since the default shortcut is ESC, accepts the event only if it's actually performed. + if (_clear_carets_and_selection()) { + accept_event(); + return; + } + } if (k->is_action("ui_cut", true)) { cut(); accept_event(); @@ -2073,6 +2108,17 @@ void TextEdit::gui_input(const Ref<InputEvent> &p_gui_input) { accept_event(); return; } + + if (k->is_action("ui_text_caret_add_below", true)) { + add_caret_at_carets(true); + accept_event(); + return; + } + if (k->is_action("ui_text_caret_add_above", true)) { + add_caret_at_carets(false); + accept_event(); + return; + } } // MISC. @@ -2176,8 +2222,17 @@ void TextEdit::gui_input(const Ref<InputEvent> &p_gui_input) { return; } - // Handle Unicode (if no modifiers active). Tab has a value of 0x09. - if (allow_unicode_handling && editable && (k->get_unicode() >= 32 || k->get_keycode() == Key::TAB)) { + // Handle tab as it has no set unicode value. + if (k->is_action("ui_text_indent", true)) { + if (editable) { + insert_text_at_caret("\t"); + } + accept_event(); + return; + } + + // Handle Unicode (if no modifiers active). + if (allow_unicode_handling && editable && k->get_unicode() >= 32) { handle_unicode_input(k->get_unicode()); accept_event(); return; @@ -2610,7 +2665,7 @@ void TextEdit::_do_backspace(bool p_word, bool p_all_to_left) { set_caret_line(get_caret_line(caret_idx), false, true, 0, caret_idx); set_caret_column(column, caret_idx == 0, caret_idx); - // Now we can clean up the overlaping caret. + // Now we can clean up the overlapping caret. if (overlapping_caret_index != -1) { backspace(overlapping_caret_index); i++; @@ -2778,6 +2833,65 @@ void TextEdit::_move_caret_document_end(bool p_select) { } } +bool TextEdit::_clear_carets_and_selection() { + if (get_caret_count() > 1) { + remove_secondary_carets(); + return true; + } + + if (has_selection()) { + deselect(); + return true; + } + + return false; +} + +void TextEdit::_get_above_below_caret_line_column(int p_old_line, int p_old_wrap_index, int p_old_column, bool p_below, int &p_new_line, int &p_new_column, int p_last_fit_x) const { + if (p_last_fit_x == -1) { + p_last_fit_x = _get_column_x_offset_for_line(p_old_column, p_old_line, p_old_column); + } + + // Calculate the new line and wrap index + p_new_line = p_old_line; + int caret_wrap_index = p_old_wrap_index; + if (p_below) { + if (caret_wrap_index < get_line_wrap_count(p_new_line)) { + caret_wrap_index++; + } else { + p_new_line++; + caret_wrap_index = 0; + } + } else { + if (caret_wrap_index == 0) { + p_new_line--; + caret_wrap_index = get_line_wrap_count(p_new_line); + } else { + caret_wrap_index--; + } + } + + // Boundary checks + if (p_new_line < 0) { + p_new_line = 0; + } + if (p_new_line >= text.size()) { + p_new_line = text.size() - 1; + } + + p_new_column = _get_char_pos_for_line(p_last_fit_x, p_new_line, caret_wrap_index); + if (p_new_column != 0 && get_line_wrapping_mode() != LineWrappingMode::LINE_WRAPPING_NONE && caret_wrap_index < get_line_wrap_count(p_new_line)) { + Vector<String> rows = get_line_wrapped_text(p_new_line); + int row_end_col = 0; + for (int i = 0; i < caret_wrap_index + 1; i++) { + row_end_col += rows[i].length(); + } + if (p_new_column >= row_end_col) { + p_new_column -= 1; + } + } +} + void TextEdit::_update_placeholder() { if (font.is_null() || font_size <= 0) { return; // Not in tree? @@ -2836,6 +2950,7 @@ void TextEdit::_update_caches() { /* Selection */ font_selected_color = get_theme_color(SNAME("font_selected_color")); selection_color = get_theme_color(SNAME("selection_color")); + use_selected_font_color = font_selected_color != Color(0, 0, 0, 0); /* Visual. */ style_normal = get_theme_stylebox(SNAME("normal")); @@ -2981,7 +3096,7 @@ Control::CursorShape TextEdit::get_cursor_shape(const Point2 &p_pos) const { continue; } - if (p_pos.x > left_margin && p_pos.x <= (left_margin + gutters[i].width) - 3) { + if (p_pos.x >= left_margin && p_pos.x < left_margin + gutters[i].width) { if (gutters[i].clickable || is_line_gutter_clickable(row, i)) { return CURSOR_POINTING_HAND; } @@ -3785,6 +3900,9 @@ void TextEdit::undo() { return; } + if (in_action) { + pending_action_end = true; + } _push_current_op(); if (undo_stack_pos == nullptr) { @@ -3838,7 +3956,7 @@ void TextEdit::undo() { } caret_pos_dirty = true; } - queue_redraw(); + adjust_viewport_to_caret(); } void TextEdit::redo() { @@ -3846,6 +3964,9 @@ void TextEdit::redo() { return; } + if (in_action) { + pending_action_end = true; + } _push_current_op(); if (undo_stack_pos == nullptr) { @@ -3890,7 +4011,7 @@ void TextEdit::redo() { } caret_pos_dirty = true; } - queue_redraw(); + adjust_viewport_to_caret(); } void TextEdit::clear_undo_history() { @@ -4113,7 +4234,7 @@ Point2i TextEdit::get_line_column_at_pos(const Point2i &p_pos, bool p_allow_out_ if (!p_allow_out_of_bounds) { return Point2i(-1, -1); } - return Point2i(text[row].size(), row); + return Point2i(text[row].length(), row); } int col = 0; @@ -4193,21 +4314,21 @@ int TextEdit::get_minimap_line_at_pos(const Point2i &p_pos) const { // calculate visible lines int minimap_visible_lines = get_minimap_visible_lines(); int visible_rows = get_visible_line_count() + 1; - int first_visible_line = get_first_visible_line() - 1; + int first_vis_line = get_first_visible_line() - 1; int draw_amount = visible_rows + (smooth_scroll_enabled ? 1 : 0); - draw_amount += get_line_wrap_count(first_visible_line + 1); + draw_amount += get_line_wrap_count(first_vis_line + 1); int minimap_line_height = (minimap_char_size.y + minimap_line_spacing); // calculate viewport size and y offset int viewport_height = (draw_amount - 1) * minimap_line_height; int control_height = _get_control_height() - viewport_height; - int viewport_offset_y = round(get_scroll_pos_for_line(first_visible_line + 1) * control_height) / ((v_scroll->get_max() <= minimap_visible_lines) ? (minimap_visible_lines - draw_amount) : (v_scroll->get_max() - draw_amount)); + int viewport_offset_y = round(get_scroll_pos_for_line(first_vis_line + 1) * control_height) / ((v_scroll->get_max() <= minimap_visible_lines) ? (minimap_visible_lines - draw_amount) : (v_scroll->get_max() - draw_amount)); // calculate the first line. int num_lines_before = round((viewport_offset_y) / minimap_line_height); - int minimap_line = (v_scroll->get_max() <= minimap_visible_lines) ? -1 : first_visible_line; - if (first_visible_line > 0 && minimap_line >= 0) { - minimap_line -= get_next_visible_line_index_offset_from(first_visible_line, 0, -num_lines_before).x; + int minimap_line = (v_scroll->get_max() <= minimap_visible_lines) ? -1 : first_vis_line; + if (first_vis_line > 0 && minimap_line >= 0) { + minimap_line -= get_next_visible_line_index_offset_from(first_vis_line, 0, -num_lines_before).x; minimap_line -= (minimap_line > 0 && smooth_scroll_enabled ? 1 : 0); } else { minimap_line = 0; @@ -4364,7 +4485,7 @@ int TextEdit::add_caret(int p_line, int p_col) { } void TextEdit::remove_caret(int p_caret) { - ERR_FAIL_COND(carets.size() <= 0); + ERR_FAIL_COND_MSG(carets.size() <= 1, "The main caret should not be removed."); ERR_FAIL_INDEX(p_caret, carets.size()); carets.remove_at(p_caret); caret_index_edit_dirty = true; @@ -4479,6 +4600,68 @@ int TextEdit::get_caret_count() const { return carets.size(); } +void TextEdit::add_caret_at_carets(bool p_below) { + Vector<int> caret_edit_order = get_caret_index_edit_order(); + for (const int &caret_index : caret_edit_order) { + const int caret_line = get_caret_line(caret_index); + const int caret_column = get_caret_column(caret_index); + + // The last fit x will be cleared if the caret has a selection, + // but if it does not have a selection the last fit x will be + // transferred to the new caret + int caret_from_column = 0, caret_to_column = 0, caret_last_fit_x = carets[caret_index].last_fit_x; + if (has_selection(caret_index)) { + // If the selection goes over multiple lines, deselect it. + if (get_selection_from_line(caret_index) != get_selection_to_line(caret_index)) { + deselect(caret_index); + } else { + caret_from_column = get_selection_from_column(caret_index); + caret_to_column = get_selection_to_column(caret_index); + caret_last_fit_x = -1; + carets.write[caret_index].last_fit_x = _get_column_x_offset_for_line(caret_column, caret_line, caret_column); + } + } + + // Get the line and column of the new caret as if you would move the caret by pressing the arrow keys + int new_caret_line, new_caret_column, new_caret_from_column = 0, new_caret_to_column = 0; + _get_above_below_caret_line_column(caret_line, get_caret_wrap_index(caret_index), caret_column, p_below, new_caret_line, new_caret_column, caret_last_fit_x); + + // If the caret does have a selection calculate the new from and to columns + if (caret_from_column != caret_to_column) { + // We only need to calculate the selection columns if the column of the caret changed + if (caret_column != new_caret_column) { + int _; // unused placeholder for p_new_line + _get_above_below_caret_line_column(caret_line, get_caret_wrap_index(caret_index), caret_from_column, p_below, _, new_caret_from_column); + _get_above_below_caret_line_column(caret_line, get_caret_wrap_index(caret_index), caret_to_column, p_below, _, new_caret_to_column); + } else { + new_caret_from_column = caret_from_column; + new_caret_to_column = caret_to_column; + } + } + + // Add the new caret + const int new_caret_index = add_caret(new_caret_line, new_caret_column); + + if (new_caret_index == -1) { + continue; + } + // Also add the selection if there should be one + if (new_caret_from_column != new_caret_to_column) { + select(new_caret_line, new_caret_from_column, new_caret_line, new_caret_to_column, new_caret_index); + // Necessary to properly modify the selection after adding the new caret + carets.write[new_caret_index].selection.selecting_line = new_caret_line; + carets.write[new_caret_index].selection.selecting_column = new_caret_column == new_caret_from_column ? new_caret_to_column : new_caret_from_column; + continue; + } + + // Copy the last fit x over + carets.write[new_caret_index].last_fit_x = carets[caret_index].last_fit_x; + } + + merge_overlapping_carets(); + queue_redraw(); +} + Vector<int> TextEdit::get_caret_index_edit_order() { if (!caret_index_edit_dirty) { return caret_index_edit_order; @@ -4714,14 +4897,6 @@ bool TextEdit::is_drag_and_drop_selection_enabled() const { return drag_and_drop_selection_enabled; } -void TextEdit::set_override_selected_font_color(bool p_override_selected_font_color) { - override_selected_font_color = p_override_selected_font_color; -} - -bool TextEdit::is_overriding_selected_font_color() const { - return override_selected_font_color; -} - void TextEdit::set_selection_mode(SelectionMode p_mode, int p_line, int p_column, int p_caret) { ERR_FAIL_INDEX(p_caret, carets.size()); @@ -4800,10 +4975,50 @@ void TextEdit::select_word_under_caret(int p_caret) { continue; } - select(get_caret_line(c), begin, get_caret_column(c), end, c); + select(get_caret_line(c), begin, get_caret_line(c), end, c); // Move the caret to the end of the word for easier editing. set_caret_column(end, false, c); } + merge_overlapping_carets(); +} + +void TextEdit::add_selection_for_next_occurrence() { + if (!selecting_enabled || !is_multiple_carets_enabled()) { + return; + } + + if (text.size() == 1 && text[0].length() == 0) { + return; + } + + // Always use the last caret, to correctly search for + // the next occurrence that comes after this caret. + int caret = get_caret_count() - 1; + + if (!has_selection(caret)) { + select_word_under_caret(caret); + return; + } + + const String &highlighted_text = get_selected_text(caret); + int column = get_selection_from_column(caret) + 1; + int line = get_caret_line(caret); + + const Point2i next_occurrence = search(highlighted_text, SEARCH_MATCH_CASE, line, column); + + if (next_occurrence.x == -1 || next_occurrence.y == -1) { + return; + } + + int to_column = get_selection_to_column(caret) + 1; + int end = next_occurrence.x + (to_column - column); + int new_caret = add_caret(next_occurrence.y, end); + + if (new_caret != -1) { + select(next_occurrence.y, next_occurrence.x, next_occurrence.y, end, new_caret); + adjust_viewport_to_caret(new_caret); + merge_overlapping_carets(); + } } void TextEdit::select(int p_from_line, int p_from_column, int p_to_line, int p_to_column, int p_caret) { @@ -5072,6 +5287,14 @@ bool TextEdit::is_scroll_past_end_of_file_enabled() const { return scroll_past_end_of_file_enabled; } +VScrollBar *TextEdit::get_v_scroll_bar() const { + return v_scroll; +} + +HScrollBar *TextEdit::get_h_scroll_bar() const { + return h_scroll; +} + void TextEdit::set_v_scroll(double p_scroll) { v_scroll->set_value(p_scroll); int max_v_scroll = v_scroll->get_max() - v_scroll->get_page(); @@ -5925,6 +6148,7 @@ void TextEdit::_bind_methods() { ClassDB::bind_method(D_METHOD("remove_secondary_carets"), &TextEdit::remove_secondary_carets); ClassDB::bind_method(D_METHOD("merge_overlapping_carets"), &TextEdit::merge_overlapping_carets); ClassDB::bind_method(D_METHOD("get_caret_count"), &TextEdit::get_caret_count); + ClassDB::bind_method(D_METHOD("add_caret_at_carets", "below"), &TextEdit::add_caret_at_carets); ClassDB::bind_method(D_METHOD("get_caret_index_edit_order"), &TextEdit::get_caret_index_edit_order); ClassDB::bind_method(D_METHOD("adjust_carets_after_edit", "caret", "from_line", "from_col", "to_line", "to_col"), &TextEdit::adjust_carets_after_edit); @@ -5958,14 +6182,12 @@ void TextEdit::_bind_methods() { ClassDB::bind_method(D_METHOD("set_drag_and_drop_selection_enabled", "enable"), &TextEdit::set_drag_and_drop_selection_enabled); ClassDB::bind_method(D_METHOD("is_drag_and_drop_selection_enabled"), &TextEdit::is_drag_and_drop_selection_enabled); - ClassDB::bind_method(D_METHOD("set_override_selected_font_color", "override"), &TextEdit::set_override_selected_font_color); - ClassDB::bind_method(D_METHOD("is_overriding_selected_font_color"), &TextEdit::is_overriding_selected_font_color); - ClassDB::bind_method(D_METHOD("set_selection_mode", "mode", "line", "column", "caret_index"), &TextEdit::set_selection_mode, DEFVAL(-1), DEFVAL(-1), DEFVAL(0)); ClassDB::bind_method(D_METHOD("get_selection_mode"), &TextEdit::get_selection_mode); ClassDB::bind_method(D_METHOD("select_all"), &TextEdit::select_all); ClassDB::bind_method(D_METHOD("select_word_under_caret", "caret_index"), &TextEdit::select_word_under_caret, DEFVAL(-1)); + ClassDB::bind_method(D_METHOD("add_selection_for_next_occurrence"), &TextEdit::add_selection_for_next_occurrence); ClassDB::bind_method(D_METHOD("select", "from_line", "from_column", "to_line", "to_column", "caret_index"), &TextEdit::select, DEFVAL(0)); ClassDB::bind_method(D_METHOD("has_selection", "caret_index"), &TextEdit::has_selection, DEFVAL(-1)); @@ -6004,6 +6226,9 @@ void TextEdit::_bind_methods() { ClassDB::bind_method(D_METHOD("set_smooth_scroll_enabled", "enable"), &TextEdit::set_smooth_scroll_enabled); ClassDB::bind_method(D_METHOD("is_smooth_scroll_enabled"), &TextEdit::is_smooth_scroll_enabled); + ClassDB::bind_method(D_METHOD("get_v_scroll_bar"), &TextEdit::get_v_scroll_bar); + ClassDB::bind_method(D_METHOD("get_h_scroll_bar"), &TextEdit::get_h_scroll_bar); + ClassDB::bind_method(D_METHOD("set_v_scroll", "value"), &TextEdit::set_v_scroll); ClassDB::bind_method(D_METHOD("get_v_scroll"), &TextEdit::get_v_scroll); @@ -6126,7 +6351,6 @@ void TextEdit::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::BOOL, "middle_mouse_paste_enabled"), "set_middle_mouse_paste_enabled", "is_middle_mouse_paste_enabled"); ADD_PROPERTY(PropertyInfo(Variant::INT, "wrap_mode", PROPERTY_HINT_ENUM, "None,Boundary"), "set_line_wrapping_mode", "get_line_wrapping_mode"); - ADD_PROPERTY(PropertyInfo(Variant::BOOL, "override_selected_font_color"), "set_override_selected_font_color", "is_overriding_selected_font_color"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "highlight_all_occurrences"), "set_highlight_all_occurrences", "is_highlight_all_occurrences_enabled"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "highlight_current_line"), "set_highlight_current_line", "is_highlight_current_line_enabled"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "draw_control_chars"), "set_draw_control_chars", "get_draw_control_chars"); @@ -6342,7 +6566,7 @@ void TextEdit::_cut_internal(int p_caret) { int indent_level = get_indent_level(cl); double hscroll = get_h_scroll(); - // Check for overlaping carets. + // Check for overlapping carets. // We don't need to worry about selections as that is caught before this entire section. for (int j = i - 1; j >= 0; j--) { if (get_caret_line(caret_edit_order[j]) == cl) { @@ -7324,10 +7548,10 @@ void TextEdit::_remove_text(int p_from_line, int p_from_column, int p_to_line, i idle_detect->start(); } - String text; + String txt; if (undo_enabled) { _clear_redo(); - text = _base_get_text(p_from_line, p_from_column, p_to_line, p_to_column); + txt = _base_get_text(p_from_line, p_from_column, p_to_line, p_to_column); } _base_remove_text(p_from_line, p_from_column, p_to_line, p_to_column); @@ -7343,7 +7567,7 @@ void TextEdit::_remove_text(int p_from_line, int p_from_column, int p_to_line, i op.from_column = p_from_column; op.to_line = p_to_line; op.to_column = p_to_column; - op.text = text; + op.text = txt; op.version = ++version; op.chain_forward = false; op.chain_backward = false; @@ -7360,7 +7584,7 @@ void TextEdit::_remove_text(int p_from_line, int p_from_column, int p_to_line, i // See if it can be merged. if (current_op.from_line == p_to_line && current_op.from_column == p_to_column) { // Backspace or similar. - current_op.text = text + current_op.text; + current_op.text = txt + current_op.text; current_op.from_line = p_from_line; current_op.from_column = p_from_column; current_op.end_carets = carets; diff --git a/scene/gui/text_edit.h b/scene/gui/text_edit.h index a8e087909e..92acbaf521 100644 --- a/scene/gui/text_edit.h +++ b/scene/gui/text_edit.h @@ -443,9 +443,9 @@ private: bool deselect_on_focus_loss_enabled = true; bool drag_and_drop_selection_enabled = true; - Color font_selected_color = Color(1, 1, 1); + Color font_selected_color = Color(0, 0, 0, 0); Color selection_color = Color(1, 1, 1); - bool override_selected_font_color = false; + bool use_selected_font_color = false; bool selection_drag_attempt = false; bool dragging_selection = false; @@ -597,6 +597,10 @@ private: void _delete(bool p_word = false, bool p_all_to_right = false); void _move_caret_document_start(bool p_select); void _move_caret_document_end(bool p_select); + bool _clear_carets_and_selection(); + + // Used in add_caret_at_carets + void _get_above_below_caret_line_column(int p_old_line, int p_old_wrap_index, int p_old_column, bool p_below, int &p_new_line, int &p_new_column, int p_last_fit_x = -1) const; protected: void _notification(int p_what); @@ -816,6 +820,7 @@ public: void remove_secondary_carets(); void merge_overlapping_carets(); int get_caret_count() const; + void add_caret_at_carets(bool p_below); Vector<int> get_caret_index_edit_order(); void adjust_carets_after_edit(int p_caret, int p_from_line, int p_from_col, int p_to_line, int p_to_col); @@ -851,6 +856,7 @@ public: void select_all(); void select_word_under_caret(int p_caret = -1); + void add_selection_for_next_occurrence(); void select(int p_from_line, int p_from_column, int p_to_line, int p_to_column, int p_caret = 0); bool has_selection(int p_caret = -1) const; @@ -886,6 +892,9 @@ public: void set_scroll_past_end_of_file_enabled(const bool p_enabled); bool is_scroll_past_end_of_file_enabled() const; + VScrollBar *get_v_scroll_bar() const; + HScrollBar *get_h_scroll_bar() const; + void set_v_scroll(double p_scroll); double get_v_scroll() const; diff --git a/scene/gui/texture_button.cpp b/scene/gui/texture_button.cpp index d9ab1c2c55..ccdf56c1d7 100644 --- a/scene/gui/texture_button.cpp +++ b/scene/gui/texture_button.cpp @@ -64,7 +64,7 @@ Size2 TextureButton::get_minimum_size() const { bool TextureButton::has_point(const Point2 &p_point) const { if (click_mask.is_valid()) { Point2 point = p_point; - Rect2 rect = Rect2(); + Rect2 rect; Size2 mask_size = click_mask->get_size(); if (!_position_rect.has_area()) { @@ -250,11 +250,11 @@ void TextureButton::_notification(int p_what) { } void TextureButton::_bind_methods() { - ClassDB::bind_method(D_METHOD("set_normal_texture", "texture"), &TextureButton::set_normal_texture); - ClassDB::bind_method(D_METHOD("set_pressed_texture", "texture"), &TextureButton::set_pressed_texture); - ClassDB::bind_method(D_METHOD("set_hover_texture", "texture"), &TextureButton::set_hover_texture); - ClassDB::bind_method(D_METHOD("set_disabled_texture", "texture"), &TextureButton::set_disabled_texture); - ClassDB::bind_method(D_METHOD("set_focused_texture", "texture"), &TextureButton::set_focused_texture); + ClassDB::bind_method(D_METHOD("set_texture_normal", "texture"), &TextureButton::set_texture_normal); + ClassDB::bind_method(D_METHOD("set_texture_pressed", "texture"), &TextureButton::set_texture_pressed); + ClassDB::bind_method(D_METHOD("set_texture_hover", "texture"), &TextureButton::set_texture_hover); + ClassDB::bind_method(D_METHOD("set_texture_disabled", "texture"), &TextureButton::set_texture_disabled); + ClassDB::bind_method(D_METHOD("set_texture_focused", "texture"), &TextureButton::set_texture_focused); ClassDB::bind_method(D_METHOD("set_click_mask", "mask"), &TextureButton::set_click_mask); ClassDB::bind_method(D_METHOD("set_ignore_texture_size", "ignore"), &TextureButton::set_ignore_texture_size); ClassDB::bind_method(D_METHOD("set_stretch_mode", "mode"), &TextureButton::set_stretch_mode); @@ -263,21 +263,21 @@ void TextureButton::_bind_methods() { ClassDB::bind_method(D_METHOD("set_flip_v", "enable"), &TextureButton::set_flip_v); ClassDB::bind_method(D_METHOD("is_flipped_v"), &TextureButton::is_flipped_v); - ClassDB::bind_method(D_METHOD("get_normal_texture"), &TextureButton::get_normal_texture); - ClassDB::bind_method(D_METHOD("get_pressed_texture"), &TextureButton::get_pressed_texture); - ClassDB::bind_method(D_METHOD("get_hover_texture"), &TextureButton::get_hover_texture); - ClassDB::bind_method(D_METHOD("get_disabled_texture"), &TextureButton::get_disabled_texture); - ClassDB::bind_method(D_METHOD("get_focused_texture"), &TextureButton::get_focused_texture); + ClassDB::bind_method(D_METHOD("get_texture_normal"), &TextureButton::get_texture_normal); + ClassDB::bind_method(D_METHOD("get_texture_pressed"), &TextureButton::get_texture_pressed); + ClassDB::bind_method(D_METHOD("get_texture_hover"), &TextureButton::get_texture_hover); + ClassDB::bind_method(D_METHOD("get_texture_disabled"), &TextureButton::get_texture_disabled); + ClassDB::bind_method(D_METHOD("get_texture_focused"), &TextureButton::get_texture_focused); ClassDB::bind_method(D_METHOD("get_click_mask"), &TextureButton::get_click_mask); ClassDB::bind_method(D_METHOD("get_ignore_texture_size"), &TextureButton::get_ignore_texture_size); ClassDB::bind_method(D_METHOD("get_stretch_mode"), &TextureButton::get_stretch_mode); ADD_GROUP("Textures", "texture_"); - ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "texture_normal", PROPERTY_HINT_RESOURCE_TYPE, "Texture2D"), "set_normal_texture", "get_normal_texture"); - ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "texture_pressed", PROPERTY_HINT_RESOURCE_TYPE, "Texture2D"), "set_pressed_texture", "get_pressed_texture"); - ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "texture_hover", PROPERTY_HINT_RESOURCE_TYPE, "Texture2D"), "set_hover_texture", "get_hover_texture"); - ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "texture_disabled", PROPERTY_HINT_RESOURCE_TYPE, "Texture2D"), "set_disabled_texture", "get_disabled_texture"); - ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "texture_focused", PROPERTY_HINT_RESOURCE_TYPE, "Texture2D"), "set_focused_texture", "get_focused_texture"); + ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "texture_normal", PROPERTY_HINT_RESOURCE_TYPE, "Texture2D"), "set_texture_normal", "get_texture_normal"); + ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "texture_pressed", PROPERTY_HINT_RESOURCE_TYPE, "Texture2D"), "set_texture_pressed", "get_texture_pressed"); + ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "texture_hover", PROPERTY_HINT_RESOURCE_TYPE, "Texture2D"), "set_texture_hover", "get_texture_hover"); + ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "texture_disabled", PROPERTY_HINT_RESOURCE_TYPE, "Texture2D"), "set_texture_disabled", "get_texture_disabled"); + ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "texture_focused", PROPERTY_HINT_RESOURCE_TYPE, "Texture2D"), "set_texture_focused", "get_texture_focused"); ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "texture_click_mask", PROPERTY_HINT_RESOURCE_TYPE, "BitMap"), "set_click_mask", "get_click_mask"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "ignore_texture_size", PROPERTY_HINT_RESOURCE_TYPE, "bool"), "set_ignore_texture_size", "get_ignore_texture_size"); ADD_PROPERTY(PropertyInfo(Variant::INT, "stretch_mode", PROPERTY_HINT_ENUM, "Scale,Tile,Keep,Keep Centered,Keep Aspect,Keep Aspect Centered,Keep Aspect Covered"), "set_stretch_mode", "get_stretch_mode"); @@ -293,7 +293,7 @@ void TextureButton::_bind_methods() { BIND_ENUM_CONSTANT(STRETCH_KEEP_ASPECT_COVERED); } -void TextureButton::set_normal_texture(const Ref<Texture2D> &p_normal) { +void TextureButton::set_texture_normal(const Ref<Texture2D> &p_normal) { if (normal == p_normal) { return; } @@ -303,7 +303,7 @@ void TextureButton::set_normal_texture(const Ref<Texture2D> &p_normal) { update_minimum_size(); } -void TextureButton::set_pressed_texture(const Ref<Texture2D> &p_pressed) { +void TextureButton::set_texture_pressed(const Ref<Texture2D> &p_pressed) { if (pressed == p_pressed) { return; } @@ -313,7 +313,7 @@ void TextureButton::set_pressed_texture(const Ref<Texture2D> &p_pressed) { update_minimum_size(); } -void TextureButton::set_hover_texture(const Ref<Texture2D> &p_hover) { +void TextureButton::set_texture_hover(const Ref<Texture2D> &p_hover) { if (hover == p_hover) { return; } @@ -323,7 +323,7 @@ void TextureButton::set_hover_texture(const Ref<Texture2D> &p_hover) { update_minimum_size(); } -void TextureButton::set_disabled_texture(const Ref<Texture2D> &p_disabled) { +void TextureButton::set_texture_disabled(const Ref<Texture2D> &p_disabled) { if (disabled == p_disabled) { return; } @@ -341,19 +341,19 @@ void TextureButton::set_click_mask(const Ref<BitMap> &p_click_mask) { update_minimum_size(); } -Ref<Texture2D> TextureButton::get_normal_texture() const { +Ref<Texture2D> TextureButton::get_texture_normal() const { return normal; } -Ref<Texture2D> TextureButton::get_pressed_texture() const { +Ref<Texture2D> TextureButton::get_texture_pressed() const { return pressed; } -Ref<Texture2D> TextureButton::get_hover_texture() const { +Ref<Texture2D> TextureButton::get_texture_hover() const { return hover; } -Ref<Texture2D> TextureButton::get_disabled_texture() const { +Ref<Texture2D> TextureButton::get_texture_disabled() const { return disabled; } @@ -361,11 +361,11 @@ Ref<BitMap> TextureButton::get_click_mask() const { return click_mask; } -Ref<Texture2D> TextureButton::get_focused_texture() const { +Ref<Texture2D> TextureButton::get_texture_focused() const { return focused; }; -void TextureButton::set_focused_texture(const Ref<Texture2D> &p_focused) { +void TextureButton::set_texture_focused(const Ref<Texture2D> &p_focused) { focused = p_focused; }; diff --git a/scene/gui/texture_button.h b/scene/gui/texture_button.h index 9f6f7c1515..4b6d5b5bec 100644 --- a/scene/gui/texture_button.h +++ b/scene/gui/texture_button.h @@ -71,18 +71,18 @@ protected: static void _bind_methods(); public: - void set_normal_texture(const Ref<Texture2D> &p_normal); - void set_pressed_texture(const Ref<Texture2D> &p_pressed); - void set_hover_texture(const Ref<Texture2D> &p_hover); - void set_disabled_texture(const Ref<Texture2D> &p_disabled); - void set_focused_texture(const Ref<Texture2D> &p_focused); + void set_texture_normal(const Ref<Texture2D> &p_normal); + void set_texture_pressed(const Ref<Texture2D> &p_pressed); + void set_texture_hover(const Ref<Texture2D> &p_hover); + void set_texture_disabled(const Ref<Texture2D> &p_disabled); + void set_texture_focused(const Ref<Texture2D> &p_focused); void set_click_mask(const Ref<BitMap> &p_click_mask); - Ref<Texture2D> get_normal_texture() const; - Ref<Texture2D> get_pressed_texture() const; - Ref<Texture2D> get_hover_texture() const; - Ref<Texture2D> get_disabled_texture() const; - Ref<Texture2D> get_focused_texture() const; + Ref<Texture2D> get_texture_normal() const; + Ref<Texture2D> get_texture_pressed() const; + Ref<Texture2D> get_texture_hover() const; + Ref<Texture2D> get_texture_disabled() const; + Ref<Texture2D> get_texture_focused() const; Ref<BitMap> get_click_mask() const; bool get_ignore_texture_size() const; diff --git a/scene/gui/texture_progress_bar.cpp b/scene/gui/texture_progress_bar.cpp index a9982b3ece..48c6dc5cfc 100644 --- a/scene/gui/texture_progress_bar.cpp +++ b/scene/gui/texture_progress_bar.cpp @@ -510,18 +510,38 @@ void TextureProgressBar::_notification(int p_what) { } pts.append(to); + Ref<AtlasTexture> atlas_progress = progress; + bool valid_atlas_progress = atlas_progress.is_valid() && atlas_progress->get_atlas().is_valid(); + Rect2 region_rect; + Size2 atlas_size; + if (valid_atlas_progress) { + region_rect = atlas_progress->get_region(); + atlas_size = atlas_progress->get_atlas()->get_size(); + } + Vector<Point2> uvs; Vector<Point2> points; - uvs.push_back(get_relative_center()); - points.push_back(progress_offset + s * get_relative_center()); for (int i = 0; i < pts.size(); i++) { Point2 uv = unit_val_to_uv(pts[i]); if (uvs.find(uv) >= 0) { continue; } - uvs.push_back(uv); points.push_back(progress_offset + Point2(uv.x * s.x, uv.y * s.y)); + if (valid_atlas_progress) { + uv.x = Math::remap(uv.x, 0, 1, region_rect.position.x / atlas_size.x, (region_rect.position.x + region_rect.size.x) / atlas_size.x); + uv.y = Math::remap(uv.y, 0, 1, region_rect.position.y / atlas_size.y, (region_rect.position.y + region_rect.size.y) / atlas_size.y); + } + uvs.push_back(uv); + } + + Point2 center_point = get_relative_center(); + points.push_back(progress_offset + s * center_point); + if (valid_atlas_progress) { + center_point.x = Math::remap(center_point.x, 0, 1, region_rect.position.x / atlas_size.x, (region_rect.position.x + region_rect.size.x) / atlas_size.x); + center_point.y = Math::remap(center_point.y, 0, 1, region_rect.position.y / atlas_size.y, (region_rect.position.y + region_rect.size.y) / atlas_size.y); } + uvs.push_back(center_point); + Vector<Color> colors; colors.push_back(tint_progress); draw_polygon(points, colors, uvs, progress); diff --git a/scene/gui/tree.cpp b/scene/gui/tree.cpp index db7d390b16..2da76883b4 100644 --- a/scene/gui/tree.cpp +++ b/scene/gui/tree.cpp @@ -738,9 +738,9 @@ TreeItem *TreeItem::get_first_child() const { TreeItem *TreeItem::_get_prev_visible(bool p_wrap) { TreeItem *current = this; - TreeItem *prev = current->get_prev(); + TreeItem *prev_item = current->get_prev(); - if (!prev) { + if (!prev_item) { current = current->parent; if (current == tree->root && tree->hide_root) { return nullptr; @@ -757,7 +757,7 @@ TreeItem *TreeItem::_get_prev_visible(bool p_wrap) { } } } else { - current = prev; + current = prev_item; while (!current->collapsed && current->first_child) { //go to the very end @@ -773,16 +773,16 @@ TreeItem *TreeItem::_get_prev_visible(bool p_wrap) { TreeItem *TreeItem::get_prev_visible(bool p_wrap) { TreeItem *loop = this; - TreeItem *prev = this->_get_prev_visible(p_wrap); - while (prev && !prev->is_visible()) { - prev = prev->_get_prev_visible(p_wrap); - if (prev == loop) { + TreeItem *prev_item = this->_get_prev_visible(p_wrap); + while (prev_item && !prev_item->is_visible()) { + prev_item = prev_item->_get_prev_visible(p_wrap); + if (prev_item == loop) { // Check that we haven't looped all the way around to the start. - prev = nullptr; + prev_item = nullptr; break; } } - return prev; + return prev_item; } TreeItem *TreeItem::_get_next_visible(bool p_wrap) { @@ -814,16 +814,16 @@ TreeItem *TreeItem::_get_next_visible(bool p_wrap) { TreeItem *TreeItem::get_next_visible(bool p_wrap) { TreeItem *loop = this; - TreeItem *next = this->_get_next_visible(p_wrap); - while (next && !next->is_visible()) { - next = next->_get_next_visible(p_wrap); - if (next == loop) { + TreeItem *next_item = this->_get_next_visible(p_wrap); + while (next_item && !next_item->is_visible()) { + next_item = next_item->_get_next_visible(p_wrap); + if (next_item == loop) { // Check that we haven't looped all the way around to the start. - next = nullptr; + next_item = nullptr; break; } } - return next; + return next_item; } TreeItem *TreeItem::get_child(int p_idx) { @@ -1317,8 +1317,8 @@ bool TreeItem::is_folding_disabled() const { Size2 TreeItem::get_minimum_size(int p_column) { ERR_FAIL_INDEX_V(p_column, cells.size(), Size2()); - Tree *tree = get_tree(); - ERR_FAIL_COND_V(!tree, Size2()); + Tree *parent_tree = get_tree(); + ERR_FAIL_COND_V(!parent_tree, Size2()); const TreeItem::Cell &cell = cells[p_column]; @@ -1328,7 +1328,7 @@ Size2 TreeItem::get_minimum_size(int p_column) { // Text. if (!cell.text.is_empty()) { if (cell.dirty) { - tree->update_item_cell(this, p_column); + parent_tree->update_item_cell(this, p_column); } Size2 text_size = cell.text_buf->get_size(); size.width += text_size.width; @@ -1337,14 +1337,14 @@ Size2 TreeItem::get_minimum_size(int p_column) { // Icon. if (cell.mode == CELL_MODE_CHECK) { - size.width += tree->theme_cache.checked->get_width() + tree->theme_cache.hseparation; + size.width += parent_tree->theme_cache.checked->get_width() + parent_tree->theme_cache.hseparation; } if (cell.icon.is_valid()) { Size2i icon_size = cell.get_icon_size(); if (cell.icon_max_w > 0 && icon_size.width > cell.icon_max_w) { icon_size.width = cell.icon_max_w; } - size.width += icon_size.width + tree->theme_cache.hseparation; + size.width += icon_size.width + parent_tree->theme_cache.hseparation; size.height = MAX(size.height, icon_size.height); } @@ -1352,13 +1352,13 @@ Size2 TreeItem::get_minimum_size(int p_column) { for (int i = 0; i < cell.buttons.size(); i++) { Ref<Texture2D> texture = cell.buttons[i].texture; if (texture.is_valid()) { - Size2 button_size = texture->get_size() + tree->theme_cache.button_pressed->get_minimum_size(); + Size2 button_size = texture->get_size() + parent_tree->theme_cache.button_pressed->get_minimum_size(); size.width += button_size.width; size.height = MAX(size.height, button_size.height); } } if (cell.buttons.size() >= 2) { - size.width += (cell.buttons.size() - 1) * tree->theme_cache.button_margin; + size.width += (cell.buttons.size() - 1) * parent_tree->theme_cache.button_margin; } cells.write[p_column].cached_minimum_size = size; @@ -1504,6 +1504,7 @@ void TreeItem::_bind_methods() { ClassDB::bind_method(D_METHOD("set_button", "column", "button_idx", "button"), &TreeItem::set_button); ClassDB::bind_method(D_METHOD("erase_button", "column", "button_idx"), &TreeItem::erase_button); ClassDB::bind_method(D_METHOD("set_button_disabled", "column", "button_idx", "disabled"), &TreeItem::set_button_disabled); + ClassDB::bind_method(D_METHOD("set_button_color", "column", "button_idx", "color"), &TreeItem::set_button_color); ClassDB::bind_method(D_METHOD("is_button_disabled", "column", "button_idx"), &TreeItem::is_button_disabled); ClassDB::bind_method(D_METHOD("set_tooltip_text", "column", "tooltip"), &TreeItem::set_tooltip_text); @@ -3190,7 +3191,7 @@ void Tree::gui_input(const Ref<InputEvent> &p_event) { Ref<InputEventKey> k = p_event; bool is_command = k.is_valid() && k->is_command_or_control_pressed(); - if (p_event->is_action("ui_right") && p_event->is_pressed()) { + if (p_event->is_action("ui_right", true) && p_event->is_pressed()) { if (!cursor_can_exit_tree) { accept_event(); } @@ -3208,7 +3209,7 @@ void Tree::gui_input(const Ref<InputEvent> &p_event) { } else { _go_right(); } - } else if (p_event->is_action("ui_left") && p_event->is_pressed()) { + } else if (p_event->is_action("ui_left", true) && p_event->is_pressed()) { if (!cursor_can_exit_tree) { accept_event(); } @@ -3228,21 +3229,21 @@ void Tree::gui_input(const Ref<InputEvent> &p_event) { _go_left(); } - } else if (p_event->is_action("ui_up") && p_event->is_pressed() && !is_command) { + } else if (p_event->is_action("ui_up", true) && p_event->is_pressed() && !is_command) { if (!cursor_can_exit_tree) { accept_event(); } _go_up(); - } else if (p_event->is_action("ui_down") && p_event->is_pressed() && !is_command) { + } else if (p_event->is_action("ui_down", true) && p_event->is_pressed() && !is_command) { if (!cursor_can_exit_tree) { accept_event(); } _go_down(); - } else if (p_event->is_action("ui_page_down") && p_event->is_pressed()) { + } else if (p_event->is_action("ui_page_down", true) && p_event->is_pressed()) { if (!cursor_can_exit_tree) { accept_event(); } @@ -3280,7 +3281,7 @@ void Tree::gui_input(const Ref<InputEvent> &p_event) { } ensure_cursor_is_visible(); - } else if (p_event->is_action("ui_page_up") && p_event->is_pressed()) { + } else if (p_event->is_action("ui_page_up", true) && p_event->is_pressed()) { if (!cursor_can_exit_tree) { accept_event(); } @@ -3317,7 +3318,7 @@ void Tree::gui_input(const Ref<InputEvent> &p_event) { prev->select(selected_col); } ensure_cursor_is_visible(); - } else if (p_event->is_action("ui_accept") && p_event->is_pressed()) { + } else if (p_event->is_action("ui_accept", true) && p_event->is_pressed()) { if (selected_item) { //bring up editor if possible if (!edit_selected()) { @@ -3326,7 +3327,7 @@ void Tree::gui_input(const Ref<InputEvent> &p_event) { } } accept_event(); - } else if (p_event->is_action("ui_select") && p_event->is_pressed()) { + } else if (p_event->is_action("ui_select", true) && p_event->is_pressed()) { if (select_mode == SELECT_MULTI) { if (!selected_item) { return; @@ -4337,6 +4338,12 @@ TreeItem *Tree::get_selected() const { return selected_item; } +void Tree::set_selected(TreeItem *p_item, int p_column) { + ERR_FAIL_INDEX(p_column, columns.size()); + ERR_FAIL_COND(!p_item); + select_single_item(p_item, get_root(), p_column); +} + int Tree::get_selected_column() const { return selected_col; } @@ -4546,6 +4553,7 @@ void Tree::ensure_cursor_is_visible() { return; // Nothing under cursor. } + // Note: Code below similar to Tree::scroll_to_item(), in case of bug fix both. const Size2 area_size = get_size() - theme_cache.panel_style->get_minimum_size(); int y_offset = get_item_offset(selected_item); @@ -4554,7 +4562,10 @@ void Tree::ensure_cursor_is_visible() { y_offset -= tbh; const int cell_h = compute_item_height(selected_item) + theme_cache.vseparation; - const int screen_h = area_size.height - h_scroll->get_combined_minimum_size().height - tbh; + int screen_h = area_size.height - tbh; + if (h_scroll->is_visible()) { + screen_h -= h_scroll->get_combined_minimum_size().height; + } if (cell_h > screen_h) { // Screen size is too small, maybe it was not resized yet. v_scroll->set_value(y_offset); @@ -4705,26 +4716,32 @@ Point2 Tree::get_scroll() const { void Tree::scroll_to_item(TreeItem *p_item, bool p_center_on_item) { ERR_FAIL_NULL(p_item); - if (!is_visible_in_tree() || !p_item->is_visible()) { - return; // Hack to work around crash in get_item_rect() if Tree is not in tree. - } update_scrollbars(); - const real_t tree_height = get_size().y; - const Rect2 item_rect = get_item_rect(p_item); - const real_t item_y = item_rect.position.y; - const real_t item_height = item_rect.size.y + theme_cache.vseparation; + // Note: Code below similar to Tree::ensure_cursor_is_visible(), in case of bug fix both. + const Size2 area_size = get_size() - theme_cache.panel_style->get_minimum_size(); - if (p_center_on_item) { - v_scroll->set_value(item_y - (tree_height - item_height) / 2.0f); - } else { - if (item_y < v_scroll->get_value()) { - v_scroll->set_value(item_y); + int y_offset = get_item_offset(p_item); + if (y_offset != -1) { + const int tbh = _get_title_button_height(); + y_offset -= tbh; + + const int cell_h = compute_item_height(p_item) + theme_cache.vseparation; + int screen_h = area_size.height - tbh; + if (h_scroll->is_visible()) { + screen_h -= h_scroll->get_combined_minimum_size().height; + } + + if (p_center_on_item) { + v_scroll->set_value(y_offset - (screen_h - cell_h) / 2.0f); } else { - const real_t new_position = item_y + item_height - tree_height; - if (new_position > v_scroll->get_value()) { - v_scroll->set_value(new_position); + if (cell_h > screen_h) { // Screen size is too small, maybe it was not resized yet. + v_scroll->set_value(y_offset); + } else if (y_offset + cell_h > v_scroll->get_value() + screen_h) { + v_scroll->set_value(y_offset - screen_h + cell_h); + } else if (y_offset < v_scroll->get_value()) { + v_scroll->set_value(y_offset); } } } @@ -4817,7 +4834,7 @@ TreeItem *Tree::get_item_with_text(const String &p_find) const { void Tree::_do_incr_search(const String &p_add) { uint64_t time = OS::get_singleton()->get_ticks_usec() / 1000; // convert to msec uint64_t diff = time - last_keypress; - if (diff > uint64_t(GLOBAL_DEF("gui/timers/incremental_search_max_interval_msec", 2000))) { + if (diff > uint64_t(GLOBAL_GET("gui/timers/incremental_search_max_interval_msec"))) { incr_search = p_add; } else if (incr_search != p_add) { incr_search += p_add; @@ -5155,6 +5172,7 @@ void Tree::_bind_methods() { ClassDB::bind_method(D_METHOD("is_root_hidden"), &Tree::is_root_hidden); ClassDB::bind_method(D_METHOD("get_next_selected", "from"), &Tree::get_next_selected); ClassDB::bind_method(D_METHOD("get_selected"), &Tree::get_selected); + ClassDB::bind_method(D_METHOD("set_selected", "item", "column"), &Tree::set_selected); ClassDB::bind_method(D_METHOD("get_selected_column"), &Tree::get_selected_column); ClassDB::bind_method(D_METHOD("get_pressed_button"), &Tree::get_pressed_button); ClassDB::bind_method(D_METHOD("set_select_mode", "mode"), &Tree::set_select_mode); diff --git a/scene/gui/tree.h b/scene/gui/tree.h index f994a5cec1..77a62e1d6a 100644 --- a/scene/gui/tree.h +++ b/scene/gui/tree.h @@ -666,6 +666,7 @@ public: bool is_root_hidden() const; TreeItem *get_next_selected(TreeItem *p_item); TreeItem *get_selected() const; + void set_selected(TreeItem *p_item, int p_column = 0); int get_selected_column() const; int get_pressed_button() const; void set_select_mode(SelectMode p_mode); diff --git a/scene/gui/view_panner.cpp b/scene/gui/view_panner.cpp index 3b7f499a07..e8e3e3e556 100644 --- a/scene/gui/view_panner.cpp +++ b/scene/gui/view_panner.cpp @@ -38,7 +38,9 @@ bool ViewPanner::gui_input(const Ref<InputEvent> &p_event, Rect2 p_canvas_rect) Ref<InputEventMouseButton> mb = p_event; if (mb.is_valid()) { Vector2 scroll_vec = Vector2((mb->get_button_index() == MouseButton::WHEEL_RIGHT) - (mb->get_button_index() == MouseButton::WHEEL_LEFT), (mb->get_button_index() == MouseButton::WHEEL_DOWN) - (mb->get_button_index() == MouseButton::WHEEL_UP)); - if (scroll_vec != Vector2()) { + // Moving the scroll wheel sends two events: one with pressed as true, + // and one with pressed as false. Make sure we only process one of them. + if (scroll_vec != Vector2() && mb->is_pressed()) { if (control_scheme == SCROLL_PANS) { if (mb->is_ctrl_pressed()) { scroll_vec.y *= mb->get_factor(); |