diff options
Diffstat (limited to 'scene/gui')
62 files changed, 3309 insertions, 3062 deletions
diff --git a/scene/gui/base_button.h b/scene/gui/base_button.h index ba3852ec98..7cf8de6432 100644 --- a/scene/gui/base_button.h +++ b/scene/gui/base_button.h @@ -155,4 +155,4 @@ public: ButtonGroup(); }; -#endif +#endif // BASE_BUTTON_H diff --git a/scene/gui/button.cpp b/scene/gui/button.cpp index 1371c9cd57..a67f850a86 100644 --- a/scene/gui/button.cpp +++ b/scene/gui/button.cpp @@ -347,7 +347,6 @@ void Button::_notification(int p_what) { if (outline_size > 0 && font_outline_color.a > 0) { text_buf->draw_outline(ci, text_ofs, outline_size, font_outline_color); } - text_buf->draw(ci, text_ofs, color); } break; } @@ -363,7 +362,7 @@ void Button::_shape() { } else { text_buf->set_direction((TextServer::Direction)text_direction); } - text_buf->add_string(xl_text, font, font_size, opentype_features, (!language.is_empty()) ? language : TranslationServer::get_singleton()->get_tool_locale()); + text_buf->add_string(xl_text, font, font_size, language); text_buf->set_text_overrun_behavior(overrun_behavior); } @@ -409,29 +408,6 @@ Control::TextDirection Button::get_text_direction() const { return text_direction; } -void Button::clear_opentype_features() { - opentype_features.clear(); - _shape(); - update(); -} - -void Button::set_opentype_feature(const String &p_name, int p_value) { - int32_t tag = TS->name_to_tag(p_name); - if (!opentype_features.has(tag) || (int)opentype_features[tag] != p_value) { - opentype_features[tag] = p_value; - _shape(); - update(); - } -} - -int Button::get_opentype_feature(const String &p_name) const { - int32_t tag = TS->name_to_tag(p_name); - if (!opentype_features.has(tag)) { - return -1; - } - return opentype_features[tag]; -} - void Button::set_language(const String &p_language) { if (language != p_language) { language = p_language; @@ -512,56 +488,6 @@ HorizontalAlignment Button::get_icon_alignment() const { return icon_alignment; } -bool Button::_set(const StringName &p_name, const Variant &p_value) { - String str = p_name; - if (str.begins_with("opentype_features/")) { - String name = str.get_slicec('/', 1); - int32_t tag = TS->name_to_tag(name); - int value = p_value; - if (value == -1) { - if (opentype_features.has(tag)) { - opentype_features.erase(tag); - _shape(); - update(); - } - } else { - if (!opentype_features.has(tag) || (int)opentype_features[tag] != value) { - opentype_features[tag] = value; - _shape(); - update(); - } - } - notify_property_list_changed(); - return true; - } - - return false; -} - -bool Button::_get(const StringName &p_name, Variant &r_ret) const { - String str = p_name; - if (str.begins_with("opentype_features/")) { - String name = str.get_slicec('/', 1); - int32_t tag = TS->name_to_tag(name); - if (opentype_features.has(tag)) { - r_ret = opentype_features[tag]; - return true; - } else { - r_ret = -1; - return true; - } - } - return false; -} - -void Button::_get_property_list(List<PropertyInfo> *p_list) const { - for (const Variant *ftr = opentype_features.next(nullptr); ftr != nullptr; ftr = opentype_features.next(ftr)) { - String name = TS->tag_to_name(*ftr); - p_list->push_back(PropertyInfo(Variant::INT, "opentype_features/" + name)); - } - p_list->push_back(PropertyInfo(Variant::NIL, "opentype_features/_new", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_EDITOR)); -} - void Button::_bind_methods() { ClassDB::bind_method(D_METHOD("set_text", "text"), &Button::set_text); ClassDB::bind_method(D_METHOD("get_text"), &Button::get_text); @@ -569,9 +495,6 @@ void Button::_bind_methods() { ClassDB::bind_method(D_METHOD("get_text_overrun_behavior"), &Button::get_text_overrun_behavior); ClassDB::bind_method(D_METHOD("set_text_direction", "direction"), &Button::set_text_direction); ClassDB::bind_method(D_METHOD("get_text_direction"), &Button::get_text_direction); - ClassDB::bind_method(D_METHOD("set_opentype_feature", "tag", "value"), &Button::set_opentype_feature); - ClassDB::bind_method(D_METHOD("get_opentype_feature", "tag"), &Button::get_opentype_feature); - ClassDB::bind_method(D_METHOD("clear_opentype_features"), &Button::clear_opentype_features); ClassDB::bind_method(D_METHOD("set_language", "language"), &Button::set_language); ClassDB::bind_method(D_METHOD("get_language"), &Button::get_language); ClassDB::bind_method(D_METHOD("set_button_icon", "texture"), &Button::set_icon); @@ -588,8 +511,6 @@ void Button::_bind_methods() { ClassDB::bind_method(D_METHOD("is_expand_icon"), &Button::is_expand_icon); ADD_PROPERTY(PropertyInfo(Variant::STRING, "text", PROPERTY_HINT_MULTILINE_TEXT, "", PROPERTY_USAGE_DEFAULT_INTL), "set_text", "get_text"); - ADD_PROPERTY(PropertyInfo(Variant::INT, "text_direction", PROPERTY_HINT_ENUM, "Auto,Left-to-Right,Right-to-Left,Inherited"), "set_text_direction", "get_text_direction"); - ADD_PROPERTY(PropertyInfo(Variant::STRING, "language", PROPERTY_HINT_LOCALE_ID, ""), "set_language", "get_language"); ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "icon", PROPERTY_HINT_RESOURCE_TYPE, "Texture2D"), "set_button_icon", "get_button_icon"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "flat"), "set_flat", "is_flat"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "clip_text"), "set_clip_text", "get_clip_text"); @@ -597,11 +518,15 @@ void Button::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::INT, "text_overrun_behavior", PROPERTY_HINT_ENUM, "Trim Nothing,Trim Characters,Trim Words,Ellipsis,Word Ellipsis"), "set_text_overrun_behavior", "get_text_overrun_behavior"); ADD_PROPERTY(PropertyInfo(Variant::INT, "icon_alignment", PROPERTY_HINT_ENUM, "Left,Center,Right"), "set_icon_alignment", "get_icon_alignment"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "expand_icon"), "set_expand_icon", "is_expand_icon"); + + ADD_GROUP("BiDi", ""); + ADD_PROPERTY(PropertyInfo(Variant::INT, "text_direction", PROPERTY_HINT_ENUM, "Auto,Left-to-Right,Right-to-Left,Inherited"), "set_text_direction", "get_text_direction"); + ADD_PROPERTY(PropertyInfo(Variant::STRING, "language", PROPERTY_HINT_LOCALE_ID, ""), "set_language", "get_language"); } Button::Button(const String &p_text) { text_buf.instantiate(); - text_buf->set_flags(TextServer::BREAK_MANDATORY); + text_buf->set_break_flags(TextServer::BREAK_MANDATORY); set_mouse_filter(MOUSE_FILTER_STOP); set_text(p_text); diff --git a/scene/gui/button.h b/scene/gui/button.h index a1d71195cb..9d8d457f7c 100644 --- a/scene/gui/button.h +++ b/scene/gui/button.h @@ -43,7 +43,6 @@ private: String xl_text; Ref<TextParagraph> text_buf; - Dictionary opentype_features; String language; TextDirection text_direction = TEXT_DIRECTION_AUTO; TextServer::OverrunBehavior overrun_behavior = TextServer::OVERRUN_NO_TRIMMING; @@ -62,10 +61,6 @@ protected: void _notification(int p_what); static void _bind_methods(); - bool _set(const StringName &p_name, const Variant &p_value); - bool _get(const StringName &p_name, Variant &r_ret) const; - void _get_property_list(List<PropertyInfo> *p_list) const; - public: virtual Size2 get_minimum_size() const override; @@ -78,10 +73,6 @@ public: void set_text_direction(TextDirection p_text_direction); TextDirection get_text_direction() const; - void set_opentype_feature(const String &p_name, int p_value); - int get_opentype_feature(const String &p_name) const; - void clear_opentype_features(); - void set_language(const String &p_language); String get_language() const; @@ -107,4 +98,4 @@ public: ~Button(); }; -#endif +#endif // BUTTON_H diff --git a/scene/gui/code_edit.cpp b/scene/gui/code_edit.cpp index 22e9763929..8968c1cc17 100644 --- a/scene/gui/code_edit.cpp +++ b/scene/gui/code_edit.cpp @@ -46,7 +46,7 @@ void CodeEdit::_notification(int p_what) { line_spacing = get_theme_constant(SNAME("line_spacing")); set_gutter_width(main_gutter, get_line_height()); - set_gutter_width(line_number_gutter, (line_number_digits + 1) * font->get_char_size('0', 0, font_size).width); + set_gutter_width(line_number_gutter, (line_number_digits + 1) * font->get_char_size('0', font_size).width); set_gutter_width(fold_gutter, get_line_height() / 1.2); breakpoint_color = get_theme_color(SNAME("breakpoint_color")); @@ -68,6 +68,7 @@ void CodeEdit::_notification(int p_what) { code_completion_max_lines = get_theme_constant(SNAME("completion_lines")); code_completion_scroll_width = get_theme_constant(SNAME("completion_scroll_width")); code_completion_scroll_color = get_theme_color(SNAME("completion_scroll_color")); + code_completion_scroll_hovered_color = get_theme_color(SNAME("completion_scroll_hovered_color")); code_completion_background_color = get_theme_color(SNAME("completion_background_color")); code_completion_selected_color = get_theme_color(SNAME("completion_selected_color")); code_completion_existing_color = get_theme_color(SNAME("completion_existing_color")); @@ -85,7 +86,7 @@ void CodeEdit::_notification(int p_what) { if (line_length_guideline_columns.size() > 0) { const int xmargin_beg = style_normal->get_margin(SIDE_LEFT) + get_total_gutter_width(); const int xmargin_end = size.width - style_normal->get_margin(SIDE_RIGHT) - (is_drawing_minimap() ? get_minimap_width() : 0); - const float char_size = font->get_char_size('0', 0, font_size).width; + const float char_size = font->get_char_size('0', font_size).width; for (int i = 0; i < line_length_guideline_columns.size(); i++) { const int xoffset = xmargin_beg + char_size * (int)line_length_guideline_columns[i] - get_h_scroll(); @@ -122,7 +123,7 @@ void CodeEdit::_notification(int p_what) { } const int scroll_width = code_completion_options_count > code_completion_max_lines ? code_completion_scroll_width : 0; - const int code_completion_base_width = font->get_string_size(code_completion_base, font_size).width; + const int code_completion_base_width = font->get_string_size(code_completion_base, HORIZONTAL_ALIGNMENT_LEFT, -1, font_size).width; if (caret_pos.x - code_completion_base_width + code_completion_rect.size.width + scroll_width > get_size().width) { code_completion_rect.position.x = get_size().width - code_completion_rect.size.width - scroll_width; } else { @@ -134,6 +135,9 @@ void CodeEdit::_notification(int p_what) { RenderingServer::get_singleton()->canvas_item_add_rect(ci, Rect2(code_completion_rect.position, code_completion_rect.size + Size2(scroll_width, 0)), code_completion_background_color); } + 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); 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); @@ -174,8 +178,8 @@ void CodeEdit::_notification(int p_what) { for (int j = 0; j < code_completion_options[l].matches.size(); j++) { Pair<int, int> match = code_completion_options[l].matches[j]; - int match_offset = font->get_string_size(code_completion_options[l].display.substr(0, match.first), font_size).width; - int match_len = font->get_string_size(code_completion_options[l].display.substr(match.first, match.second), font_size).width; + int match_offset = font->get_string_size(code_completion_options[l].display.substr(0, match.first), HORIZONTAL_ALIGNMENT_LEFT, -1, font_size).width; + int match_len = font->get_string_size(code_completion_options[l].display.substr(match.first, match.second), HORIZONTAL_ALIGNMENT_LEFT, -1, font_size).width; draw_rect(Rect2(match_pos + Point2(match_offset, 0), Size2(match_len, row_height)), code_completion_existing_color); } @@ -185,9 +189,11 @@ void CodeEdit::_notification(int p_what) { /* Draw a small scroll rectangle to show a position in the options. */ if (scroll_width) { + Color scroll_color = is_code_completion_scroll_hovered || is_code_completion_scroll_pressed ? code_completion_scroll_hovered_color : code_completion_scroll_color; + float r = (float)code_completion_max_lines / code_completion_options_count; float o = (float)code_completion_line_ofs / code_completion_options_count; - draw_rect(Rect2(code_completion_rect.position.x + code_completion_rect.size.width, code_completion_rect.position.y + o * code_completion_rect.size.y, scroll_width, code_completion_rect.size.y * r), code_completion_scroll_color); + draw_rect(Rect2(code_completion_rect.position.x + code_completion_rect.size.width, code_completion_rect.position.y + o * code_completion_rect.size.y, scroll_width, code_completion_rect.size.y * r), scroll_color); } } @@ -202,11 +208,11 @@ void CodeEdit::_notification(int p_what) { int max_width = 0; for (int i = 0; i < line_count; i++) { - max_width = MAX(max_width, font->get_string_size(code_hint_lines[i], font_size).x); + max_width = MAX(max_width, font->get_string_size(code_hint_lines[i], HORIZONTAL_ALIGNMENT_LEFT, -1, font_size).x); } Size2 minsize = sb->get_minimum_size() + Size2(max_width, line_count * font_height + (line_spacing * line_count - 1)); - int offset = font->get_string_size(code_hint_lines[0].substr(0, code_hint_lines[0].find(String::chr(0xFFFF))), font_size).x; + int offset = font->get_string_size(code_hint_lines[0].substr(0, code_hint_lines[0].find(String::chr(0xFFFF))), HORIZONTAL_ALIGNMENT_LEFT, -1, font_size).x; if (code_hint_xpos == -0xFFFF) { code_hint_xpos = get_caret_draw_pos().x - offset; } @@ -226,8 +232,8 @@ void CodeEdit::_notification(int p_what) { int begin = 0; int end = 0; if (line.contains(String::chr(0xFFFF))) { - begin = font->get_string_size(line.substr(0, line.find(String::chr(0xFFFF))), font_size).x; - end = font->get_string_size(line.substr(0, line.rfind(String::chr(0xFFFF))), font_size).x; + begin = font->get_string_size(line.substr(0, line.find(String::chr(0xFFFF))), HORIZONTAL_ALIGNMENT_LEFT, -1, font_size).x; + end = font->get_string_size(line.substr(0, line.rfind(String::chr(0xFFFF))), HORIZONTAL_ALIGNMENT_LEFT, -1, font_size).x; } Point2 round_ofs = hint_ofs + sb->get_offset() + Vector2(0, font->get_ascent(font_size) + font_height * i + yofs); @@ -260,6 +266,12 @@ void CodeEdit::gui_input(const Ref<InputEvent> &p_gui_input) { return; } + if (is_code_completion_scroll_pressed && mb->get_button_index() == MouseButton::LEFT) { + is_code_completion_scroll_pressed = false; + update(); + return; + } + if (code_completion_active && code_completion_rect.has_point(mb->get_position())) { if (!mb->is_pressed()) { return; @@ -289,7 +301,21 @@ void CodeEdit::gui_input(const Ref<InputEvent> &p_gui_input) { break; } return; + } else if (code_completion_active && code_completion_scroll_rect.has_point(mb->get_position())) { + if (mb->get_button_index() != MouseButton::LEFT) { + return; + } + + if (mb->is_pressed()) { + is_code_completion_scroll_pressed = true; + + _update_scroll_selected_line(mb->get_position().y); + update(); + } + + return; } + cancel_code_completion(); set_code_hint(""); @@ -354,6 +380,18 @@ void CodeEdit::gui_input(const Ref<InputEvent> &p_gui_input) { set_symbol_lookup_word_as_valid(false); } } + + bool scroll_hovered = code_completion_scroll_rect.has_point(mpos); + if (is_code_completion_scroll_hovered != scroll_hovered) { + is_code_completion_scroll_hovered = scroll_hovered; + update(); + } + + if (is_code_completion_scroll_pressed) { + _update_scroll_selected_line(mpos.y); + update(); + return; + } } Ref<InputEventKey> k = p_gui_input; @@ -369,7 +407,7 @@ void CodeEdit::gui_input(const Ref<InputEvent> &p_gui_input) { } /* Ctrl + Hover symbols */ -#ifdef OSX_ENABLED +#ifdef MACOS_ENABLED if (k->get_keycode() == Key::META) { #else if (k->get_keycode() == Key::CTRL) { @@ -546,6 +584,10 @@ Control::CursorShape CodeEdit::get_cursor_shape(const Point2 &p_pos) const { return CURSOR_ARROW; } + if (code_completion_active && code_completion_scroll_rect.has_point(p_pos)) { + return CURSOR_ARROW; + } + Point2i pos = get_line_column_at_pos(p_pos, false); int line = pos.y; int col = pos.x; @@ -2697,6 +2739,13 @@ TypedArray<String> CodeEdit::_get_delimiters(DelimiterType p_type) const { } /* Code Completion */ +void CodeEdit::_update_scroll_selected_line(float p_mouse_y) { + float percent = (float)(p_mouse_y - code_completion_scroll_rect.position.y) / code_completion_scroll_rect.size.height; + percent = CLAMP(percent, 0.0f, 1.0f); + + code_completion_current_selected = (int)(percent * (code_completion_options.size() - 1)); +} + void CodeEdit::_filter_code_completion_candidates_impl() { int line_height = get_line_height(); @@ -2746,7 +2795,7 @@ void CodeEdit::_filter_code_completion_candidates_impl() { offset = line_height; } - max_width = MAX(max_width, font->get_string_size(option.display, font_size).width + offset); + 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); } @@ -2857,7 +2906,7 @@ 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, font_size).width + offset); + max_width = MAX(max_width, font->get_string_size(option.display, HORIZONTAL_ALIGNMENT_LEFT, -1, font_size).width + offset); continue; } @@ -2963,7 +3012,7 @@ 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, font_size).width + offset); + 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(); @@ -2980,7 +3029,7 @@ 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, font_size).width + offset); + max_width = MAX(max_width, font->get_string_size(option.display, HORIZONTAL_ALIGNMENT_LEFT, -1, font_size).width + offset); } } @@ -3036,7 +3085,7 @@ void CodeEdit::_text_changed() { } if (font.is_valid()) { - set_gutter_width(line_number_gutter, (line_number_digits + 1) * font->get_char_size('0', 0, font_size).width); + set_gutter_width(line_number_gutter, (line_number_digits + 1) * font->get_char_size('0', font_size).width); } lc = get_line_count(); diff --git a/scene/gui/code_edit.h b/scene/gui/code_edit.h index ccf046c612..08bd91a368 100644 --- a/scene/gui/code_edit.h +++ b/scene/gui/code_edit.h @@ -28,8 +28,8 @@ /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /*************************************************************************/ -#ifndef CODEEDIT_H -#define CODEEDIT_H +#ifndef CODE_EDIT_H +#define CODE_EDIT_H #include "scene/gui/text_edit.h" @@ -203,22 +203,27 @@ private: int code_completion_max_lines = 7; int code_completion_scroll_width = 0; Color code_completion_scroll_color = Color(0, 0, 0, 0); + Color code_completion_scroll_hovered_color = Color(0, 0, 0, 0); Color code_completion_background_color = Color(0, 0, 0, 0); Color code_completion_selected_color = Color(0, 0, 0, 0); Color code_completion_existing_color = Color(0, 0, 0, 0); bool code_completion_active = false; + bool is_code_completion_scroll_hovered = false; + bool is_code_completion_scroll_pressed = false; Vector<ScriptLanguage::CodeCompletionOption> code_completion_options; int code_completion_line_ofs = 0; int code_completion_current_selected = 0; int code_completion_longest_line = 0; Rect2i code_completion_rect; + Rect2i code_completion_scroll_rect; HashSet<char32_t> code_completion_prefixes; List<ScriptLanguage::CodeCompletionOption> code_completion_option_submitted; List<ScriptLanguage::CodeCompletionOption> code_completion_option_sources; String code_completion_base; + void _update_scroll_selected_line(float p_mouse_y); void _filter_code_completion_candidates_impl(); /* Line length guidelines */ @@ -428,4 +433,4 @@ public: VARIANT_ENUM_CAST(CodeEdit::CodeCompletionKind); -#endif // CODEEDIT_H +#endif // CODE_EDIT_H diff --git a/scene/gui/color_mode.cpp b/scene/gui/color_mode.cpp new file mode 100644 index 0000000000..af78d67e5a --- /dev/null +++ b/scene/gui/color_mode.cpp @@ -0,0 +1,330 @@ +/*************************************************************************/ +/* color_mode.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#include "color_mode.h" + +#include "core/math/color.h" +#include "scene/gui/slider.h" +#include "thirdparty/misc/ok_color.h" + +ColorMode::ColorMode(ColorPicker *p_color_picker) { + color_picker = p_color_picker; +} + +String ColorModeRGB::get_slider_label(int idx) const { + ERR_FAIL_INDEX_V_MSG(idx, 3, String(), "Couldn't get slider label."); + return labels[idx]; +} + +float ColorModeRGB::get_slider_max(int idx) const { + ERR_FAIL_INDEX_V_MSG(idx, 4, 0, "Couldn't get slider max value."); + Color color = color_picker->get_pick_color(); + return next_power_of_2(MAX(255, color.components[idx] * 255.0)) - 1; +} + +float ColorModeRGB::get_slider_value(int idx) const { + ERR_FAIL_INDEX_V_MSG(idx, 4, 0, "Couldn't get slider value."); + return color_picker->get_pick_color().components[idx] * 255; +} + +Color ColorModeRGB::get_color() const { + Vector<float> values = color_picker->get_active_slider_values(); + Color color; + for (int i = 0; i < 4; i++) { + color.components[i] = values[i] / 255.0; + } + return color; +} + +void ColorModeRGB::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(); + 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); + + left_color = color; + left_color.a = 0; + right_color = color; + right_color.a = 1; + } else { + left_color = Color( + p_which == 0 ? 0 : color.r, + p_which == 1 ? 0 : color.g, + p_which == 2 ? 0 : color.b); + right_color = Color( + p_which == 0 ? 1 : color.r, + p_which == 1 ? 1 : color.g, + p_which == 2 ? 1 : color.b); + } + + 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); +} + +String ColorModeHSV::get_slider_label(int idx) const { + ERR_FAIL_INDEX_V_MSG(idx, 3, String(), "Couldn't get slider label."); + return labels[idx]; +} + +float ColorModeHSV::get_slider_max(int idx) const { + ERR_FAIL_INDEX_V_MSG(idx, 4, 0, "Couldn't get slider max value."); + return slider_max[idx]; +} + +float ColorModeHSV::get_slider_value(int idx) const { + switch (idx) { + case 0: + return color_picker->get_pick_color().get_h() * 360.0; + case 1: + return color_picker->get_pick_color().get_s() * 100.0; + case 2: + return color_picker->get_pick_color().get_v() * 100.0; + case 3: + return Math::round(color_picker->get_pick_color().components[3] * 255.0); + default: + ERR_FAIL_V_MSG(0, "Couldn't get slider value."); + } +} + +Color ColorModeHSV::get_color() const { + Vector<float> values = color_picker->get_active_slider_values(); + Color color; + color.set_hsv(values[0] / 360.0, values[1] / 100.0, values[2] / 100.0, values[3] / 255.0); + return color; +} + +void ColorModeHSV::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(); + 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); + + 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(slider->get_size().x, margin)), false, Color(1, 1, 1), true); + return; + } else { + Color s_col; + Color v_col; + s_col.set_hsv(color.get_h(), 0, color.get_v()); + left_color = (p_which == 1) ? s_col : Color(0, 0, 0); + s_col.set_hsv(color.get_h(), 1, color.get_v()); + v_col.set_hsv(color.get_h(), color.get_s(), 1); + right_color = (p_which == 1) ? s_col : v_col; + } + 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); +} + +String ColorModeRAW::get_slider_label(int idx) const { + ERR_FAIL_INDEX_V_MSG(idx, 3, String(), "Couldn't get slider label."); + return labels[idx]; +} + +float ColorModeRAW::get_slider_max(int idx) const { + ERR_FAIL_INDEX_V_MSG(idx, 4, 0, "Couldn't get slider max value."); + return slider_max[idx]; +} + +float ColorModeRAW::get_slider_value(int idx) const { + ERR_FAIL_INDEX_V_MSG(idx, 4, 0, "Couldn't get slider value."); + return color_picker->get_pick_color().components[idx]; +} + +Color ColorModeRAW::get_color() const { + Vector<float> values = color_picker->get_active_slider_values(); + Color color; + for (int i = 0; i < 4; i++) { + color.components[i] = values[i]; + } + return color; +} + +void ColorModeRAW::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(); + 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); + + left_color = color; + left_color.a = 0; + right_color = color; + right_color.a = 1; + + 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); + } +} + +bool ColorModeRAW::apply_theme() const { + for (int i = 0; i < 4; i++) { + HSlider *slider = color_picker->get_slider(i); + 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"); + } + + return true; +} + +String ColorModeOKHSL::get_slider_label(int idx) const { + ERR_FAIL_INDEX_V_MSG(idx, 3, String(), "Couldn't get slider label."); + return labels[idx]; +} + +float ColorModeOKHSL::get_slider_max(int idx) const { + ERR_FAIL_INDEX_V_MSG(idx, 4, 0, "Couldn't get slider max value."); + return slider_max[idx]; +} + +float ColorModeOKHSL::get_slider_value(int idx) const { + switch (idx) { + case 0: + return color_picker->get_pick_color().get_ok_hsl_h() * 360.0; + case 1: + return color_picker->get_pick_color().get_ok_hsl_s() * 100.0; + case 2: + return color_picker->get_pick_color().get_ok_hsl_l() * 100.0; + case 3: + return Math::round(color_picker->get_pick_color().components[3] * 255.0); + default: + ERR_FAIL_V_MSG(0, "Couldn't get slider value."); + } +} + +Color ColorModeOKHSL::get_color() const { + Vector<float> values = color_picker->get_active_slider_values(); + Color color; + color.set_ok_hsl(values[0] / 360.0, values[1] / 100.0, values[2] / 100.0, values[3] / 255.0); + return color; +} + +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(); + 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); + + 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(slider->get_size().x, margin)), false, Color(1, 1, 1), true); + 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, 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_mode.h b/scene/gui/color_mode.h new file mode 100644 index 0000000000..8a19699c40 --- /dev/null +++ b/scene/gui/color_mode.h @@ -0,0 +1,143 @@ +/*************************************************************************/ +/* color_mode.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#ifndef COLOR_MODE_H +#define COLOR_MODE_H + +#include "scene/gui/color_picker.h" + +struct Color; + +class ColorMode { +public: + ColorPicker *color_picker = nullptr; + + virtual String get_name() const = 0; + + virtual int get_slider_count() const { return 3; }; + virtual float get_slider_step() const = 0; + virtual String get_slider_label(int idx) const = 0; + virtual float get_slider_max(int idx) const = 0; + virtual float get_slider_value(int idx) const = 0; + + virtual Color get_color() const = 0; + + virtual void slider_draw(int p_which) = 0; + virtual bool apply_theme() const { return false; } + virtual ColorPicker::PickerShapeType get_shape_override() const { return ColorPicker::SHAPE_MAX; } + + ColorMode(ColorPicker *p_color_picker); + virtual ~ColorMode(){}; +}; + +class ColorModeHSV : public ColorMode { +public: + String labels[3] = { "H", "S", "V" }; + float slider_max[4] = { 359, 100, 100, 255 }; + + virtual String get_name() const override { return "HSV"; } + + virtual float get_slider_step() const override { return 1.0; } + virtual String get_slider_label(int idx) const override; + virtual float get_slider_max(int idx) const override; + virtual float get_slider_value(int idx) const override; + + virtual Color get_color() const override; + + virtual void slider_draw(int p_which) override; + + ColorModeHSV(ColorPicker *p_color_picker) : + ColorMode(p_color_picker){}; +}; + +class ColorModeRGB : public ColorMode { +public: + String labels[3] = { "R", "G", "B" }; + + virtual String get_name() const override { return "RGB"; } + + virtual float get_slider_step() const override { return 1; } + virtual String get_slider_label(int idx) const override; + virtual float get_slider_max(int idx) const override; + virtual float get_slider_value(int idx) const override; + + virtual Color get_color() const override; + + virtual void slider_draw(int p_which) override; + + ColorModeRGB(ColorPicker *p_color_picker) : + ColorMode(p_color_picker){}; +}; + +class ColorModeRAW : public ColorMode { +public: + String labels[3] = { "R", "G", "B" }; + float slider_max[4] = { 100, 100, 100, 1 }; + + virtual String get_name() const override { return "RAW"; } + + virtual float get_slider_step() const override { return 0.01; } + virtual String get_slider_label(int idx) const override; + virtual float get_slider_max(int idx) const override; + virtual float get_slider_value(int idx) const override; + + virtual Color get_color() const override; + + virtual void slider_draw(int p_which) override; + virtual bool apply_theme() const override; + + ColorModeRAW(ColorPicker *p_color_picker) : + ColorMode(p_color_picker){}; +}; + +class ColorModeOKHSL : public ColorMode { +public: + String labels[3] = { "H", "S", "L" }; + float slider_max[4] = { 359, 100, 100, 255 }; + + virtual String get_name() const override { return "OKHSL"; } + + virtual float get_slider_step() const override { return 1.0; } + virtual String get_slider_label(int idx) const override; + virtual float get_slider_max(int idx) const override; + virtual float get_slider_value(int idx) const override; + + virtual Color get_color() const override; + + virtual void slider_draw(int p_which) override; + virtual ColorPicker::PickerShapeType get_shape_override() const override { return ColorPicker::SHAPE_OKHSL_CIRCLE; } + + ColorModeOKHSL(ColorPicker *p_color_picker) : + ColorMode(p_color_picker){}; + + ~ColorModeOKHSL(){}; +}; + +#endif // COLOR_MODE_H diff --git a/scene/gui/color_picker.cpp b/scene/gui/color_picker.cpp index 28d645e8f6..e2ead6415a 100644 --- a/scene/gui/color_picker.cpp +++ b/scene/gui/color_picker.cpp @@ -34,7 +34,7 @@ #include "core/math/color.h" #include "core/os/keyboard.h" #include "core/os/os.h" -#include "scene/main/window.h" +#include "scene/gui/color_mode.h" #ifdef TOOLS_ENABLED #include "editor/editor_settings.h" @@ -75,10 +75,14 @@ void ColorPicker::_notification(int p_what) { wheel_edit->set_custom_minimum_size(Size2(get_theme_constant(SNAME("sv_width")), get_theme_constant(SNAME("sv_height")))); wheel_margin->add_theme_constant_override("margin_bottom", 8 * get_theme_default_base_scale()); - for (int i = 0; i < 4; i++) { + for (int i = 0; i < SLIDER_COUNT; i++) { labels[i]->set_custom_minimum_size(Size2(get_theme_constant(SNAME("label_width")), 0)); set_offset((Side)i, get_offset((Side)i) + get_theme_constant(SNAME("margin"))); } + alpha_label->set_custom_minimum_size(Size2(get_theme_constant(SNAME("label_width")), 0)); + set_offset((Side)0, get_offset((Side)0) + get_theme_constant(SNAME("margin"))); + + _reset_theme(); if (Engine::get_singleton()->is_editor_hint()) { // Adjust for the width of the "Script" icon. @@ -194,75 +198,38 @@ void ColorPicker::set_focus_on_line_edit() { } void ColorPicker::_update_controls() { - const char *rgb[3] = { "R", "G", "B" }; - const char *hsv[3] = { "H", "S", "V" }; - const char *hsl[3] = { "H", "S", "L" }; - if (hsv_mode_enabled && picker_type == SHAPE_OKHSL_CIRCLE) { - for (int i = 0; i < 3; i++) { - labels[i]->set_text(hsl[i]); - } - } else if (hsv_mode_enabled && picker_type != SHAPE_OKHSL_CIRCLE) { - for (int i = 0; i < 3; i++) { - labels[i]->set_text(hsv[i]); - } - } else { - for (int i = 0; i < 3; i++) { - labels[i]->set_text(rgb[i]); - } + int mode_sliders_count = modes[current_mode]->get_slider_count(); + + for (int i = current_slider_count; i < mode_sliders_count; i++) { + sliders[i]->show(); + labels[i]->show(); + values[i]->show(); } - if (picker_type == SHAPE_OKHSL_CIRCLE) { - btn_hsv->set_text(RTR("OKHSL")); - } else { - btn_hsv->set_text(RTR("HSV")); - } - if (hsv_mode_enabled) { - set_raw_mode(false); - set_hsv_mode(true); - btn_raw->set_disabled(true); - } else if (raw_mode_enabled) { - set_raw_mode(true); - set_hsv_mode(false); - btn_raw->set_disabled(false); - btn_hsv->set_disabled(true); - } else { - set_raw_mode(false); - set_hsv_mode(false); - btn_raw->set_disabled(false); - btn_hsv->set_disabled(false); - } - - if (raw_mode_enabled) { - for (int i = 0; i < 3; i++) { - scroll[i]->remove_theme_icon_override("grabber"); - scroll[i]->remove_theme_icon_override("grabber_highlight"); - scroll[i]->remove_theme_style_override("slider"); - scroll[i]->remove_theme_style_override("grabber_area"); - scroll[i]->remove_theme_style_override("grabber_area_highlight"); - } - } else { - Ref<StyleBoxEmpty> style_box_empty(memnew(StyleBoxEmpty)); - Ref<Texture2D> bar_arrow = get_theme_icon(SNAME("bar_arrow")); - - for (int i = 0; i < 4; i++) { - scroll[i]->add_theme_icon_override("grabber", bar_arrow); - scroll[i]->add_theme_icon_override("grabber_highlight", bar_arrow); - scroll[i]->add_theme_style_override("slider", style_box_empty); - scroll[i]->add_theme_style_override("grabber_area", style_box_empty); - scroll[i]->add_theme_style_override("grabber_area_highlight", style_box_empty); - } + for (int i = mode_sliders_count; i < current_slider_count; i++) { + sliders[i]->hide(); + labels[i]->hide(); + values[i]->hide(); + } + current_slider_count = mode_sliders_count; + + for (int i = 0; i < current_slider_count; i++) { + labels[i]->set_text(modes[current_mode]->get_slider_label(i)); } + alpha_label->set_text("A"); + + slider_theme_modified = modes[current_mode]->apply_theme(); if (edit_alpha) { - values[3]->show(); - scroll[3]->show(); - labels[3]->show(); + alpha_value->show(); + alpha_slider->show(); + alpha_label->show(); } else { - values[3]->hide(); - scroll[3]->hide(); - labels[3]->hide(); + alpha_value->hide(); + alpha_slider->hide(); + alpha_label->hide(); } - switch (picker_type) { + switch (_get_actual_shape()) { case SHAPE_HSV_RECTANGLE: wheel_edit->hide(); w_edit->show(); @@ -297,7 +264,7 @@ void ColorPicker::_update_controls() { void ColorPicker::_set_pick_color(const Color &p_color, bool p_update_sliders) { color = p_color; if (color != last_color) { - if (picker_type == SHAPE_OKHSL_CIRCLE) { + if (_get_actual_shape() == SHAPE_OKHSL_CIRCLE) { h = color.get_ok_hsl_h(); s = color.get_ok_hsl_s(); v = color.get_ok_hsl_l(); @@ -353,27 +320,93 @@ void ColorPicker::_value_changed(double) { return; } - if (hsv_mode_enabled) { - h = scroll[0]->get_value() / 360.0; - s = scroll[1]->get_value() / 100.0; - v = scroll[2]->get_value() / 100.0; - if (picker_type == SHAPE_OKHSL_CIRCLE) { - color.set_ok_hsl(h, s, v, Math::round(scroll[3]->get_value() / 255.0)); - } else { - color.set_hsv(h, s, v, Math::round(scroll[3]->get_value() / 255.0)); - } + color = modes[current_mode]->get_color(); + if (current_mode == MODE_HSV || current_mode == MODE_OKHSL) { + h = sliders[0]->get_value() / 360.0; + s = sliders[1]->get_value() / 100.0; + v = sliders[2]->get_value() / 100.0; last_color = color; - } else { - for (int i = 0; i < 4; i++) { - color.components[i] = scroll[i]->get_value() / (raw_mode_enabled ? 1.0 : 255.0); - } } _set_pick_color(color, false); emit_signal(SNAME("color_changed"), color); } +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); + + HSlider *s = memnew(HSlider); + s->set_v_size_flags(SIZE_SHRINK_CENTER); + s->set_focus_mode(FOCUS_NONE); + gc->add_child(s); + + 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)); + + s->set_h_size_flags(SIZE_EXPAND_FILL); + + s->connect("value_changed", callable_mp(this, &ColorPicker::_value_changed)); + s->connect("draw", callable_mp(this, &ColorPicker::_slider_draw).bind(idx)); + + if (idx < SLIDER_COUNT) { + sliders[idx] = s; + values[idx] = v; + labels[idx] = l; + } else { + alpha_slider = s; + alpha_value = v; + alpha_label = l; + } +} + +HSlider *ColorPicker::get_slider(int idx) { + if (idx < SLIDER_COUNT) { + return sliders[idx]; + } + return alpha_slider; +} + +Vector<float> ColorPicker::get_active_slider_values() { + Vector<float> values; + for (int i = 0; i < current_slider_count; i++) { + values.push_back(sliders[i]->get_value()); + } + values.push_back(alpha_slider->get_value()); + return values; +} + +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)); + + 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); + } + 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); +} + void ColorPicker::_html_submitted(const String &p_html) { if (updating || text_is_constructor || !c_text->is_visible()) { return; @@ -397,35 +430,15 @@ void ColorPicker::_update_color(bool p_update_sliders) { updating = true; if (p_update_sliders) { - if (hsv_mode_enabled) { - for (int i = 0; i < 4; i++) { - scroll[i]->set_step(1.0); - } - scroll[0]->set_max(359); - scroll[0]->set_value(h * 360.0); - scroll[1]->set_max(100); - scroll[1]->set_value(s * 100.0); - scroll[2]->set_max(100); - scroll[2]->set_value(v * 100.0); - scroll[3]->set_max(255); - scroll[3]->set_value(Math::round(color.components[3] * 255.0)); - } else { - for (int i = 0; i < 4; i++) { - if (raw_mode_enabled) { - scroll[i]->set_step(0.01); - scroll[i]->set_max(100); - if (i == 3) { - scroll[i]->set_max(1); - } - scroll[i]->set_value(color.components[i]); - } else { - scroll[i]->set_step(1); - const float byte_value = Math::round(color.components[i] * 255.0); - scroll[i]->set_max(next_power_of_2(MAX(255, byte_value)) - 1); - scroll[i]->set_value(byte_value); - } - } + float step = modes[current_mode]->get_slider_step(); + for (int i = 0; i < current_slider_count; i++) { + sliders[i]->set_max(modes[current_mode]->get_slider_max(i)); + sliders[i]->set_step(step); + sliders[i]->set_value(modes[current_mode]->get_slider_value(i)); } + alpha_slider->set_max(modes[current_mode]->get_slider_max(current_slider_count)); + alpha_slider->set_step(step); + alpha_slider->set_value(modes[current_mode]->get_slider_value(current_slider_count)); } _update_text_value(); @@ -433,9 +446,10 @@ void ColorPicker::_update_color(bool p_update_sliders) { sample->update(); uv_edit->update(); w_edit->update(); - for (int i = 0; i < 4; i++) { - scroll[i]->update(); + for (int i = 0; i < current_slider_count; i++) { + sliders[i]->update(); } + alpha_slider->update(); wheel->update(); wheel_uv->update(); updating = false; @@ -481,15 +495,16 @@ Color ColorPicker::get_pick_color() const { return color; } -void ColorPicker::set_picker_shape(PickerShapeType p_picker_type) { - ERR_FAIL_INDEX(p_picker_type, SHAPE_MAX); - picker_type = p_picker_type; +void ColorPicker::set_picker_shape(PickerShapeType p_shape) { + ERR_FAIL_INDEX(p_shape, SHAPE_MAX); + current_shape = p_shape; + _update_controls(); _update_color(); } ColorPicker::PickerShapeType ColorPicker::get_picker_shape() const { - return picker_type; + return current_shape; } inline int ColorPicker::_get_preset_size() { @@ -500,11 +515,26 @@ 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), varray(p_color)); + btn_preset->connect("gui_input", callable_mp(this, &ColorPicker::_preset_input).bind(p_color)); btn_preset->set_tooltip(vformat(RTR("Color: #%s\nLMB: Apply color\nRMB: Remove preset"), p_color.to_html(p_color.a < 1))); preset_container->add_child(btn_preset); } +void ColorPicker::_set_color_mode(ColorModeType p_mode) { + if (slider_theme_modified) { + _reset_theme(); + } + + current_mode = p_mode; + + if (!is_inside_tree()) { + return; + } + + _update_controls(); + _update_color(); +} + void ColorPicker::add_preset(const Color &p_color) { if (presets.find(p_color)) { presets.move_to_back(presets.find(p_color)); @@ -564,46 +594,14 @@ PackedColorArray ColorPicker::get_presets() const { return arr; } -void ColorPicker::set_hsv_mode(bool p_enabled) { - if (hsv_mode_enabled == p_enabled || raw_mode_enabled) { - return; - } - hsv_mode_enabled = p_enabled; - if (btn_hsv->is_pressed() != p_enabled) { - btn_hsv->set_pressed(p_enabled); - } - - if (!is_inside_tree()) { - return; - } - - _update_controls(); - _update_color(); +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); } -bool ColorPicker::is_hsv_mode() const { - return hsv_mode_enabled; -} - -void ColorPicker::set_raw_mode(bool p_enabled) { - if (raw_mode_enabled == p_enabled || hsv_mode_enabled) { - return; - } - raw_mode_enabled = p_enabled; - if (btn_raw->is_pressed() != p_enabled) { - btn_raw->set_pressed(p_enabled); - } - - if (!is_inside_tree()) { - return; - } - - _update_controls(); - _update_color(); -} - -bool ColorPicker::is_raw_mode() const { - return raw_mode_enabled; +ColorPicker::ColorModeType ColorPicker::get_color_mode() const { + return current_mode; } void ColorPicker::set_deferred_mode(bool p_enabled) { @@ -690,6 +688,8 @@ void ColorPicker::_hsv_draw(int p_which, Control *c) { if (!c) { return; } + + PickerShapeType actual_shape = _get_actual_shape(); if (p_which == 0) { Vector<Point2> points; Vector<Color> colors; @@ -697,7 +697,7 @@ void ColorPicker::_hsv_draw(int p_which, Control *c) { Color col = color; Vector2 center = c->get_size() / 2.0; - switch (picker_type) { + switch (actual_shape) { case SHAPE_HSV_WHEEL: { points.resize(4); colors.resize(4); @@ -759,7 +759,7 @@ void ColorPicker::_hsv_draw(int p_which, Control *c) { Ref<Texture2D> cursor = get_theme_icon(SNAME("picker_cursor"), SNAME("ColorPicker")); int x; int y; - if (picker_type == SHAPE_VHS_CIRCLE || picker_type == SHAPE_OKHSL_CIRCLE) { + if (actual_shape == SHAPE_VHS_CIRCLE || actual_shape == SHAPE_OKHSL_CIRCLE) { x = center.x + (center.x * Math::cos(h * Math_TAU) * s) - (cursor->get_width() / 2); y = center.y + (center.y * Math::sin(h * Math_TAU) * s) - (cursor->get_height() / 2); } else { @@ -773,7 +773,7 @@ void ColorPicker::_hsv_draw(int p_which, Control *c) { c->draw_texture(cursor, Point2(x, y)); col.set_hsv(h, 1, 1); - if (picker_type == SHAPE_HSV_WHEEL) { + if (actual_shape == SHAPE_HSV_WHEEL) { points.resize(4); double h1 = h - (0.5 / 360); double h2 = h + (0.5 / 360); @@ -785,14 +785,14 @@ void ColorPicker::_hsv_draw(int p_which, Control *c) { } } else if (p_which == 1) { - if (picker_type == SHAPE_HSV_RECTANGLE) { + 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())); int y = c->get_size().y - c->get_size().y * (1.0 - h); Color col; col.set_hsv(h, 1, 1); c->draw_line(Point2(0, y), Point2(c->get_size().x, y), col.inverted()); - } else if (picker_type == SHAPE_OKHSL_CIRCLE) { + } else if (actual_shape == SHAPE_OKHSL_CIRCLE) { Vector<Point2> points; Vector<Color> colors; Color col; @@ -811,7 +811,7 @@ void ColorPicker::_hsv_draw(int p_which, Control *c) { int y = c->get_size().y - c->get_size().y * CLAMP(v, 0, 1); col.set_ok_hsl(h, 1, v); c->draw_line(Point2(0, y), Point2(c->get_size().x, y), col.inverted()); - } else if (picker_type == SHAPE_VHS_CIRCLE) { + } else if (actual_shape == SHAPE_VHS_CIRCLE) { Vector<Point2> points; Vector<Color> colors; Color col; @@ -833,87 +833,24 @@ void ColorPicker::_hsv_draw(int p_which, Control *c) { } } else if (p_which == 2) { c->draw_rect(Rect2(Point2(), c->get_size()), Color(1, 1, 1)); - if (picker_type == SHAPE_VHS_CIRCLE || picker_type == SHAPE_OKHSL_CIRCLE) { + if (actual_shape == SHAPE_VHS_CIRCLE || actual_shape == SHAPE_OKHSL_CIRCLE) { circle_mat->set_shader_param("v", v); } } } void ColorPicker::_slider_draw(int p_which) { - Vector<Vector2> pos; - pos.resize(4); - Vector<Color> col; - col.resize(4); - Size2 size = scroll[p_which]->get_size(); - Color left_color; - Color right_color; - const real_t margin = 4 * get_theme_default_base_scale(); - - if (p_which == 3) { - scroll[p_which]->draw_texture_rect(get_theme_icon(SNAME("sample_bg"), SNAME("ColorPicker")), Rect2(Point2(0, margin), Size2(size.x, margin)), true); - - left_color = color; - left_color.a = 0; - right_color = color; - right_color.a = 1; - } else { - if (raw_mode_enabled) { - return; - } - if (hsv_mode_enabled) { - if (p_which == 0) { - Ref<Texture2D> hue = get_theme_icon(SNAME("color_hue"), SNAME("ColorPicker")); - scroll[p_which]->draw_set_transform(Point2(), -Math_PI / 2, Size2(1.0, 1.0)); - scroll[p_which]->draw_texture_rect(hue, Rect2(Vector2(margin * -2, 0), Vector2(scroll[p_which]->get_size().x, margin)), false, Color(1, 1, 1), true); - return; - } - Color s_col; - Color v_col; - if (picker_type == SHAPE_OKHSL_CIRCLE) { - s_col.set_ok_hsl(h, 0, v); - } else { - s_col.set_hsv(h, 0, v); - } - left_color = (p_which == 1) ? s_col : Color(0, 0, 0); - if (picker_type == SHAPE_OKHSL_CIRCLE) { - s_col.set_ok_hsl(h, 1, v); - v_col.set_ok_hsl(h, s, 1); - } else { - s_col.set_hsv(h, 1, v); - v_col.set_hsv(h, s, 1); - } - right_color = (p_which == 1) ? s_col : v_col; - } else { - left_color = Color( - p_which == 0 ? 0 : color.r, - p_which == 1 ? 0 : color.g, - p_which == 2 ? 0 : color.b); - right_color = Color( - p_which == 0 ? 1 : color.r, - p_which == 1 ? 1 : color.g, - p_which == 2 ? 1 : color.b); - } - } - - 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)); - - scroll[p_which]->draw_polygon(pos, col); + 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(); if (bev.is_valid()) { if (bev->is_pressed() && bev->get_button_index() == MouseButton::LEFT) { Vector2 center = c->get_size() / 2.0; - if (picker_type == SHAPE_VHS_CIRCLE || picker_type == SHAPE_OKHSL_CIRCLE) { + if (current_picker == SHAPE_VHS_CIRCLE || current_picker == 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()); @@ -951,11 +888,12 @@ void ColorPicker::_uv_input(const Ref<InputEvent> &p_event, Control *c) { } } changing_color = true; - if (picker_type == SHAPE_OKHSL_CIRCLE) { + if (current_picker == SHAPE_OKHSL_CIRCLE) { color.set_ok_hsl(h, s, v, color.a); - } else if (picker_type != SHAPE_OKHSL_CIRCLE) { + } else { color.set_hsv(h, s, v, color.a); } + last_color = color; set_pick_color(color); @@ -981,7 +919,7 @@ void ColorPicker::_uv_input(const Ref<InputEvent> &p_event, Control *c) { } Vector2 center = c->get_size() / 2.0; - if (picker_type == SHAPE_VHS_CIRCLE || picker_type == SHAPE_OKHSL_CIRCLE) { + if (current_picker == SHAPE_VHS_CIRCLE || current_picker == 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; @@ -1002,9 +940,9 @@ void ColorPicker::_uv_input(const Ref<InputEvent> &p_event, Control *c) { v = 1.0 - (y - corner_y) / real_size.y; } } - if (picker_type != SHAPE_OKHSL_CIRCLE) { + if (current_picker != SHAPE_OKHSL_CIRCLE) { color.set_hsv(h, s, v, color.a); - } else if (picker_type == SHAPE_OKHSL_CIRCLE) { + } else { color.set_ok_hsl(h, s, v, color.a); } last_color = color; @@ -1018,12 +956,13 @@ void ColorPicker::_uv_input(const Ref<InputEvent> &p_event, Control *c) { void ColorPicker::_w_input(const Ref<InputEvent> &p_event) { Ref<InputEventMouseButton> bev = p_event; + PickerShapeType actual_shape = _get_actual_shape(); if (bev.is_valid()) { if (bev->is_pressed() && bev->get_button_index() == MouseButton::LEFT) { changing_color = true; float y = CLAMP((float)bev->get_position().y, 0, w_edit->get_size().height); - if (picker_type == SHAPE_VHS_CIRCLE || picker_type == SHAPE_OKHSL_CIRCLE) { + if (actual_shape == SHAPE_VHS_CIRCLE || actual_shape == SHAPE_OKHSL_CIRCLE) { v = 1.0 - (y / w_edit->get_size().height); } else { h = y / w_edit->get_size().height; @@ -1031,9 +970,9 @@ void ColorPicker::_w_input(const Ref<InputEvent> &p_event) { } else { changing_color = false; } - if (picker_type != SHAPE_OKHSL_CIRCLE) { + if (actual_shape != SHAPE_OKHSL_CIRCLE) { color.set_hsv(h, s, v, color.a); - } else if (picker_type == SHAPE_OKHSL_CIRCLE) { + } else { color.set_ok_hsl(h, s, v, color.a); } last_color = color; @@ -1053,16 +992,18 @@ void ColorPicker::_w_input(const Ref<InputEvent> &p_event) { return; } float y = CLAMP((float)mev->get_position().y, 0, w_edit->get_size().height); - if (picker_type == SHAPE_VHS_CIRCLE || picker_type == SHAPE_OKHSL_CIRCLE) { + if (actual_shape == SHAPE_VHS_CIRCLE || actual_shape == SHAPE_OKHSL_CIRCLE) { v = 1.0 - (y / w_edit->get_size().height); } else { h = y / w_edit->get_size().height; } - if (hsv_mode_enabled && picker_type != SHAPE_OKHSL_CIRCLE) { - color.set_hsv(h, s, v, color.a); - } else if (hsv_mode_enabled && picker_type == SHAPE_OKHSL_CIRCLE) { + + if (actual_shape == SHAPE_OKHSL_CIRCLE) { color.set_ok_hsl(h, s, v, color.a); + } else { + color.set_hsv(h, s, v, color.a); } + last_color = color; set_pick_color(color); _update_color(); @@ -1130,11 +1071,11 @@ void ColorPicker::_screen_pick_pressed() { screen = memnew(Control); r->add_child(screen); screen->set_as_top_level(true); - screen->set_anchors_and_offsets_preset(Control::PRESET_WIDE); + screen->set_anchors_and_offsets_preset(Control::PRESET_FULL_RECT); screen->set_default_cursor_shape(CURSOR_POINTING_HAND); screen->connect("gui_input", callable_mp(this, &ColorPicker::_screen_input)); // It immediately toggles off in the first press otherwise. - screen->call_deferred(SNAME("connect"), "hidden", Callable(btn_pick, "set_pressed"), varray(false)); + screen->call_deferred(SNAME("connect"), "hidden", Callable(btn_pick, "set_pressed").bind(false)); } else { screen->show(); } @@ -1153,21 +1094,30 @@ void ColorPicker::_focus_enter() { c_text->select(0, 0); } - for (int i = 0; i < 4; i++) { + 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 < 4; i++) { + 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); } @@ -1207,12 +1157,10 @@ bool ColorPicker::are_presets_visible() const { 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); - ClassDB::bind_method(D_METHOD("set_hsv_mode", "enabled"), &ColorPicker::set_hsv_mode); - ClassDB::bind_method(D_METHOD("is_hsv_mode"), &ColorPicker::is_hsv_mode); - ClassDB::bind_method(D_METHOD("set_raw_mode", "enabled"), &ColorPicker::set_raw_mode); - ClassDB::bind_method(D_METHOD("is_raw_mode"), &ColorPicker::is_raw_mode); ClassDB::bind_method(D_METHOD("set_deferred_mode", "mode"), &ColorPicker::set_deferred_mode); ClassDB::bind_method(D_METHOD("is_deferred_mode"), &ColorPicker::is_deferred_mode); + ClassDB::bind_method(D_METHOD("set_color_mode", "color_mode"), &ColorPicker::set_color_mode); + 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); @@ -1222,13 +1170,12 @@ void ColorPicker::_bind_methods() { 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("set_picker_shape", "picker"), &ColorPicker::set_picker_shape); + 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); 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::BOOL, "hsv_mode"), "set_hsv_mode", "is_hsv_mode"); - ADD_PROPERTY(PropertyInfo(Variant::BOOL, "raw_mode"), "set_raw_mode", "is_raw_mode"); + 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"); @@ -1238,6 +1185,11 @@ void ColorPicker::_bind_methods() { ADD_SIGNAL(MethodInfo("preset_added", PropertyInfo(Variant::COLOR, "color"))); ADD_SIGNAL(MethodInfo("preset_removed", PropertyInfo(Variant::COLOR, "color"))); + BIND_ENUM_CONSTANT(MODE_RGB); + BIND_ENUM_CONSTANT(MODE_HSV); + BIND_ENUM_CONSTANT(MODE_RAW); + BIND_ENUM_CONSTANT(MODE_OKHSL); + BIND_ENUM_CONSTANT(SHAPE_HSV_RECTANGLE); BIND_ENUM_CONSTANT(SHAPE_HSV_WHEEL); BIND_ENUM_CONSTANT(SHAPE_VHS_CIRCLE); @@ -1250,21 +1202,24 @@ ColorPicker::ColorPicker() : add_child(hb_edit, false, INTERNAL_MODE_FRONT); hb_edit->set_v_size_flags(SIZE_EXPAND_FILL); + uv_edit = memnew(Control); hb_edit->add_child(uv_edit); - uv_edit->connect("gui_input", callable_mp(this, &ColorPicker::_uv_input), make_binds(uv_edit)); + uv_edit->connect("gui_input", callable_mp(this, &ColorPicker::_uv_input).bind(uv_edit)); uv_edit->set_mouse_filter(MOUSE_FILTER_PASS); uv_edit->set_h_size_flags(SIZE_EXPAND_FILL); uv_edit->set_v_size_flags(SIZE_EXPAND_FILL); - uv_edit->connect("draw", callable_mp(this, &ColorPicker::_hsv_draw), make_binds(0, uv_edit)); + 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 = memnew(TextureRect); hb_smpl->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); @@ -1277,51 +1232,35 @@ ColorPicker::ColorPicker() : 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); - for (int i = 0; i < 4; i++) { - HBoxContainer *hbc = memnew(HBoxContainer); + GridContainer *gc = memnew(GridContainer); - labels[i] = memnew(Label()); - labels[i]->set_custom_minimum_size(Size2(get_theme_constant(SNAME("label_width")), 0)); - labels[i]->set_v_size_flags(SIZE_SHRINK_CENTER); - hbc->add_child(labels[i]); + vbr->add_child(gc); + gc->set_h_size_flags(SIZE_EXPAND_FILL); + gc->set_columns(3); - scroll[i] = memnew(HSlider); - scroll[i]->set_v_size_flags(SIZE_SHRINK_CENTER); - scroll[i]->set_focus_mode(FOCUS_NONE); - hbc->add_child(scroll[i]); - - values[i] = memnew(SpinBox); - scroll[i]->share(values[i]); - hbc->add_child(values[i]); - values[i]->get_line_edit()->connect("focus_entered", callable_mp(this, &ColorPicker::_focus_enter)); - values[i]->get_line_edit()->connect("focus_exited", callable_mp(this, &ColorPicker::_focus_exit)); - - scroll[i]->set_min(0); - scroll[i]->set_page(0); - scroll[i]->set_h_size_flags(SIZE_EXPAND_FILL); - - scroll[i]->connect("value_changed", callable_mp(this, &ColorPicker::_value_changed)); - scroll[i]->connect("draw", callable_mp(this, &ColorPicker::_slider_draw), make_binds(i)); - - vbr->add_child(hbc); + for (int i = 0; i < SLIDER_COUNT + 1; i++) { + create_slider(gc, i); } - labels[3]->set_text("A"); + alpha_label->set_text("A"); HBoxContainer *hhb = memnew(HBoxContainer); vbr->add_child(hhb); - hhb->add_child(btn_hsv); - btn_hsv->set_text(RTR("HSV")); - btn_hsv->connect("toggled", callable_mp(this, &ColorPicker::set_hsv_mode)); + mode_option_button = memnew(OptionButton); - hhb->add_child(btn_raw); - btn_raw->set_text(RTR("Raw")); - btn_raw->connect("toggled", callable_mp(this, &ColorPicker::set_raw_mode)); + 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)); + text_type = memnew(Button); hhb->add_child(text_type); text_type->set_text("#"); text_type->set_tooltip(RTR("Switch between hexadecimal and code values.")); @@ -1332,12 +1271,14 @@ ColorPicker::ColorPicker() : text_type->set_mouse_filter(MOUSE_FILTER_IGNORE); } + c_text = memnew(LineEdit); hhb->add_child(c_text); c_text->set_h_size_flags(SIZE_EXPAND_FILL); 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("focus_exited", callable_mp(this, &ColorPicker::_html_focus_exit)); + wheel_edit = memnew(AspectRatioContainer); wheel_edit->set_h_size_flags(SIZE_EXPAND_FILL); wheel_edit->set_v_size_flags(SIZE_EXPAND_FILL); hb_edit->add_child(wheel_edit); @@ -1347,41 +1288,53 @@ ColorPicker::ColorPicker() : circle_mat.instantiate(); circle_mat->set_shader(circle_shader); + wheel_margin = memnew(MarginContainer); wheel_margin->add_theme_constant_override("margin_bottom", 8); wheel_edit->add_child(wheel_margin); + wheel = memnew(Control); wheel_margin->add_child(wheel); wheel->set_mouse_filter(MOUSE_FILTER_PASS); - wheel->connect("draw", callable_mp(this, &ColorPicker::_hsv_draw), make_binds(2, wheel)); + wheel->connect("draw", callable_mp(this, &ColorPicker::_hsv_draw).bind(2, wheel)); + wheel_uv = memnew(Control); wheel_margin->add_child(wheel_uv); - wheel_uv->connect("gui_input", callable_mp(this, &ColorPicker::_uv_input), make_binds(wheel_uv)); - wheel_uv->connect("draw", callable_mp(this, &ColorPicker::_hsv_draw), make_binds(0, wheel_uv)); + wheel_uv->connect("gui_input", callable_mp(this, &ColorPicker::_uv_input).bind(wheel_uv)); + wheel_uv->connect("draw", callable_mp(this, &ColorPicker::_hsv_draw).bind(0, wheel_uv)); + w_edit = memnew(Control); hb_edit->add_child(w_edit); w_edit->set_h_size_flags(SIZE_FILL); w_edit->set_v_size_flags(SIZE_EXPAND_FILL); w_edit->connect("gui_input", callable_mp(this, &ColorPicker::_w_input)); - w_edit->connect("draw", callable_mp(this, &ColorPicker::_hsv_draw), make_binds(1, w_edit)); + w_edit->connect("draw", callable_mp(this, &ColorPicker::_hsv_draw).bind(1, w_edit)); - picker_type = SHAPE_HSV_RECTANGLE; _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); add_child(preset_container, false, INTERNAL_MODE_FRONT); + btn_add_preset = memnew(Button); btn_add_preset->set_icon_alignment(HORIZONTAL_ALIGNMENT_CENTER); btn_add_preset->set_tooltip(RTR("Add current color as a preset.")); btn_add_preset->connect("pressed", callable_mp(this, &ColorPicker::_add_preset_pressed)); preset_container->add_child(btn_add_preset); } +ColorPicker::~ColorPicker() { + for (int i = 0; i < modes.size(); i++) { + delete modes[i]; + } +} + ///////////////// void ColorPickerButton::_about_to_popup() { @@ -1503,7 +1456,7 @@ void ColorPickerButton::_update_picker() { popup = memnew(PopupPanel); popup->set_wrap_controls(true); picker = memnew(ColorPicker); - picker->set_anchors_and_offsets_preset(PRESET_WIDE); + picker->set_anchors_and_offsets_preset(PRESET_FULL_RECT); popup->add_child(picker); add_child(popup, false, INTERNAL_MODE_FRONT); picker->connect("color_changed", callable_mp(this, &ColorPickerButton::_color_changed)); diff --git a/scene/gui/color_picker.h b/scene/gui/color_picker.h index 953be032ec..8e65ee1861 100644 --- a/scene/gui/color_picker.h +++ b/scene/gui/color_picker.h @@ -34,16 +34,23 @@ #include "scene/gui/aspect_ratio_container.h" #include "scene/gui/box_container.h" #include "scene/gui/button.h" -#include "scene/gui/check_button.h" +#include "scene/gui/control.h" #include "scene/gui/grid_container.h" #include "scene/gui/label.h" #include "scene/gui/line_edit.h" +#include "scene/gui/option_button.h" #include "scene/gui/popup.h" #include "scene/gui/separator.h" #include "scene/gui/slider.h" #include "scene/gui/spin_box.h" #include "scene/gui/texture_rect.h" +class ColorMode; +class ColorModeRGB; +class ColorModeHSV; +class ColorModeRAW; +class ColorModeOKHSL; + class ColorPresetButton : public BaseButton { GDCLASS(ColorPresetButton, BaseButton); @@ -64,6 +71,15 @@ class ColorPicker : public BoxContainer { GDCLASS(ColorPicker, BoxContainer); public: + enum ColorModeType { + MODE_RGB, + MODE_HSV, + MODE_RAW, + MODE_OKHSL, + + MODE_MAX + }; + enum PickerShapeType { SHAPE_HSV_RECTANGLE, SHAPE_HSV_WHEEL, @@ -73,38 +89,52 @@ public: SHAPE_MAX }; + static const int SLIDER_COUNT = 4; + private: static Ref<Shader> wheel_shader; static Ref<Shader> circle_shader; static Ref<Shader> circle_ok_color_shader; static List<Color> preset_cache; + int current_slider_count = SLIDER_COUNT; + + bool slider_theme_modified = true; + + Vector<ColorMode *> modes; + Control *screen = nullptr; - Control *uv_edit = memnew(Control); - Control *w_edit = memnew(Control); - AspectRatioContainer *wheel_edit = memnew(AspectRatioContainer); - MarginContainer *wheel_margin = memnew(MarginContainer); + Control *uv_edit = nullptr; + Control *w_edit = nullptr; + AspectRatioContainer *wheel_edit = nullptr; + MarginContainer *wheel_margin = nullptr; Ref<ShaderMaterial> wheel_mat; Ref<ShaderMaterial> circle_mat; - Control *wheel = memnew(Control); - Control *wheel_uv = memnew(Control); - TextureRect *sample = memnew(TextureRect); - GridContainer *preset_container = memnew(GridContainer); - HSeparator *preset_separator = memnew(HSeparator); - Button *btn_add_preset = memnew(Button); - Button *btn_pick = memnew(Button); - CheckButton *btn_hsv = memnew(CheckButton); - CheckButton *btn_raw = memnew(CheckButton); - HSlider *scroll[4]; - SpinBox *values[4]; - Label *labels[4]; - Button *text_type = memnew(Button); - LineEdit *c_text = memnew(LineEdit); + Control *wheel = nullptr; + Control *wheel_uv = nullptr; + TextureRect *sample = nullptr; + GridContainer *preset_container = nullptr; + HSeparator *preset_separator = nullptr; + Button *btn_add_preset = nullptr; + Button *btn_pick = nullptr; + + OptionButton *mode_option_button = nullptr; + + HSlider *sliders[SLIDER_COUNT]; + SpinBox *values[SLIDER_COUNT]; + Label *labels[SLIDER_COUNT]; + Button *text_type = nullptr; + LineEdit *c_text = nullptr; + + HSlider *alpha_slider = nullptr; + SpinBox *alpha_value = nullptr; + Label *alpha_label = nullptr; bool edit_alpha = true; Size2i ms; bool text_is_constructor = false; - PickerShapeType picker_type = SHAPE_HSV_WHEEL; + PickerShapeType current_shape = SHAPE_HSV_RECTANGLE; + ColorModeType current_mode = MODE_RGB; const int preset_column_count = 9; int prev_preset_size = 0; @@ -114,8 +144,6 @@ private: Color old_color; bool display_old_color = false; - bool raw_mode_enabled = false; - bool hsv_mode_enabled = false; bool deferred_mode_enabled = false; bool updating = true; bool changing_color = false; @@ -128,6 +156,9 @@ private: float v = 0.0; Color last_color; + PickerShapeType _get_actual_shape() const; + void create_slider(GridContainer *gc, int idx); + void _reset_theme(); void _html_submitted(const String &p_html); void _value_changed(double); void _update_controls(); @@ -152,14 +183,21 @@ private: inline int _get_preset_size(); void _add_preset_button(int p_size, const Color &p_color); + void _set_color_mode(ColorModeType p_mode); + protected: void _notification(int); static void _bind_methods(); public: + HSlider *get_slider(int idx); + Vector<float> get_active_slider_values(); + static void init_shaders(); static void finish_shaders(); + void add_mode(ColorMode *p_mode); + void set_edit_alpha(bool p_show); bool is_editing_alpha() const; @@ -173,7 +211,7 @@ public: void set_display_old_color(bool p_enabled); bool is_displaying_old_color() const; - void set_picker_shape(PickerShapeType p_picker_type); + void set_picker_shape(PickerShapeType p_shape); PickerShapeType get_picker_shape() const; void add_preset(const Color &p_color); @@ -181,11 +219,8 @@ public: PackedColorArray get_presets() const; void _update_presets(); - void set_hsv_mode(bool p_enabled); - bool is_hsv_mode() const; - - void set_raw_mode(bool p_enabled); - bool is_raw_mode() const; + void set_color_mode(ColorModeType p_mode); + ColorModeType get_color_mode() const; void set_deferred_mode(bool p_enabled); bool is_deferred_mode() const; @@ -199,6 +234,7 @@ public: void set_focus_on_line_edit(); ColorPicker(); + ~ColorPicker(); }; class ColorPickerButton : public Button { @@ -239,4 +275,6 @@ public: }; VARIANT_ENUM_CAST(ColorPicker::PickerShapeType); +VARIANT_ENUM_CAST(ColorPicker::ColorModeType); + #endif // COLOR_PICKER_H diff --git a/scene/gui/control.cpp b/scene/gui/control.cpp index 118e77c009..06aa913eb1 100644 --- a/scene/gui/control.cpp +++ b/scene/gui/control.cpp @@ -50,6 +50,9 @@ #include "editor/plugins/control_editor_plugin.h" #endif +// Editor plugin interoperability. + +// TODO: Decouple controls from their editor plugin and get rid of this. #ifdef TOOLS_ENABLED Dictionary Control::_edit_get_state() const { Dictionary s; @@ -181,6 +184,49 @@ Size2 Control::_edit_get_minimum_size() const { } #endif +// Editor integration. + +void Control::get_argument_options(const StringName &p_function, int p_idx, List<String> *r_options) const { + Node::get_argument_options(p_function, p_idx, r_options); + + if (p_idx == 0) { + List<StringName> sn; + String pf = p_function; + if (pf == "add_theme_color_override" || pf == "has_theme_color" || pf == "has_theme_color_override" || pf == "get_theme_color") { + Theme::get_default()->get_color_list(get_class(), &sn); + } else if (pf == "add_theme_style_override" || pf == "has_theme_style" || pf == "has_theme_style_override" || pf == "get_theme_style") { + Theme::get_default()->get_stylebox_list(get_class(), &sn); + } else if (pf == "add_theme_font_override" || pf == "has_theme_font" || pf == "has_theme_font_override" || pf == "get_theme_font") { + Theme::get_default()->get_font_list(get_class(), &sn); + } else if (pf == "add_theme_font_size_override" || pf == "has_theme_font_size" || pf == "has_theme_font_size_override" || pf == "get_theme_font_size") { + Theme::get_default()->get_font_size_list(get_class(), &sn); + } else if (pf == "add_theme_constant_override" || pf == "has_theme_constant" || pf == "has_theme_constant_override" || pf == "get_theme_constant") { + Theme::get_default()->get_constant_list(get_class(), &sn); + } + + sn.sort_custom<StringName::AlphCompare>(); + for (const StringName &name : sn) { + r_options->push_back(String(name).quote()); + } + } +} + +TypedArray<String> Control::get_configuration_warnings() const { + TypedArray<String> warnings = Node::get_configuration_warnings(); + + if (data.mouse_filter == MOUSE_FILTER_IGNORE && !data.tooltip.is_empty()) { + warnings.push_back(RTR("The Hint Tooltip won't be displayed as the control's Mouse Filter is set to \"Ignore\". To solve this, set the Mouse Filter to \"Stop\" or \"Pass\".")); + } + + return warnings; +} + +bool Control::is_text_field() const { + return false; +} + +// Dynamic properties. + String Control::properties_managed_by_container[] = { "offset_left", "offset_top", @@ -196,58 +242,6 @@ String Control::properties_managed_by_container[] = { "size" }; -void Control::accept_event() { - if (is_inside_tree()) { - get_viewport()->_gui_accept_event(); - } -} - -void Control::set_custom_minimum_size(const Size2 &p_custom) { - if (p_custom == data.custom_minimum_size) { - return; - } - data.custom_minimum_size = p_custom; - update_minimum_size(); -} - -Size2 Control::get_custom_minimum_size() const { - return data.custom_minimum_size; -} - -void Control::_update_minimum_size_cache() { - Size2 minsize = get_minimum_size(); - minsize.x = MAX(minsize.x, data.custom_minimum_size.x); - minsize.y = MAX(minsize.y, data.custom_minimum_size.y); - - bool size_changed = false; - if (data.minimum_size_cache != minsize) { - size_changed = true; - } - - data.minimum_size_cache = minsize; - data.minimum_size_valid = true; - - if (size_changed) { - update_minimum_size(); - } -} - -Size2 Control::get_combined_minimum_size() const { - if (!data.minimum_size_valid) { - const_cast<Control *>(this)->_update_minimum_size_cache(); - } - return data.minimum_size_cache; -} - -Transform2D Control::_get_internal_transform() const { - Transform2D rot_scale; - rot_scale.set_rotation_and_scale(data.rotation, data.scale); - Transform2D offset; - offset.set_origin(-data.pivot_offset); - - return offset.affine_inverse() * (rot_scale * offset); -} - bool Control::_set(const StringName &p_name, const Variant &p_value) { String name = p_name; if (!name.begins_with("theme_override")) { @@ -258,21 +252,21 @@ bool Control::_set(const StringName &p_name, const Variant &p_value) { if (name.begins_with("theme_override_icons/")) { String dname = name.get_slicec('/', 1); if (data.icon_override.has(dname)) { - data.icon_override[dname]->disconnect("changed", callable_mp(this, &Control::_override_changed)); + data.icon_override[dname]->disconnect("changed", callable_mp(this, &Control::_theme_property_override_changed)); } data.icon_override.erase(dname); notification(NOTIFICATION_THEME_CHANGED); } else if (name.begins_with("theme_override_styles/")) { String dname = name.get_slicec('/', 1); if (data.style_override.has(dname)) { - data.style_override[dname]->disconnect("changed", callable_mp(this, &Control::_override_changed)); + data.style_override[dname]->disconnect("changed", callable_mp(this, &Control::_theme_property_override_changed)); } data.style_override.erase(dname); notification(NOTIFICATION_THEME_CHANGED); } else if (name.begins_with("theme_override_fonts/")) { String dname = name.get_slicec('/', 1); if (data.font_override.has(dname)) { - data.font_override[dname]->disconnect("changed", callable_mp(this, &Control::_override_changed)); + data.font_override[dname]->disconnect("changed", callable_mp(this, &Control::_theme_property_override_changed)); } data.font_override.erase(dname); notification(NOTIFICATION_THEME_CHANGED); @@ -318,21 +312,6 @@ bool Control::_set(const StringName &p_name, const Variant &p_value) { return true; } -void Control::_update_minimum_size() { - if (!is_inside_tree()) { - return; - } - - Size2 minsize = get_combined_minimum_size(); - data.updating_last_minimum_size = false; - - if (minsize != data.last_minimum_size) { - data.last_minimum_size = minsize; - _size_changed(); - emit_signal(SceneStringNames::get_singleton()->minimum_size_changed); - } -} - bool Control::_get(const StringName &p_name, Variant &r_ret) const { String sname = p_name; if (!sname.begins_with("theme_override")) { @@ -586,6 +565,12 @@ void Control::_validate_property(PropertyInfo &property) const { } } +// Global relations. + +bool Control::is_top_level_control() const { + return is_inside_tree() && (!data.parent_canvas_item && !data.RI && is_set_as_top_level()); +} + Control *Control::get_parent_control() const { return data.parent; } @@ -594,97 +579,64 @@ Window *Control::get_parent_window() const { return data.parent_window; } -void Control::set_layout_direction(Control::LayoutDirection p_direction) { - ERR_FAIL_INDEX((int)p_direction, 4); - - data.layout_dir = p_direction; - data.is_rtl_dirty = true; - - propagate_notification(NOTIFICATION_LAYOUT_DIRECTION_CHANGED); -} +Control *Control::get_root_parent_control() const { + const CanvasItem *ci = this; + const Control *root = this; -Control::LayoutDirection Control::get_layout_direction() const { - return data.layout_dir; -} + while (ci) { + const Control *c = Object::cast_to<Control>(ci); + if (c) { + root = c; -bool Control::is_layout_rtl() const { - if (data.is_rtl_dirty) { - const_cast<Control *>(this)->data.is_rtl_dirty = false; - if (data.layout_dir == LAYOUT_DIRECTION_INHERITED) { - Window *parent_window = get_parent_window(); - Control *parent_control = get_parent_control(); - if (parent_control) { - const_cast<Control *>(this)->data.is_rtl = parent_control->is_layout_rtl(); - } else if (parent_window) { - const_cast<Control *>(this)->data.is_rtl = parent_window->is_layout_rtl(); - } else { - if (GLOBAL_GET(SNAME("internationalization/rendering/force_right_to_left_layout_direction"))) { - const_cast<Control *>(this)->data.is_rtl = true; - } else { - String locale = TranslationServer::get_singleton()->get_tool_locale(); - const_cast<Control *>(this)->data.is_rtl = TS->is_locale_right_to_left(locale); - } - } - } else if (data.layout_dir == LAYOUT_DIRECTION_LOCALE) { - if (GLOBAL_GET(SNAME("internationalization/rendering/force_right_to_left_layout_direction"))) { - const_cast<Control *>(this)->data.is_rtl = true; - } else { - String locale = TranslationServer::get_singleton()->get_tool_locale(); - const_cast<Control *>(this)->data.is_rtl = TS->is_locale_right_to_left(locale); + if (c->data.RI || c->is_top_level_control()) { + break; } - } else { - const_cast<Control *>(this)->data.is_rtl = (data.layout_dir == LAYOUT_DIRECTION_RTL); } - } - return data.is_rtl; -} -void Control::set_auto_translate(bool p_enable) { - if (p_enable == data.auto_translate) { - return; + ci = ci->get_parent_item(); } - data.auto_translate = p_enable; - - notification(MainLoop::NOTIFICATION_TRANSLATION_CHANGED); -} - -bool Control::is_auto_translating() const { - return data.auto_translate; -} - -void Control::_clear_size_warning() { - data.size_warning = false; + return const_cast<Control *>(root); } -//moved theme configuration here, so controls can set up even if still not inside active scene - -void Control::add_child_notify(Node *p_child) { - Control *child_c = Object::cast_to<Control>(p_child); - - if (child_c && child_c->data.theme.is_null() && (data.theme_owner || data.theme_owner_window)) { - _propagate_theme_changed(child_c, data.theme_owner, data.theme_owner_window); //need to propagate here, since many controls may require setting up stuff +Rect2 Control::get_parent_anchorable_rect() const { + if (!is_inside_tree()) { + return Rect2(); } - Window *child_w = Object::cast_to<Window>(p_child); + Rect2 parent_rect; + if (data.parent_canvas_item) { + parent_rect = data.parent_canvas_item->get_anchorable_rect(); + } else { +#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")); + } else { + parent_rect = get_viewport()->get_visible_rect(); + } - if (child_w && child_w->theme.is_null() && (data.theme_owner || data.theme_owner_window)) { - _propagate_theme_changed(child_w, data.theme_owner, data.theme_owner_window); //need to propagate here, since many controls may require setting up stuff +#else + parent_rect = get_viewport()->get_visible_rect(); +#endif } + + return parent_rect; } -void Control::remove_child_notify(Node *p_child) { - Control *child_c = Object::cast_to<Control>(p_child); +Size2 Control::get_parent_area_size() const { + return get_parent_anchorable_rect().size; +} - if (child_c && (child_c->data.theme_owner || child_c->data.theme_owner_window) && child_c->data.theme.is_null()) { - _propagate_theme_changed(child_c, nullptr, nullptr); - } +// Positioning and sizing. - Window *child_w = Object::cast_to<Window>(p_child); +Transform2D Control::_get_internal_transform() const { + Transform2D rot_scale; + rot_scale.set_rotation_and_scale(data.rotation, data.scale); + Transform2D offset; + offset.set_origin(-data.pivot_offset); - if (child_w && (child_w->theme_owner || child_w->theme_owner_window) && child_w->theme.is_null()) { - _propagate_theme_changed(child_w, nullptr, nullptr); - } + return offset.affine_inverse() * (rot_scale * offset); } void Control::_update_canvas_item_transform() { @@ -699,815 +651,146 @@ void Control::_update_canvas_item_transform() { RenderingServer::get_singleton()->canvas_item_set_transform(get_canvas_item(), xform); } -void Control::_notification(int p_notification) { - switch (p_notification) { - case NOTIFICATION_POST_ENTER_TREE: { - data.minimum_size_valid = false; - data.is_rtl_dirty = true; - _size_changed(); - } break; - - case NOTIFICATION_EXIT_TREE: { - release_focus(); - get_viewport()->_gui_remove_control(this); - } break; - - case NOTIFICATION_READY: { -#ifdef DEBUG_ENABLED - connect("ready", callable_mp(this, &Control::_clear_size_warning), varray(), CONNECT_DEFERRED | CONNECT_ONESHOT); -#endif - } break; - - case NOTIFICATION_ENTER_CANVAS: { - data.parent = Object::cast_to<Control>(get_parent()); - data.parent_window = Object::cast_to<Window>(get_parent()); - data.is_rtl_dirty = true; - - if (data.theme.is_null()) { - if (data.parent && (data.parent->data.theme_owner || data.parent->data.theme_owner_window)) { - data.theme_owner = data.parent->data.theme_owner; - data.theme_owner_window = data.parent->data.theme_owner_window; - notification(NOTIFICATION_THEME_CHANGED); - } else if (data.parent_window && (data.parent_window->theme_owner || data.parent_window->theme_owner_window)) { - data.theme_owner = data.parent_window->theme_owner; - data.theme_owner_window = data.parent_window->theme_owner_window; - notification(NOTIFICATION_THEME_CHANGED); - } - } - - CanvasItem *node = this; - bool has_parent_control = false; - - while (!node->is_set_as_top_level()) { - CanvasItem *parent = Object::cast_to<CanvasItem>(node->get_parent()); - if (!parent) { - break; - } - - Control *parent_control = Object::cast_to<Control>(parent); - if (parent_control) { - has_parent_control = true; - break; - } - - node = parent; - } - - if (has_parent_control) { - // Do nothing, has a parent control. - } else { - // Is a regular root control or top_level. - Viewport *viewport = get_viewport(); - ERR_FAIL_COND(!viewport); - data.RI = viewport->_gui_add_root_control(this); - } - - data.parent_canvas_item = get_parent_item(); - - if (data.parent_canvas_item) { - data.parent_canvas_item->connect("item_rect_changed", callable_mp(this, &Control::_size_changed)); - } else { - // Connect viewport. - Viewport *viewport = get_viewport(); - ERR_FAIL_COND(!viewport); - viewport->connect("size_changed", callable_mp(this, &Control::_size_changed)); - } - } break; - - case NOTIFICATION_EXIT_CANVAS: { - if (data.parent_canvas_item) { - data.parent_canvas_item->disconnect("item_rect_changed", callable_mp(this, &Control::_size_changed)); - data.parent_canvas_item = nullptr; - } else if (!is_set_as_top_level()) { - //disconnect viewport - Viewport *viewport = get_viewport(); - ERR_FAIL_COND(!viewport); - viewport->disconnect("size_changed", callable_mp(this, &Control::_size_changed)); - } - - if (data.RI) { - get_viewport()->_gui_remove_root_control(data.RI); - data.RI = nullptr; - } - - data.parent = nullptr; - data.parent_canvas_item = nullptr; - data.parent_window = nullptr; - data.is_rtl_dirty = true; - } break; - - case NOTIFICATION_MOVED_IN_PARENT: { - // some parents need to know the order of the children to draw (like TabContainer) - // update if necessary - if (data.parent) { - data.parent->update(); - } - update(); - - if (data.RI) { - get_viewport()->_gui_set_root_order_dirty(); - } - } break; - - case NOTIFICATION_RESIZED: { - emit_signal(SceneStringNames::get_singleton()->resized); - } break; - - case NOTIFICATION_DRAW: { - _update_canvas_item_transform(); - RenderingServer::get_singleton()->canvas_item_set_custom_rect(get_canvas_item(), !data.disable_visibility_clip, Rect2(Point2(), get_size())); - RenderingServer::get_singleton()->canvas_item_set_clip(get_canvas_item(), data.clip_contents); - } break; - - case NOTIFICATION_MOUSE_ENTER: { - emit_signal(SceneStringNames::get_singleton()->mouse_entered); - } break; - - case NOTIFICATION_MOUSE_EXIT: { - emit_signal(SceneStringNames::get_singleton()->mouse_exited); - } break; - - case NOTIFICATION_FOCUS_ENTER: { - emit_signal(SceneStringNames::get_singleton()->focus_entered); - update(); - } break; - - case NOTIFICATION_FOCUS_EXIT: { - emit_signal(SceneStringNames::get_singleton()->focus_exited); - update(); - } break; - - case NOTIFICATION_THEME_CHANGED: { - update_minimum_size(); - update(); - } break; - - case NOTIFICATION_VISIBILITY_CHANGED: { - if (!is_visible_in_tree()) { - if (get_viewport() != nullptr) { - get_viewport()->_gui_hide_control(this); - } - } else { - data.minimum_size_valid = false; - _update_minimum_size(); - _size_changed(); - } - } break; - - case NOTIFICATION_TRANSLATION_CHANGED: - case NOTIFICATION_LAYOUT_DIRECTION_CHANGED: { - if (is_inside_tree()) { - data.is_rtl_dirty = true; - _size_changed(); - } - } break; - } -} - -bool Control::has_point(const Point2 &p_point) const { - bool ret; - if (GDVIRTUAL_CALL(_has_point, p_point, ret)) { - return ret; - } - return Rect2(Point2(), get_size()).has_point(p_point); -} - -void Control::set_drag_forwarding(Object *p_target) { - if (p_target) { - data.drag_owner = p_target->get_instance_id(); - } else { - data.drag_owner = ObjectID(); - } -} - -Variant Control::get_drag_data(const Point2 &p_point) { - if (data.drag_owner.is_valid()) { - Object *obj = ObjectDB::get_instance(data.drag_owner); - if (obj) { - return obj->call("_get_drag_data_fw", p_point, this); - } - } - - Variant dd; - if (GDVIRTUAL_CALL(_get_drag_data, p_point, dd)) { - return dd; - } - - return Variant(); -} - -bool Control::can_drop_data(const Point2 &p_point, const Variant &p_data) const { - if (data.drag_owner.is_valid()) { - Object *obj = ObjectDB::get_instance(data.drag_owner); - if (obj) { - return obj->call("_can_drop_data_fw", p_point, p_data, this); - } - } - - bool ret; - if (GDVIRTUAL_CALL(_can_drop_data, p_point, p_data, ret)) { - return ret; - } - return false; -} - -void Control::drop_data(const Point2 &p_point, const Variant &p_data) { - if (data.drag_owner.is_valid()) { - Object *obj = ObjectDB::get_instance(data.drag_owner); - if (obj) { - obj->call("_drop_data_fw", p_point, p_data, this); - return; - } - } - - GDVIRTUAL_CALL(_drop_data, p_point, p_data); -} - -void Control::force_drag(const Variant &p_data, Control *p_control) { - ERR_FAIL_COND(!is_inside_tree()); - ERR_FAIL_COND(p_data.get_type() == Variant::NIL); - - get_viewport()->_gui_force_drag(this, p_data, p_control); -} - -void Control::set_drag_preview(Control *p_control) { - ERR_FAIL_COND(!is_inside_tree()); - ERR_FAIL_COND(!get_viewport()->gui_is_dragging()); - get_viewport()->_gui_set_drag_preview(this, p_control); -} - -bool Control::is_drag_successful() const { - return is_inside_tree() && get_viewport()->gui_is_drag_successful(); -} - -void Control::_call_gui_input(const Ref<InputEvent> &p_event) { - emit_signal(SceneStringNames::get_singleton()->gui_input, p_event); //signal should be first, so it's possible to override an event (and then accept it) - if (!is_inside_tree() || get_viewport()->is_input_handled()) { - return; //input was handled, abort - } - GDVIRTUAL_CALL(_gui_input, p_event); - if (!is_inside_tree() || get_viewport()->is_input_handled()) { - return; //input was handled, abort - } - gui_input(p_event); -} -void Control::gui_input(const Ref<InputEvent> &p_event) { +Transform2D Control::get_transform() const { + Transform2D xform = _get_internal_transform(); + xform[2] += get_position(); + return xform; } -Size2 Control::get_minimum_size() const { - Vector2 ms; - if (GDVIRTUAL_CALL(_get_minimum_size, ms)) { - return ms; - } - return Vector2(); -} +/// Anchors and offsets. -template <class T> -T Control::get_theme_item_in_types(Control *p_theme_owner, Window *p_theme_owner_window, Theme::DataType p_data_type, const StringName &p_name, List<StringName> p_theme_types) { - ERR_FAIL_COND_V_MSG(p_theme_types.size() == 0, T(), "At least one theme type must be specified."); - - // First, look through each control or window node in the branch, until no valid parent can be found. - // Only nodes with a theme resource attached are considered. - Control *theme_owner = p_theme_owner; - Window *theme_owner_window = p_theme_owner_window; - - while (theme_owner || theme_owner_window) { - // For each theme resource check the theme types provided and see if p_name exists with any of them. - for (const StringName &E : p_theme_types) { - if (theme_owner && theme_owner->data.theme->has_theme_item(p_data_type, p_name, E)) { - return theme_owner->data.theme->get_theme_item(p_data_type, p_name, E); - } - - if (theme_owner_window && theme_owner_window->theme->has_theme_item(p_data_type, p_name, E)) { - return theme_owner_window->theme->get_theme_item(p_data_type, p_name, E); - } - } - - Node *parent = theme_owner ? theme_owner->get_parent() : theme_owner_window->get_parent(); - Control *parent_c = Object::cast_to<Control>(parent); - if (parent_c) { - theme_owner = parent_c->data.theme_owner; - theme_owner_window = parent_c->data.theme_owner_window; - } else { - Window *parent_w = Object::cast_to<Window>(parent); - if (parent_w) { - theme_owner = parent_w->theme_owner; - theme_owner_window = parent_w->theme_owner_window; - } else { - theme_owner = nullptr; - theme_owner_window = nullptr; - } - } - } - - // Secondly, check the project-defined Theme resource. - if (Theme::get_project_default().is_valid()) { - for (const StringName &E : p_theme_types) { - if (Theme::get_project_default()->has_theme_item(p_data_type, p_name, E)) { - return Theme::get_project_default()->get_theme_item(p_data_type, p_name, E); - } - } - } - - // Lastly, fall back on the items defined in the default Theme, if they exist. - for (const StringName &E : p_theme_types) { - if (Theme::get_default()->has_theme_item(p_data_type, p_name, E)) { - return Theme::get_default()->get_theme_item(p_data_type, p_name, E); - } - } - // If they don't exist, use any type to return the default/empty value. - return Theme::get_default()->get_theme_item(p_data_type, p_name, p_theme_types[0]); +void Control::_set_anchor(Side p_side, real_t p_anchor) { + set_anchor(p_side, p_anchor); } -bool Control::has_theme_item_in_types(Control *p_theme_owner, Window *p_theme_owner_window, Theme::DataType p_data_type, const StringName &p_name, List<StringName> p_theme_types) { - ERR_FAIL_COND_V_MSG(p_theme_types.size() == 0, false, "At least one theme type must be specified."); - - // First, look through each control or window node in the branch, until no valid parent can be found. - // Only nodes with a theme resource attached are considered. - Control *theme_owner = p_theme_owner; - Window *theme_owner_window = p_theme_owner_window; - - while (theme_owner || theme_owner_window) { - // For each theme resource check the theme types provided and see if p_name exists with any of them. - for (const StringName &E : p_theme_types) { - if (theme_owner && theme_owner->data.theme->has_theme_item(p_data_type, p_name, E)) { - return true; - } - - if (theme_owner_window && theme_owner_window->theme->has_theme_item(p_data_type, p_name, E)) { - return true; - } - } - - Node *parent = theme_owner ? theme_owner->get_parent() : theme_owner_window->get_parent(); - Control *parent_c = Object::cast_to<Control>(parent); - if (parent_c) { - theme_owner = parent_c->data.theme_owner; - theme_owner_window = parent_c->data.theme_owner_window; - } else { - Window *parent_w = Object::cast_to<Window>(parent); - if (parent_w) { - theme_owner = parent_w->theme_owner; - theme_owner_window = parent_w->theme_owner_window; - } else { - theme_owner = nullptr; - theme_owner_window = nullptr; - } - } - } +void Control::set_anchor(Side p_side, real_t p_anchor, bool p_keep_offset, bool p_push_opposite_anchor) { + ERR_FAIL_INDEX((int)p_side, 4); - // Secondly, check the project-defined Theme resource. - if (Theme::get_project_default().is_valid()) { - for (const StringName &E : p_theme_types) { - if (Theme::get_project_default()->has_theme_item(p_data_type, p_name, E)) { - return true; - } - } - } + Rect2 parent_rect = get_parent_anchorable_rect(); + real_t parent_range = (p_side == SIDE_LEFT || p_side == SIDE_RIGHT) ? parent_rect.size.x : parent_rect.size.y; + real_t previous_pos = data.offset[p_side] + data.anchor[p_side] * parent_range; + real_t previous_opposite_pos = data.offset[(p_side + 2) % 4] + data.anchor[(p_side + 2) % 4] * parent_range; - // Lastly, fall back on the items defined in the default Theme, if they exist. - for (const StringName &E : p_theme_types) { - if (Theme::get_default()->has_theme_item(p_data_type, p_name, E)) { - return true; - } - } - return false; -} + data.anchor[p_side] = p_anchor; -void Control::_get_theme_type_dependencies(const StringName &p_theme_type, List<StringName> *p_list) const { - if (p_theme_type == StringName() || p_theme_type == get_class_name() || p_theme_type == data.theme_type_variation) { - if (Theme::get_project_default().is_valid() && Theme::get_project_default()->get_type_variation_base(data.theme_type_variation) != StringName()) { - Theme::get_project_default()->get_type_dependencies(get_class_name(), data.theme_type_variation, p_list); + if (((p_side == SIDE_LEFT || p_side == SIDE_TOP) && data.anchor[p_side] > data.anchor[(p_side + 2) % 4]) || + ((p_side == SIDE_RIGHT || p_side == SIDE_BOTTOM) && data.anchor[p_side] < data.anchor[(p_side + 2) % 4])) { + if (p_push_opposite_anchor) { + data.anchor[(p_side + 2) % 4] = data.anchor[p_side]; } else { - Theme::get_default()->get_type_dependencies(get_class_name(), data.theme_type_variation, p_list); - } - } else { - Theme::get_default()->get_type_dependencies(p_theme_type, StringName(), p_list); - } -} - -Ref<Texture2D> Control::get_theme_icon(const StringName &p_name, const StringName &p_theme_type) const { - if (p_theme_type == StringName() || p_theme_type == get_class_name() || p_theme_type == data.theme_type_variation) { - const Ref<Texture2D> *tex = data.icon_override.getptr(p_name); - if (tex) { - return *tex; - } - } - - List<StringName> theme_types; - _get_theme_type_dependencies(p_theme_type, &theme_types); - return get_theme_item_in_types<Ref<Texture2D>>(data.theme_owner, data.theme_owner_window, Theme::DATA_TYPE_ICON, p_name, theme_types); -} - -Ref<StyleBox> Control::get_theme_stylebox(const StringName &p_name, const StringName &p_theme_type) const { - if (p_theme_type == StringName() || p_theme_type == get_class_name() || p_theme_type == data.theme_type_variation) { - const Ref<StyleBox> *style = data.style_override.getptr(p_name); - if (style) { - return *style; - } - } - - List<StringName> theme_types; - _get_theme_type_dependencies(p_theme_type, &theme_types); - return get_theme_item_in_types<Ref<StyleBox>>(data.theme_owner, data.theme_owner_window, Theme::DATA_TYPE_STYLEBOX, p_name, theme_types); -} - -Ref<Font> Control::get_theme_font(const StringName &p_name, const StringName &p_theme_type) const { - if (p_theme_type == StringName() || p_theme_type == get_class_name() || p_theme_type == data.theme_type_variation) { - const Ref<Font> *font = data.font_override.getptr(p_name); - if (font && (*font)->get_data_count() > 0) { - return *font; - } - } - - List<StringName> theme_types; - _get_theme_type_dependencies(p_theme_type, &theme_types); - return get_theme_item_in_types<Ref<Font>>(data.theme_owner, data.theme_owner_window, Theme::DATA_TYPE_FONT, p_name, theme_types); -} - -int Control::get_theme_font_size(const StringName &p_name, const StringName &p_theme_type) const { - if (p_theme_type == StringName() || p_theme_type == get_class_name() || p_theme_type == data.theme_type_variation) { - const int *font_size = data.font_size_override.getptr(p_name); - if (font_size && (*font_size) > 0) { - return *font_size; + data.anchor[p_side] = data.anchor[(p_side + 2) % 4]; } } - List<StringName> theme_types; - _get_theme_type_dependencies(p_theme_type, &theme_types); - return get_theme_item_in_types<int>(data.theme_owner, data.theme_owner_window, Theme::DATA_TYPE_FONT_SIZE, p_name, theme_types); -} - -Color Control::get_theme_color(const StringName &p_name, const StringName &p_theme_type) const { - if (p_theme_type == StringName() || p_theme_type == get_class_name() || p_theme_type == data.theme_type_variation) { - const Color *color = data.color_override.getptr(p_name); - if (color) { - return *color; + if (!p_keep_offset) { + data.offset[p_side] = previous_pos - data.anchor[p_side] * parent_range; + if (p_push_opposite_anchor) { + data.offset[(p_side + 2) % 4] = previous_opposite_pos - data.anchor[(p_side + 2) % 4] * parent_range; } } - - List<StringName> theme_types; - _get_theme_type_dependencies(p_theme_type, &theme_types); - return get_theme_item_in_types<Color>(data.theme_owner, data.theme_owner_window, Theme::DATA_TYPE_COLOR, p_name, theme_types); -} - -int Control::get_theme_constant(const StringName &p_name, const StringName &p_theme_type) const { - if (p_theme_type == StringName() || p_theme_type == get_class_name() || p_theme_type == data.theme_type_variation) { - const int *constant = data.constant_override.getptr(p_name); - if (constant) { - return *constant; - } + if (is_inside_tree()) { + _size_changed(); } - List<StringName> theme_types; - _get_theme_type_dependencies(p_theme_type, &theme_types); - return get_theme_item_in_types<int>(data.theme_owner, data.theme_owner_window, Theme::DATA_TYPE_CONSTANT, p_name, theme_types); -} - -bool Control::has_theme_icon_override(const StringName &p_name) const { - const Ref<Texture2D> *tex = data.icon_override.getptr(p_name); - return tex != nullptr; -} - -bool Control::has_theme_stylebox_override(const StringName &p_name) const { - const Ref<StyleBox> *style = data.style_override.getptr(p_name); - return style != nullptr; -} - -bool Control::has_theme_font_override(const StringName &p_name) const { - const Ref<Font> *font = data.font_override.getptr(p_name); - return font != nullptr; -} - -bool Control::has_theme_font_size_override(const StringName &p_name) const { - const int *font_size = data.font_size_override.getptr(p_name); - return font_size != nullptr; -} - -bool Control::has_theme_color_override(const StringName &p_name) const { - const Color *color = data.color_override.getptr(p_name); - return color != nullptr; -} - -bool Control::has_theme_constant_override(const StringName &p_name) const { - const int *constant = data.constant_override.getptr(p_name); - return constant != nullptr; + update(); } -bool Control::has_theme_icon(const StringName &p_name, const StringName &p_theme_type) const { - if (p_theme_type == StringName() || p_theme_type == get_class_name() || p_theme_type == data.theme_type_variation) { - if (has_theme_icon_override(p_name)) { - return true; - } - } +real_t Control::get_anchor(Side p_side) const { + ERR_FAIL_INDEX_V(int(p_side), 4, 0.0); - List<StringName> theme_types; - _get_theme_type_dependencies(p_theme_type, &theme_types); - return has_theme_item_in_types(data.theme_owner, data.theme_owner_window, Theme::DATA_TYPE_ICON, p_name, theme_types); + return data.anchor[p_side]; } -bool Control::has_theme_stylebox(const StringName &p_name, const StringName &p_theme_type) const { - if (p_theme_type == StringName() || p_theme_type == get_class_name() || p_theme_type == data.theme_type_variation) { - if (has_theme_stylebox_override(p_name)) { - return true; - } - } +void Control::set_offset(Side p_side, real_t p_value) { + ERR_FAIL_INDEX((int)p_side, 4); - List<StringName> theme_types; - _get_theme_type_dependencies(p_theme_type, &theme_types); - return has_theme_item_in_types(data.theme_owner, data.theme_owner_window, Theme::DATA_TYPE_STYLEBOX, p_name, theme_types); + data.offset[p_side] = p_value; + _size_changed(); } -bool Control::has_theme_font(const StringName &p_name, const StringName &p_theme_type) const { - if (p_theme_type == StringName() || p_theme_type == get_class_name() || p_theme_type == data.theme_type_variation) { - if (has_theme_font_override(p_name)) { - return true; - } - } +real_t Control::get_offset(Side p_side) const { + ERR_FAIL_INDEX_V((int)p_side, 4, 0); - List<StringName> theme_types; - _get_theme_type_dependencies(p_theme_type, &theme_types); - return has_theme_item_in_types(data.theme_owner, data.theme_owner_window, Theme::DATA_TYPE_FONT, p_name, theme_types); + return data.offset[p_side]; } -bool Control::has_theme_font_size(const StringName &p_name, const StringName &p_theme_type) const { - if (p_theme_type == StringName() || p_theme_type == get_class_name() || p_theme_type == data.theme_type_variation) { - if (has_theme_font_size_override(p_name)) { - return true; - } - } - - List<StringName> theme_types; - _get_theme_type_dependencies(p_theme_type, &theme_types); - return has_theme_item_in_types(data.theme_owner, data.theme_owner_window, Theme::DATA_TYPE_FONT_SIZE, p_name, theme_types); +void Control::set_anchor_and_offset(Side p_side, real_t p_anchor, real_t p_pos, bool p_push_opposite_anchor) { + set_anchor(p_side, p_anchor, false, p_push_opposite_anchor); + set_offset(p_side, p_pos); } -bool Control::has_theme_color(const StringName &p_name, const StringName &p_theme_type) const { - if (p_theme_type == StringName() || p_theme_type == get_class_name() || p_theme_type == data.theme_type_variation) { - if (has_theme_color_override(p_name)) { - return true; - } - } - - List<StringName> theme_types; - _get_theme_type_dependencies(p_theme_type, &theme_types); - return has_theme_item_in_types(data.theme_owner, data.theme_owner_window, Theme::DATA_TYPE_COLOR, p_name, theme_types); +void Control::set_begin(const Size2 &p_point) { + data.offset[0] = p_point.x; + data.offset[1] = p_point.y; + _size_changed(); } -bool Control::has_theme_constant(const StringName &p_name, const StringName &p_theme_type) const { - if (p_theme_type == StringName() || p_theme_type == get_class_name() || p_theme_type == data.theme_type_variation) { - if (has_theme_constant_override(p_name)) { - return true; - } - } - - List<StringName> theme_types; - _get_theme_type_dependencies(p_theme_type, &theme_types); - return has_theme_item_in_types(data.theme_owner, data.theme_owner_window, Theme::DATA_TYPE_CONSTANT, p_name, theme_types); +Size2 Control::get_begin() const { + return Size2(data.offset[0], data.offset[1]); } -float Control::fetch_theme_default_base_scale(Control *p_theme_owner, Window *p_theme_owner_window) { - // First, look through each control or window node in the branch, until no valid parent can be found. - // Only nodes with a theme resource attached are considered. - // For each theme resource see if their assigned theme has the default value defined and valid. - Control *theme_owner = p_theme_owner; - Window *theme_owner_window = p_theme_owner_window; - - while (theme_owner || theme_owner_window) { - if (theme_owner && theme_owner->data.theme->has_default_base_scale()) { - return theme_owner->data.theme->get_default_base_scale(); - } - - if (theme_owner_window && theme_owner_window->theme->has_default_base_scale()) { - return theme_owner_window->theme->get_default_base_scale(); - } - - Node *parent = theme_owner ? theme_owner->get_parent() : theme_owner_window->get_parent(); - Control *parent_c = Object::cast_to<Control>(parent); - if (parent_c) { - theme_owner = parent_c->data.theme_owner; - theme_owner_window = parent_c->data.theme_owner_window; - } else { - Window *parent_w = Object::cast_to<Window>(parent); - if (parent_w) { - theme_owner = parent_w->theme_owner; - theme_owner_window = parent_w->theme_owner_window; - } else { - theme_owner = nullptr; - theme_owner_window = nullptr; - } - } - } - - // Secondly, check the project-defined Theme resource. - if (Theme::get_project_default().is_valid()) { - if (Theme::get_project_default()->has_default_base_scale()) { - return Theme::get_project_default()->get_default_base_scale(); - } - } - - // Lastly, fall back on the default Theme. - if (Theme::get_default()->has_default_base_scale()) { - return Theme::get_default()->get_default_base_scale(); - } - return Theme::get_fallback_base_scale(); +void Control::set_end(const Size2 &p_point) { + data.offset[2] = p_point.x; + data.offset[3] = p_point.y; + _size_changed(); } -float Control::get_theme_default_base_scale() const { - return fetch_theme_default_base_scale(data.theme_owner, data.theme_owner_window); +Size2 Control::get_end() const { + return Size2(data.offset[2], data.offset[3]); } -Ref<Font> Control::fetch_theme_default_font(Control *p_theme_owner, Window *p_theme_owner_window) { - // First, look through each control or window node in the branch, until no valid parent can be found. - // Only nodes with a theme resource attached are considered. - // For each theme resource see if their assigned theme has the default value defined and valid. - Control *theme_owner = p_theme_owner; - Window *theme_owner_window = p_theme_owner_window; - - while (theme_owner || theme_owner_window) { - if (theme_owner && theme_owner->data.theme->has_default_font()) { - return theme_owner->data.theme->get_default_font(); - } - - if (theme_owner_window && theme_owner_window->theme->has_default_font()) { - return theme_owner_window->theme->get_default_font(); - } - - Node *parent = theme_owner ? theme_owner->get_parent() : theme_owner_window->get_parent(); - Control *parent_c = Object::cast_to<Control>(parent); - if (parent_c) { - theme_owner = parent_c->data.theme_owner; - theme_owner_window = parent_c->data.theme_owner_window; - } else { - Window *parent_w = Object::cast_to<Window>(parent); - if (parent_w) { - theme_owner = parent_w->theme_owner; - theme_owner_window = parent_w->theme_owner_window; - } else { - theme_owner = nullptr; - theme_owner_window = nullptr; - } - } - } - - // Secondly, check the project-defined Theme resource. - if (Theme::get_project_default().is_valid()) { - if (Theme::get_project_default()->has_default_font()) { - return Theme::get_project_default()->get_default_font(); - } - } +void Control::set_h_grow_direction(GrowDirection p_direction) { + ERR_FAIL_INDEX((int)p_direction, 3); - // Lastly, fall back on the default Theme. - if (Theme::get_default()->has_default_font()) { - return Theme::get_default()->get_default_font(); - } - return Theme::get_fallback_font(); + data.h_grow = p_direction; + _size_changed(); } -Ref<Font> Control::get_theme_default_font() const { - return fetch_theme_default_font(data.theme_owner, data.theme_owner_window); +Control::GrowDirection Control::get_h_grow_direction() const { + return data.h_grow; } -int Control::fetch_theme_default_font_size(Control *p_theme_owner, Window *p_theme_owner_window) { - // First, look through each control or window node in the branch, until no valid parent can be found. - // Only nodes with a theme resource attached are considered. - // For each theme resource see if their assigned theme has the default value defined and valid. - Control *theme_owner = p_theme_owner; - Window *theme_owner_window = p_theme_owner_window; - - while (theme_owner || theme_owner_window) { - if (theme_owner && theme_owner->data.theme->has_default_font_size()) { - return theme_owner->data.theme->get_default_font_size(); - } - - if (theme_owner_window && theme_owner_window->theme->has_default_font_size()) { - return theme_owner_window->theme->get_default_font_size(); - } - - Node *parent = theme_owner ? theme_owner->get_parent() : theme_owner_window->get_parent(); - Control *parent_c = Object::cast_to<Control>(parent); - if (parent_c) { - theme_owner = parent_c->data.theme_owner; - theme_owner_window = parent_c->data.theme_owner_window; - } else { - Window *parent_w = Object::cast_to<Window>(parent); - if (parent_w) { - theme_owner = parent_w->theme_owner; - theme_owner_window = parent_w->theme_owner_window; - } else { - theme_owner = nullptr; - theme_owner_window = nullptr; - } - } - } - - // Secondly, check the project-defined Theme resource. - if (Theme::get_project_default().is_valid()) { - if (Theme::get_project_default()->has_default_font_size()) { - return Theme::get_project_default()->get_default_font_size(); - } - } +void Control::set_v_grow_direction(GrowDirection p_direction) { + ERR_FAIL_INDEX((int)p_direction, 3); - // Lastly, fall back on the default Theme. - if (Theme::get_default()->has_default_font_size()) { - return Theme::get_default()->get_default_font_size(); - } - return Theme::get_fallback_font_size(); + data.v_grow = p_direction; + _size_changed(); } -int Control::get_theme_default_font_size() const { - return fetch_theme_default_font_size(data.theme_owner, data.theme_owner_window); +Control::GrowDirection Control::get_v_grow_direction() const { + return data.v_grow; } -Rect2 Control::get_parent_anchorable_rect() const { - if (!is_inside_tree()) { - return Rect2(); - } - - Rect2 parent_rect; - if (data.parent_canvas_item) { - parent_rect = data.parent_canvas_item->get_anchorable_rect(); - } else { -#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")); - } else { - parent_rect = get_viewport()->get_visible_rect(); - } +void Control::_compute_anchors(Rect2 p_rect, const real_t p_offsets[4], real_t (&r_anchors)[4]) { + Size2 parent_rect_size = get_parent_anchorable_rect().size; + ERR_FAIL_COND(parent_rect_size.x == 0.0); + ERR_FAIL_COND(parent_rect_size.y == 0.0); -#else - parent_rect = get_viewport()->get_visible_rect(); -#endif + real_t x = p_rect.position.x; + if (is_layout_rtl()) { + x = parent_rect_size.x - x - p_rect.size.x; } - - return parent_rect; -} - -Size2 Control::get_parent_area_size() const { - return get_parent_anchorable_rect().size; + r_anchors[0] = (x - p_offsets[0]) / parent_rect_size.x; + r_anchors[1] = (p_rect.position.y - p_offsets[1]) / parent_rect_size.y; + r_anchors[2] = (x + p_rect.size.x - p_offsets[2]) / parent_rect_size.x; + r_anchors[3] = (p_rect.position.y + p_rect.size.y - p_offsets[3]) / parent_rect_size.y; } -void Control::_size_changed() { - Rect2 parent_rect = get_parent_anchorable_rect(); - - real_t edge_pos[4]; - - for (int i = 0; i < 4; i++) { - real_t area = parent_rect.size[i & 1]; - edge_pos[i] = data.offset[i] + (data.anchor[i] * area); - } - - Point2 new_pos_cache = Point2(edge_pos[0], edge_pos[1]); - Size2 new_size_cache = Point2(edge_pos[2], edge_pos[3]) - new_pos_cache; - - Size2 minimum_size = get_combined_minimum_size(); - - if (minimum_size.width > new_size_cache.width) { - if (data.h_grow == GROW_DIRECTION_BEGIN) { - new_pos_cache.x += new_size_cache.width - minimum_size.width; - } else if (data.h_grow == GROW_DIRECTION_BOTH) { - new_pos_cache.x += 0.5 * (new_size_cache.width - minimum_size.width); - } - - new_size_cache.width = minimum_size.width; - } +void Control::_compute_offsets(Rect2 p_rect, const real_t p_anchors[4], real_t (&r_offsets)[4]) { + Size2 parent_rect_size = get_parent_anchorable_rect().size; + real_t x = p_rect.position.x; if (is_layout_rtl()) { - new_pos_cache.x = parent_rect.size.x - new_pos_cache.x - new_size_cache.x; - } - - if (minimum_size.height > new_size_cache.height) { - if (data.v_grow == GROW_DIRECTION_BEGIN) { - new_pos_cache.y += new_size_cache.height - minimum_size.height; - } else if (data.v_grow == GROW_DIRECTION_BOTH) { - new_pos_cache.y += 0.5 * (new_size_cache.height - minimum_size.height); - } - - new_size_cache.height = minimum_size.height; - } - - bool pos_changed = new_pos_cache != data.pos_cache; - bool size_changed = new_size_cache != data.size_cache; - - data.pos_cache = new_pos_cache; - data.size_cache = new_size_cache; - - if (is_inside_tree()) { - if (size_changed) { - notification(NOTIFICATION_RESIZED); - } - if (pos_changed || size_changed) { - item_rect_changed(size_changed); - _notify_transform(); - } - - if (pos_changed && !size_changed) { - _update_canvas_item_transform(); //move because it won't be updated - } + x = parent_rect_size.x - x - p_rect.size.x; } + r_offsets[0] = x - (p_anchors[0] * parent_rect_size.x); + r_offsets[1] = p_rect.position.y - (p_anchors[1] * parent_rect_size.y); + r_offsets[2] = x + p_rect.size.x - (p_anchors[2] * parent_rect_size.x); + r_offsets[3] = p_rect.position.y + p_rect.size.y - (p_anchors[3] * parent_rect_size.y); } +/// Presets and layout modes. + void Control::_set_layout_mode(LayoutMode p_mode) { bool list_changed = false; @@ -1557,47 +840,6 @@ Control::LayoutMode Control::_get_layout_mode() const { return LayoutMode::LAYOUT_MODE_POSITION; } -void Control::set_anchor(Side p_side, real_t p_anchor, bool p_keep_offset, bool p_push_opposite_anchor) { - ERR_FAIL_INDEX((int)p_side, 4); - - Rect2 parent_rect = get_parent_anchorable_rect(); - real_t parent_range = (p_side == SIDE_LEFT || p_side == SIDE_RIGHT) ? parent_rect.size.x : parent_rect.size.y; - real_t previous_pos = data.offset[p_side] + data.anchor[p_side] * parent_range; - real_t previous_opposite_pos = data.offset[(p_side + 2) % 4] + data.anchor[(p_side + 2) % 4] * parent_range; - - data.anchor[p_side] = p_anchor; - - if (((p_side == SIDE_LEFT || p_side == SIDE_TOP) && data.anchor[p_side] > data.anchor[(p_side + 2) % 4]) || - ((p_side == SIDE_RIGHT || p_side == SIDE_BOTTOM) && data.anchor[p_side] < data.anchor[(p_side + 2) % 4])) { - if (p_push_opposite_anchor) { - data.anchor[(p_side + 2) % 4] = data.anchor[p_side]; - } else { - data.anchor[p_side] = data.anchor[(p_side + 2) % 4]; - } - } - - if (!p_keep_offset) { - data.offset[p_side] = previous_pos - data.anchor[p_side] * parent_range; - if (p_push_opposite_anchor) { - data.offset[(p_side + 2) % 4] = previous_opposite_pos - data.anchor[(p_side + 2) % 4] * parent_range; - } - } - if (is_inside_tree()) { - _size_changed(); - } - - update(); -} - -void Control::_set_anchor(Side p_side, real_t p_anchor) { - set_anchor(p_side, p_anchor); -} - -void Control::set_anchor_and_offset(Side p_side, real_t p_anchor, real_t p_pos, bool p_push_opposite_anchor) { - set_anchor(p_side, p_anchor, false, p_push_opposite_anchor); - set_offset(p_side, p_pos); -} - void Control::_set_anchors_layout_preset(int p_preset) { bool list_changed = false; @@ -1642,7 +884,7 @@ void Control::_set_anchors_layout_preset(int p_preset) { case PRESET_BOTTOM_WIDE: case PRESET_VCENTER_WIDE: case PRESET_HCENTER_WIDE: - case PRESET_WIDE: + case PRESET_FULL_RECT: set_offsets_preset(preset, LayoutPresetMode::PRESET_MODE_MINSIZE); break; } @@ -1718,7 +960,7 @@ int Control::_get_anchors_layout_preset() const { } if (left == ANCHOR_BEGIN && right == ANCHOR_END && top == ANCHOR_BEGIN && bottom == ANCHOR_END) { - return (int)LayoutPreset::PRESET_WIDE; + return (int)LayoutPreset::PRESET_FULL_RECT; } // Does not match any preset, return "Custom". @@ -1737,7 +979,7 @@ void Control::set_anchors_preset(LayoutPreset p_preset, bool p_keep_offsets) { case PRESET_BOTTOM_WIDE: case PRESET_LEFT_WIDE: case PRESET_HCENTER_WIDE: - case PRESET_WIDE: + case PRESET_FULL_RECT: set_anchor(SIDE_LEFT, ANCHOR_BEGIN, p_keep_offsets); break; @@ -1765,7 +1007,7 @@ void Control::set_anchors_preset(LayoutPreset p_preset, bool p_keep_offsets) { case PRESET_RIGHT_WIDE: case PRESET_TOP_WIDE: case PRESET_VCENTER_WIDE: - case PRESET_WIDE: + case PRESET_FULL_RECT: set_anchor(SIDE_TOP, ANCHOR_BEGIN, p_keep_offsets); break; @@ -1807,7 +1049,7 @@ void Control::set_anchors_preset(LayoutPreset p_preset, bool p_keep_offsets) { case PRESET_RIGHT_WIDE: case PRESET_BOTTOM_WIDE: case PRESET_HCENTER_WIDE: - case PRESET_WIDE: + case PRESET_FULL_RECT: set_anchor(SIDE_RIGHT, ANCHOR_END, p_keep_offsets); break; } @@ -1835,7 +1077,7 @@ void Control::set_anchors_preset(LayoutPreset p_preset, bool p_keep_offsets) { case PRESET_RIGHT_WIDE: case PRESET_BOTTOM_WIDE: case PRESET_VCENTER_WIDE: - case PRESET_WIDE: + case PRESET_FULL_RECT: set_anchor(SIDE_BOTTOM, ANCHOR_END, p_keep_offsets); break; } @@ -1870,7 +1112,7 @@ void Control::set_offsets_preset(LayoutPreset p_preset, LayoutPresetMode p_resiz case PRESET_BOTTOM_WIDE: case PRESET_LEFT_WIDE: case PRESET_HCENTER_WIDE: - case PRESET_WIDE: + case PRESET_FULL_RECT: data.offset[0] = x * (0.0 - data.anchor[0]) + p_margin + parent_rect.position.x; break; @@ -1898,7 +1140,7 @@ void Control::set_offsets_preset(LayoutPreset p_preset, LayoutPresetMode p_resiz case PRESET_RIGHT_WIDE: case PRESET_TOP_WIDE: case PRESET_VCENTER_WIDE: - case PRESET_WIDE: + case PRESET_FULL_RECT: data.offset[1] = parent_rect.size.y * (0.0 - data.anchor[1]) + p_margin + parent_rect.position.y; break; @@ -1940,7 +1182,7 @@ void Control::set_offsets_preset(LayoutPreset p_preset, LayoutPresetMode p_resiz case PRESET_RIGHT_WIDE: case PRESET_BOTTOM_WIDE: case PRESET_HCENTER_WIDE: - case PRESET_WIDE: + case PRESET_FULL_RECT: data.offset[2] = x * (1.0 - data.anchor[2]) - p_margin + parent_rect.position.x; break; } @@ -1968,7 +1210,7 @@ void Control::set_offsets_preset(LayoutPreset p_preset, LayoutPresetMode p_resiz case PRESET_RIGHT_WIDE: case PRESET_BOTTOM_WIDE: case PRESET_VCENTER_WIDE: - case PRESET_WIDE: + case PRESET_FULL_RECT: data.offset[3] = parent_rect.size.y * (1.0 - data.anchor[3]) - p_margin + parent_rect.position.y; break; } @@ -2003,7 +1245,7 @@ void Control::set_grow_direction_preset(LayoutPreset p_preset) { case PRESET_BOTTOM_WIDE: case PRESET_VCENTER_WIDE: case PRESET_HCENTER_WIDE: - case PRESET_WIDE: + case PRESET_FULL_RECT: set_h_grow_direction(GrowDirection::GROW_DIRECTION_BOTH); break; } @@ -2031,49 +1273,43 @@ void Control::set_grow_direction_preset(LayoutPreset p_preset) { case PRESET_RIGHT_WIDE: case PRESET_VCENTER_WIDE: case PRESET_HCENTER_WIDE: - case PRESET_WIDE: + case PRESET_FULL_RECT: set_v_grow_direction(GrowDirection::GROW_DIRECTION_BOTH); break; } } -real_t Control::get_anchor(Side p_side) const { - ERR_FAIL_INDEX_V(int(p_side), 4, 0.0); +/// Manual positioning. - return data.anchor[p_side]; +void Control::_set_position(const Size2 &p_point) { + set_position(p_point); } -void Control::set_offset(Side p_side, real_t p_value) { - ERR_FAIL_INDEX((int)p_side, 4); - - data.offset[p_side] = p_value; +void Control::set_position(const Size2 &p_point, bool p_keep_offsets) { + if (p_keep_offsets) { + _compute_anchors(Rect2(p_point, data.size_cache), data.offset, data.anchor); + } else { + _compute_offsets(Rect2(p_point, data.size_cache), data.anchor, data.offset); + } _size_changed(); } -void Control::set_begin(const Size2 &p_point) { - data.offset[0] = p_point.x; - data.offset[1] = p_point.y; - _size_changed(); +Size2 Control::get_position() const { + return data.pos_cache; } -void Control::set_end(const Size2 &p_point) { - data.offset[2] = p_point.x; - data.offset[3] = p_point.y; - _size_changed(); +void Control::_set_global_position(const Point2 &p_point) { + set_global_position(p_point); } -real_t Control::get_offset(Side p_side) const { - ERR_FAIL_INDEX_V((int)p_side, 4, 0); - - return data.offset[p_side]; -} +void Control::set_global_position(const Point2 &p_point, bool p_keep_offsets) { + Transform2D inv; -Size2 Control::get_begin() const { - return Size2(data.offset[0], data.offset[1]); -} + if (data.parent_canvas_item) { + inv = data.parent_canvas_item->get_global_transform().affine_inverse(); + } -Size2 Control::get_end() const { - return Size2(data.offset[2], data.offset[3]); + set_position(inv.xform(p_point), p_keep_offsets); } Point2 Control::get_global_position() const { @@ -2091,72 +1327,6 @@ Point2 Control::get_screen_position() const { return global_pos; } -void Control::_set_global_position(const Point2 &p_point) { - set_global_position(p_point); -} - -void Control::set_global_position(const Point2 &p_point, bool p_keep_offsets) { - Transform2D inv; - - if (data.parent_canvas_item) { - inv = data.parent_canvas_item->get_global_transform().affine_inverse(); - } - - set_position(inv.xform(p_point), p_keep_offsets); -} - -void Control::_compute_anchors(Rect2 p_rect, const real_t p_offsets[4], real_t (&r_anchors)[4]) { - Size2 parent_rect_size = get_parent_anchorable_rect().size; - ERR_FAIL_COND(parent_rect_size.x == 0.0); - ERR_FAIL_COND(parent_rect_size.y == 0.0); - - real_t x = p_rect.position.x; - if (is_layout_rtl()) { - x = parent_rect_size.x - x - p_rect.size.x; - } - r_anchors[0] = (x - p_offsets[0]) / parent_rect_size.x; - r_anchors[1] = (p_rect.position.y - p_offsets[1]) / parent_rect_size.y; - r_anchors[2] = (x + p_rect.size.x - p_offsets[2]) / parent_rect_size.x; - r_anchors[3] = (p_rect.position.y + p_rect.size.y - p_offsets[3]) / parent_rect_size.y; -} - -void Control::_compute_offsets(Rect2 p_rect, const real_t p_anchors[4], real_t (&r_offsets)[4]) { - Size2 parent_rect_size = get_parent_anchorable_rect().size; - - real_t x = p_rect.position.x; - if (is_layout_rtl()) { - x = parent_rect_size.x - x - p_rect.size.x; - } - r_offsets[0] = x - (p_anchors[0] * parent_rect_size.x); - r_offsets[1] = p_rect.position.y - (p_anchors[1] * parent_rect_size.y); - r_offsets[2] = x + p_rect.size.x - (p_anchors[2] * parent_rect_size.x); - r_offsets[3] = p_rect.position.y + p_rect.size.y - (p_anchors[3] * parent_rect_size.y); -} - -void Control::_set_position(const Size2 &p_point) { - set_position(p_point); -} - -void Control::set_position(const Size2 &p_point, bool p_keep_offsets) { - if (p_keep_offsets) { - _compute_anchors(Rect2(p_point, data.size_cache), data.offset, data.anchor); - } else { - _compute_offsets(Rect2(p_point, data.size_cache), data.anchor, data.offset); - } - _size_changed(); -} - -void Control::set_rect(const Rect2 &p_rect) { - for (int i = 0; i < 4; i++) { - data.anchor[i] = ANCHOR_BEGIN; - } - - _compute_offsets(p_rect, data.anchor, data.offset); - if (is_inside_tree()) { - _size_changed(); - } -} - void Control::_set_size(const Size2 &p_size) { #ifdef DEBUG_ENABLED if (data.size_warning && (data.anchor[SIDE_LEFT] != data.anchor[SIDE_RIGHT] || data.anchor[SIDE_TOP] != data.anchor[SIDE_BOTTOM])) { @@ -2184,10 +1354,6 @@ void Control::set_size(const Size2 &p_size, bool p_keep_offsets) { _size_changed(); } -Size2 Control::get_position() const { - return data.pos_cache; -} - Size2 Control::get_size() const { return data.size_cache; } @@ -2196,6 +1362,21 @@ void Control::reset_size() { set_size(Size2()); } +void Control::set_rect(const Rect2 &p_rect) { + for (int i = 0; i < 4; i++) { + data.anchor[i] = ANCHOR_BEGIN; + } + + _compute_offsets(p_rect, data.anchor, data.offset); + if (is_inside_tree()) { + _size_changed(); + } +} + +Rect2 Control::get_rect() const { + return Rect2(get_position(), get_size()); +} + Rect2 Control::get_global_rect() const { return Rect2(get_global_position(), get_size()); } @@ -2220,118 +1401,382 @@ Rect2 Control::get_window_rect() const { return gr; } -Rect2 Control::get_rect() const { - return Rect2(get_position(), get_size()); -} - Rect2 Control::get_anchorable_rect() const { return Rect2(Point2(), get_size()); } -void Control::begin_bulk_theme_override() { - data.bulk_theme_override = true; +void Control::set_scale(const Vector2 &p_scale) { + data.scale = p_scale; + // Avoid having 0 scale values, can lead to errors in physics and rendering. + if (data.scale.x == 0) { + data.scale.x = CMP_EPSILON; + } + if (data.scale.y == 0) { + data.scale.y = CMP_EPSILON; + } + update(); + _notify_transform(); } -void Control::end_bulk_theme_override() { - ERR_FAIL_COND(!data.bulk_theme_override); +Vector2 Control::get_scale() const { + return data.scale; +} - data.bulk_theme_override = false; - _notify_theme_changed(); +void Control::set_rotation(real_t p_radians) { + data.rotation = p_radians; + update(); + _notify_transform(); } -void Control::add_theme_icon_override(const StringName &p_name, const Ref<Texture2D> &p_icon) { - ERR_FAIL_COND(!p_icon.is_valid()); +real_t Control::get_rotation() const { + return data.rotation; +} - if (data.icon_override.has(p_name)) { - data.icon_override[p_name]->disconnect("changed", callable_mp(this, &Control::_override_changed)); +void Control::set_pivot_offset(const Vector2 &p_pivot) { + data.pivot_offset = p_pivot; + update(); + _notify_transform(); +} + +Vector2 Control::get_pivot_offset() const { + return data.pivot_offset; +} + +/// Sizes. + +void Control::_update_minimum_size() { + if (!is_inside_tree()) { + return; } - data.icon_override[p_name] = p_icon; - data.icon_override[p_name]->connect("changed", callable_mp(this, &Control::_override_changed), Vector<Variant>(), CONNECT_REFERENCE_COUNTED); - _notify_theme_changed(); + Size2 minsize = get_combined_minimum_size(); + data.updating_last_minimum_size = false; + + if (minsize != data.last_minimum_size) { + data.last_minimum_size = minsize; + _size_changed(); + emit_signal(SceneStringNames::get_singleton()->minimum_size_changed); + } } -void Control::add_theme_style_override(const StringName &p_name, const Ref<StyleBox> &p_style) { - ERR_FAIL_COND(!p_style.is_valid()); +void Control::update_minimum_size() { + if (!is_inside_tree() || data.block_minimum_size_adjust) { + return; + } - if (data.style_override.has(p_name)) { - data.style_override[p_name]->disconnect("changed", callable_mp(this, &Control::_override_changed)); + Control *invalidate = this; + + //invalidate cache upwards + while (invalidate && invalidate->data.minimum_size_valid) { + invalidate->data.minimum_size_valid = false; + if (invalidate->is_set_as_top_level()) { + break; // do not go further up + } + if (!invalidate->data.parent && get_parent()) { + Window *parent_window = Object::cast_to<Window>(get_parent()); + if (parent_window && parent_window->is_wrapping_controls()) { + parent_window->child_controls_changed(); + } + } + invalidate = invalidate->data.parent; } - data.style_override[p_name] = p_style; - data.style_override[p_name]->connect("changed", callable_mp(this, &Control::_override_changed), Vector<Variant>(), CONNECT_REFERENCE_COUNTED); - _notify_theme_changed(); + if (!is_visible_in_tree()) { + return; + } + + if (data.updating_last_minimum_size) { + return; + } + + data.updating_last_minimum_size = true; + + MessageQueue::get_singleton()->push_call(this, "_update_minimum_size"); } -void Control::add_theme_font_override(const StringName &p_name, const Ref<Font> &p_font) { - ERR_FAIL_COND(!p_font.is_valid()); +void Control::set_block_minimum_size_adjust(bool p_block) { + data.block_minimum_size_adjust = p_block; +} - if (data.font_override.has(p_name)) { - data.font_override[p_name]->disconnect("changed", callable_mp(this, &Control::_override_changed)); +bool Control::is_minimum_size_adjust_blocked() const { + return data.block_minimum_size_adjust; +} + +Size2 Control::get_minimum_size() const { + Vector2 ms; + if (GDVIRTUAL_CALL(_get_minimum_size, ms)) { + return ms; } + return Vector2(); +} - data.font_override[p_name] = p_font; - data.font_override[p_name]->connect("changed", callable_mp(this, &Control::_override_changed), Vector<Variant>(), CONNECT_REFERENCE_COUNTED); - _notify_theme_changed(); +void Control::set_custom_minimum_size(const Size2 &p_custom) { + if (p_custom == data.custom_minimum_size) { + return; + } + data.custom_minimum_size = p_custom; + update_minimum_size(); } -void Control::add_theme_font_size_override(const StringName &p_name, int p_font_size) { - data.font_size_override[p_name] = p_font_size; - _notify_theme_changed(); +Size2 Control::get_custom_minimum_size() const { + return data.custom_minimum_size; } -void Control::add_theme_color_override(const StringName &p_name, const Color &p_color) { - data.color_override[p_name] = p_color; - _notify_theme_changed(); +void Control::_update_minimum_size_cache() { + Size2 minsize = get_minimum_size(); + minsize.x = MAX(minsize.x, data.custom_minimum_size.x); + minsize.y = MAX(minsize.y, data.custom_minimum_size.y); + + bool size_changed = false; + if (data.minimum_size_cache != minsize) { + size_changed = true; + } + + data.minimum_size_cache = minsize; + data.minimum_size_valid = true; + + if (size_changed) { + update_minimum_size(); + } } -void Control::add_theme_constant_override(const StringName &p_name, int p_constant) { - data.constant_override[p_name] = p_constant; - _notify_theme_changed(); +Size2 Control::get_combined_minimum_size() const { + if (!data.minimum_size_valid) { + const_cast<Control *>(this)->_update_minimum_size_cache(); + } + return data.minimum_size_cache; } -void Control::remove_theme_icon_override(const StringName &p_name) { - if (data.icon_override.has(p_name)) { - data.icon_override[p_name]->disconnect("changed", callable_mp(this, &Control::_override_changed)); +void Control::_size_changed() { + Rect2 parent_rect = get_parent_anchorable_rect(); + + real_t edge_pos[4]; + + for (int i = 0; i < 4; i++) { + real_t area = parent_rect.size[i & 1]; + edge_pos[i] = data.offset[i] + (data.anchor[i] * area); } - data.icon_override.erase(p_name); - _notify_theme_changed(); + Point2 new_pos_cache = Point2(edge_pos[0], edge_pos[1]); + Size2 new_size_cache = Point2(edge_pos[2], edge_pos[3]) - new_pos_cache; + + Size2 minimum_size = get_combined_minimum_size(); + + if (minimum_size.width > new_size_cache.width) { + if (data.h_grow == GROW_DIRECTION_BEGIN) { + new_pos_cache.x += new_size_cache.width - minimum_size.width; + } else if (data.h_grow == GROW_DIRECTION_BOTH) { + new_pos_cache.x += 0.5 * (new_size_cache.width - minimum_size.width); + } + + new_size_cache.width = minimum_size.width; + } + + if (is_layout_rtl()) { + new_pos_cache.x = parent_rect.size.x - new_pos_cache.x - new_size_cache.x; + } + + if (minimum_size.height > new_size_cache.height) { + if (data.v_grow == GROW_DIRECTION_BEGIN) { + new_pos_cache.y += new_size_cache.height - minimum_size.height; + } else if (data.v_grow == GROW_DIRECTION_BOTH) { + new_pos_cache.y += 0.5 * (new_size_cache.height - minimum_size.height); + } + + new_size_cache.height = minimum_size.height; + } + + bool pos_changed = new_pos_cache != data.pos_cache; + bool size_changed = new_size_cache != data.size_cache; + + data.pos_cache = new_pos_cache; + data.size_cache = new_size_cache; + + if (is_inside_tree()) { + if (size_changed) { + notification(NOTIFICATION_RESIZED); + } + if (pos_changed || size_changed) { + item_rect_changed(size_changed); + _notify_transform(); + } + + if (pos_changed && !size_changed) { + _update_canvas_item_transform(); //move because it won't be updated + } + } } -void Control::remove_theme_style_override(const StringName &p_name) { - if (data.style_override.has(p_name)) { - data.style_override[p_name]->disconnect("changed", callable_mp(this, &Control::_override_changed)); +void Control::_clear_size_warning() { + data.size_warning = false; +} + +// Container sizing. + +void Control::set_h_size_flags(int p_flags) { + if (data.h_size_flags == p_flags) { + return; } + data.h_size_flags = p_flags; + emit_signal(SceneStringNames::get_singleton()->size_flags_changed); +} - data.style_override.erase(p_name); - _notify_theme_changed(); +int Control::get_h_size_flags() const { + return data.h_size_flags; } -void Control::remove_theme_font_override(const StringName &p_name) { - if (data.font_override.has(p_name)) { - data.font_override[p_name]->disconnect("changed", callable_mp(this, &Control::_override_changed)); +void Control::set_v_size_flags(int p_flags) { + if (data.v_size_flags == p_flags) { + return; } + data.v_size_flags = p_flags; + emit_signal(SceneStringNames::get_singleton()->size_flags_changed); +} - data.font_override.erase(p_name); - _notify_theme_changed(); +int Control::get_v_size_flags() const { + return data.v_size_flags; } -void Control::remove_theme_font_size_override(const StringName &p_name) { - data.font_size_override.erase(p_name); - _notify_theme_changed(); +void Control::set_stretch_ratio(real_t p_ratio) { + if (data.expand == p_ratio) { + return; + } + + data.expand = p_ratio; + emit_signal(SceneStringNames::get_singleton()->size_flags_changed); } -void Control::remove_theme_color_override(const StringName &p_name) { - data.color_override.erase(p_name); - _notify_theme_changed(); +real_t Control::get_stretch_ratio() const { + return data.expand; } -void Control::remove_theme_constant_override(const StringName &p_name) { - data.constant_override.erase(p_name); - _notify_theme_changed(); +// Input events. + +void Control::_call_gui_input(const Ref<InputEvent> &p_event) { + emit_signal(SceneStringNames::get_singleton()->gui_input, p_event); //signal should be first, so it's possible to override an event (and then accept it) + if (!is_inside_tree() || get_viewport()->is_input_handled()) { + return; //input was handled, abort + } + GDVIRTUAL_CALL(_gui_input, p_event); + if (!is_inside_tree() || get_viewport()->is_input_handled()) { + return; //input was handled, abort + } + gui_input(p_event); +} + +void Control::gui_input(const Ref<InputEvent> &p_event) { +} + +void Control::accept_event() { + if (is_inside_tree()) { + get_viewport()->_gui_accept_event(); + } +} + +bool Control::has_point(const Point2 &p_point) const { + bool ret; + if (GDVIRTUAL_CALL(_has_point, p_point, ret)) { + return ret; + } + return Rect2(Point2(), get_size()).has_point(p_point); +} + +void Control::set_mouse_filter(MouseFilter p_filter) { + ERR_FAIL_INDEX(p_filter, 3); + data.mouse_filter = p_filter; + notify_property_list_changed(); + update_configuration_warnings(); } +Control::MouseFilter Control::get_mouse_filter() const { + return data.mouse_filter; +} + +void Control::set_force_pass_scroll_events(bool p_force_pass_scroll_events) { + data.force_pass_scroll_events = p_force_pass_scroll_events; +} + +bool Control::is_force_pass_scroll_events() const { + return data.force_pass_scroll_events; +} + +void Control::warp_mouse(const Point2 &p_position) { + ERR_FAIL_COND(!is_inside_tree()); + get_viewport()->warp_mouse(get_global_transform_with_canvas().xform(p_position)); +} + +// Drag and drop handling. + +void Control::set_drag_forwarding(Object *p_target) { + if (p_target) { + data.drag_owner = p_target->get_instance_id(); + } else { + data.drag_owner = ObjectID(); + } +} + +Variant Control::get_drag_data(const Point2 &p_point) { + if (data.drag_owner.is_valid()) { + Object *obj = ObjectDB::get_instance(data.drag_owner); + if (obj) { + return obj->call("_get_drag_data_fw", p_point, this); + } + } + + Variant dd; + if (GDVIRTUAL_CALL(_get_drag_data, p_point, dd)) { + return dd; + } + + return Variant(); +} + +bool Control::can_drop_data(const Point2 &p_point, const Variant &p_data) const { + if (data.drag_owner.is_valid()) { + Object *obj = ObjectDB::get_instance(data.drag_owner); + if (obj) { + return obj->call("_can_drop_data_fw", p_point, p_data, this); + } + } + + bool ret; + if (GDVIRTUAL_CALL(_can_drop_data, p_point, p_data, ret)) { + return ret; + } + return false; +} + +void Control::drop_data(const Point2 &p_point, const Variant &p_data) { + if (data.drag_owner.is_valid()) { + Object *obj = ObjectDB::get_instance(data.drag_owner); + if (obj) { + obj->call("_drop_data_fw", p_point, p_data, this); + return; + } + } + + GDVIRTUAL_CALL(_drop_data, p_point, p_data); +} + +void Control::force_drag(const Variant &p_data, Control *p_control) { + ERR_FAIL_COND(!is_inside_tree()); + ERR_FAIL_COND(p_data.get_type() == Variant::NIL); + + get_viewport()->_gui_force_drag(this, p_data, p_control); +} + +void Control::set_drag_preview(Control *p_control) { + ERR_FAIL_COND(!is_inside_tree()); + ERR_FAIL_COND(!get_viewport()->gui_is_dragging()); + get_viewport()->_gui_set_drag_preview(this, p_control); +} + +bool Control::is_drag_successful() const { + return is_inside_tree() && get_viewport()->gui_is_drag_successful(); +} + +// Focus. + void Control::set_focus_mode(FocusMode p_focus_mode) { ERR_FAIL_INDEX((int)p_focus_mode, 3); @@ -2342,9 +1787,44 @@ void Control::set_focus_mode(FocusMode p_focus_mode) { data.focus_mode = p_focus_mode; } +Control::FocusMode Control::get_focus_mode() const { + return data.focus_mode; +} + +bool Control::has_focus() const { + return is_inside_tree() && get_viewport()->_gui_control_has_focus(this); +} + +void Control::grab_focus() { + ERR_FAIL_COND(!is_inside_tree()); + + if (data.focus_mode == FOCUS_NONE) { + WARN_PRINT("This control can't grab focus. Use set_focus_mode() to allow a control to get focus."); + return; + } + + get_viewport()->_gui_control_grab_focus(this); +} + +void Control::grab_click_focus() { + ERR_FAIL_COND(!is_inside_tree()); + + get_viewport()->_gui_grab_click_focus(this); +} + +void Control::release_focus() { + ERR_FAIL_COND(!is_inside_tree()); + + if (!has_focus()) { + return; + } + + get_viewport()->gui_release_focus(); +} + static Control *_next_control(Control *p_from) { if (p_from->is_set_as_top_level()) { - return nullptr; // can't go above + return nullptr; // Can't go above. } Control *parent = Object::cast_to<Control>(p_from->get_parent()); @@ -2364,7 +1844,7 @@ static Control *_next_control(Control *p_from) { return c; } - //no next in parent, try the same in parent + // No next in parent, try the same in parent. return _next_control(parent); } @@ -2388,7 +1868,7 @@ Control *Control::find_next_valid_focus() const { } } - // find next child + // Find next child. Control *next_child = nullptr; @@ -2404,7 +1884,7 @@ Control *Control::find_next_valid_focus() const { if (!next_child) { next_child = _next_control(from); - if (!next_child) { //nothing else.. go up and find either window or subwindow + if (!next_child) { // Nothing else. Go up and find either window or subwindow. next_child = const_cast<Control *>(this); while (next_child && !next_child->is_set_as_top_level()) { next_child = cast_to<Control>(next_child->get_parent()); @@ -2422,7 +1902,7 @@ Control *Control::find_next_valid_focus() const { } } - if (next_child == this) { // no next control-> + if (next_child == from || next_child == this) { // No next control. return (get_focus_mode() == FOCUS_ALL) ? next_child : nullptr; } if (next_child) { @@ -2454,7 +1934,7 @@ static Control *_prev_control(Control *p_from) { return p_from; } - //no prev in parent, try the same in parent + // No prev in parent, try the same in parent. return _prev_control(child); } @@ -2478,12 +1958,12 @@ Control *Control::find_prev_valid_focus() const { } } - // find prev child + // Find prev child. Control *prev_child = nullptr; if (from->is_set_as_top_level() || !Object::cast_to<Control>(from->get_parent())) { - //find last of the children + // Find last of the children. prev_child = _prev_control(from); @@ -2506,7 +1986,7 @@ Control *Control::find_prev_valid_focus() const { } } - if (prev_child == this) { // no prev control-> + if (prev_child == from || prev_child == this) { // No prev control. return (get_focus_mode() == FOCUS_ALL) ? prev_child : nullptr; } @@ -2520,181 +2000,6 @@ Control *Control::find_prev_valid_focus() const { return nullptr; } -Control::FocusMode Control::get_focus_mode() const { - return data.focus_mode; -} - -bool Control::has_focus() const { - return is_inside_tree() && get_viewport()->_gui_control_has_focus(this); -} - -void Control::grab_focus() { - ERR_FAIL_COND(!is_inside_tree()); - - if (data.focus_mode == FOCUS_NONE) { - WARN_PRINT("This control can't grab focus. Use set_focus_mode() to allow a control to get focus."); - return; - } - - get_viewport()->_gui_control_grab_focus(this); -} - -void Control::release_focus() { - ERR_FAIL_COND(!is_inside_tree()); - - if (!has_focus()) { - return; - } - - get_viewport()->gui_release_focus(); -} - -bool Control::is_top_level_control() const { - return is_inside_tree() && (!data.parent_canvas_item && !data.RI && is_set_as_top_level()); -} - -void Control::_propagate_theme_changed(Node *p_at, Control *p_owner, Window *p_owner_window, bool p_assign) { - Control *c = Object::cast_to<Control>(p_at); - - if (c && c != p_owner && c->data.theme.is_valid()) { // has a theme, this can't be propagated - return; - } - - Window *w = c == nullptr ? Object::cast_to<Window>(p_at) : nullptr; - - if (w && w != p_owner_window && w->theme.is_valid()) { // has a theme, this can't be propagated - return; - } - - for (int i = 0; i < p_at->get_child_count(); i++) { - CanvasItem *child = Object::cast_to<CanvasItem>(p_at->get_child(i)); - if (child) { - _propagate_theme_changed(child, p_owner, p_owner_window, p_assign); - } else { - Window *window = Object::cast_to<Window>(p_at->get_child(i)); - if (window) { - _propagate_theme_changed(window, p_owner, p_owner_window, p_assign); - } - } - } - - if (c) { - if (p_assign) { - c->data.theme_owner = p_owner; - c->data.theme_owner_window = p_owner_window; - } - c->notification(Control::NOTIFICATION_THEME_CHANGED); - c->emit_signal(SceneStringNames::get_singleton()->theme_changed); - } - - if (w) { - if (p_assign) { - w->theme_owner = p_owner; - w->theme_owner_window = p_owner_window; - } - w->notification(Window::NOTIFICATION_THEME_CHANGED); - w->emit_signal(SceneStringNames::get_singleton()->theme_changed); - } -} - -void Control::_theme_changed() { - _propagate_theme_changed(this, this, nullptr, false); -} - -void Control::_notify_theme_changed() { - if (!data.bulk_theme_override) { - notification(NOTIFICATION_THEME_CHANGED); - } -} - -void Control::set_theme(const Ref<Theme> &p_theme) { - if (data.theme == p_theme) { - return; - } - - if (data.theme.is_valid()) { - data.theme->disconnect("changed", callable_mp(this, &Control::_theme_changed)); - } - - data.theme = p_theme; - if (!p_theme.is_null()) { - data.theme_owner = this; - data.theme_owner_window = nullptr; - _propagate_theme_changed(this, this, nullptr); - } else { - Control *parent_c = Object::cast_to<Control>(get_parent()); - - if (parent_c && (parent_c->data.theme_owner || parent_c->data.theme_owner_window)) { - Control::_propagate_theme_changed(this, parent_c->data.theme_owner, parent_c->data.theme_owner_window); - } else { - Window *parent_w = cast_to<Window>(get_parent()); - if (parent_w && (parent_w->theme_owner || parent_w->theme_owner_window)) { - Control::_propagate_theme_changed(this, parent_w->theme_owner, parent_w->theme_owner_window); - } else { - Control::_propagate_theme_changed(this, nullptr, nullptr); - } - } - } - - if (data.theme.is_valid()) { - data.theme->connect("changed", callable_mp(this, &Control::_theme_changed), varray(), CONNECT_DEFERRED); - } -} - -Ref<Theme> Control::get_theme() const { - return data.theme; -} - -void Control::set_theme_type_variation(const StringName &p_theme_type) { - data.theme_type_variation = p_theme_type; - _propagate_theme_changed(this, data.theme_owner, data.theme_owner_window); -} - -StringName Control::get_theme_type_variation() const { - return data.theme_type_variation; -} - -void Control::set_tooltip(const String &p_tooltip) { - data.tooltip = p_tooltip; - update_configuration_warnings(); -} - -String Control::get_tooltip(const Point2 &p_pos) const { - return data.tooltip; -} - -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; -} - -void Control::set_default_cursor_shape(CursorShape p_shape) { - ERR_FAIL_INDEX(int(p_shape), CURSOR_MAX); - - data.default_cursor = p_shape; -} - -Control::CursorShape Control::get_default_cursor_shape() const { - return data.default_cursor; -} - -Control::CursorShape Control::get_cursor_shape(const Point2 &p_pos) const { - return data.default_cursor; -} - -Transform2D Control::get_transform() const { - Transform2D xform = _get_internal_transform(); - xform[2] += get_position(); - return xform; -} - -String Control::_get_tooltip() const { - return data.tooltip; -} - void Control::set_focus_neighbor(Side p_side, const NodePath &p_neighbor) { ERR_FAIL_INDEX((int)p_side, 4); data.focus_neighbor[p_side] = p_neighbor; @@ -2861,273 +2166,1011 @@ void Control::_window_find_focus_neighbor(const Vector2 &p_dir, Node *p_at, cons } } -void Control::set_h_size_flags(int p_flags) { - if (data.h_size_flags == p_flags) { +// Rendering. + +void Control::set_default_cursor_shape(CursorShape p_shape) { + ERR_FAIL_INDEX(int(p_shape), CURSOR_MAX); + + data.default_cursor = p_shape; +} + +Control::CursorShape Control::get_default_cursor_shape() const { + return data.default_cursor; +} + +Control::CursorShape Control::get_cursor_shape(const Point2 &p_pos) const { + return data.default_cursor; +} + +void Control::set_disable_visibility_clip(bool p_ignore) { + data.disable_visibility_clip = p_ignore; + update(); +} + +bool Control::is_visibility_clip_disabled() const { + return data.disable_visibility_clip; +} + +void Control::set_clip_contents(bool p_clip) { + data.clip_contents = p_clip; + update(); +} + +bool Control::is_clipping_contents() { + return data.clip_contents; +} + +// Theming. + +void Control::_propagate_theme_changed(Node *p_at, Control *p_owner, Window *p_owner_window, bool p_assign) { + Control *c = Object::cast_to<Control>(p_at); + + if (c && c != p_owner && c->data.theme.is_valid()) { // has a theme, this can't be propagated return; } - data.h_size_flags = p_flags; - emit_signal(SceneStringNames::get_singleton()->size_flags_changed); + + Window *w = c == nullptr ? Object::cast_to<Window>(p_at) : nullptr; + + if (w && w != p_owner_window && w->theme.is_valid()) { // has a theme, this can't be propagated + return; + } + + for (int i = 0; i < p_at->get_child_count(); i++) { + CanvasItem *child = Object::cast_to<CanvasItem>(p_at->get_child(i)); + if (child) { + _propagate_theme_changed(child, p_owner, p_owner_window, p_assign); + } else { + Window *window = Object::cast_to<Window>(p_at->get_child(i)); + if (window) { + _propagate_theme_changed(window, p_owner, p_owner_window, p_assign); + } + } + } + + if (c) { + if (p_assign) { + c->data.theme_owner = p_owner; + c->data.theme_owner_window = p_owner_window; + } + c->notification(Control::NOTIFICATION_THEME_CHANGED); + c->emit_signal(SceneStringNames::get_singleton()->theme_changed); + } + + if (w) { + if (p_assign) { + w->theme_owner = p_owner; + w->theme_owner_window = p_owner_window; + } + w->notification(Window::NOTIFICATION_THEME_CHANGED); + w->emit_signal(SceneStringNames::get_singleton()->theme_changed); + } } -int Control::get_h_size_flags() const { - return data.h_size_flags; +void Control::_theme_changed() { + _propagate_theme_changed(this, this, nullptr, false); } -void Control::set_v_size_flags(int p_flags) { - if (data.v_size_flags == p_flags) { - return; +void Control::_theme_property_override_changed() { + notification(NOTIFICATION_THEME_CHANGED); + emit_signal(SceneStringNames::get_singleton()->theme_changed); + update_minimum_size(); // Overrides are likely to affect minimum size. +} + +void Control::_notify_theme_changed() { + if (!data.bulk_theme_override) { + notification(NOTIFICATION_THEME_CHANGED); } - data.v_size_flags = p_flags; - emit_signal(SceneStringNames::get_singleton()->size_flags_changed); } -void Control::set_stretch_ratio(real_t p_ratio) { - if (data.expand == p_ratio) { +void Control::set_theme(const Ref<Theme> &p_theme) { + if (data.theme == p_theme) { return; } - data.expand = p_ratio; - emit_signal(SceneStringNames::get_singleton()->size_flags_changed); + if (data.theme.is_valid()) { + data.theme->disconnect("changed", callable_mp(this, &Control::_theme_changed)); + } + + data.theme = p_theme; + if (!p_theme.is_null()) { + data.theme_owner = this; + data.theme_owner_window = nullptr; + _propagate_theme_changed(this, this, nullptr); + } else { + Control *parent_c = Object::cast_to<Control>(get_parent()); + + if (parent_c && (parent_c->data.theme_owner || parent_c->data.theme_owner_window)) { + Control::_propagate_theme_changed(this, parent_c->data.theme_owner, parent_c->data.theme_owner_window); + } else { + Window *parent_w = cast_to<Window>(get_parent()); + if (parent_w && (parent_w->theme_owner || parent_w->theme_owner_window)) { + Control::_propagate_theme_changed(this, parent_w->theme_owner, parent_w->theme_owner_window); + } else { + Control::_propagate_theme_changed(this, nullptr, nullptr); + } + } + } + + if (data.theme.is_valid()) { + data.theme->connect("changed", callable_mp(this, &Control::_theme_changed), CONNECT_DEFERRED); + } } -real_t Control::get_stretch_ratio() const { - return data.expand; +Ref<Theme> Control::get_theme() const { + return data.theme; } -void Control::grab_click_focus() { - ERR_FAIL_COND(!is_inside_tree()); +void Control::set_theme_type_variation(const StringName &p_theme_type) { + data.theme_type_variation = p_theme_type; + _propagate_theme_changed(this, data.theme_owner, data.theme_owner_window); +} - get_viewport()->_gui_grab_click_focus(this); +StringName Control::get_theme_type_variation() const { + return data.theme_type_variation; } -void Control::update_minimum_size() { - if (!is_inside_tree() || data.block_minimum_size_adjust) { - return; +/// Theme property lookup. + +template <class T> +T Control::get_theme_item_in_types(Control *p_theme_owner, Window *p_theme_owner_window, Theme::DataType p_data_type, const StringName &p_name, List<StringName> p_theme_types) { + ERR_FAIL_COND_V_MSG(p_theme_types.size() == 0, T(), "At least one theme type must be specified."); + + // First, look through each control or window node in the branch, until no valid parent can be found. + // Only nodes with a theme resource attached are considered. + Control *theme_owner = p_theme_owner; + Window *theme_owner_window = p_theme_owner_window; + + while (theme_owner || theme_owner_window) { + // For each theme resource check the theme types provided and see if p_name exists with any of them. + for (const StringName &E : p_theme_types) { + if (theme_owner && theme_owner->data.theme->has_theme_item(p_data_type, p_name, E)) { + return theme_owner->data.theme->get_theme_item(p_data_type, p_name, E); + } + + if (theme_owner_window && theme_owner_window->theme->has_theme_item(p_data_type, p_name, E)) { + return theme_owner_window->theme->get_theme_item(p_data_type, p_name, E); + } + } + + Node *parent = theme_owner ? theme_owner->get_parent() : theme_owner_window->get_parent(); + Control *parent_c = Object::cast_to<Control>(parent); + if (parent_c) { + theme_owner = parent_c->data.theme_owner; + theme_owner_window = parent_c->data.theme_owner_window; + } else { + Window *parent_w = Object::cast_to<Window>(parent); + if (parent_w) { + theme_owner = parent_w->theme_owner; + theme_owner_window = parent_w->theme_owner_window; + } else { + theme_owner = nullptr; + theme_owner_window = nullptr; + } + } } - Control *invalidate = this; + // Secondly, check the project-defined Theme resource. + if (Theme::get_project_default().is_valid()) { + for (const StringName &E : p_theme_types) { + if (Theme::get_project_default()->has_theme_item(p_data_type, p_name, E)) { + return Theme::get_project_default()->get_theme_item(p_data_type, p_name, E); + } + } + } - //invalidate cache upwards - while (invalidate && invalidate->data.minimum_size_valid) { - invalidate->data.minimum_size_valid = false; - if (invalidate->is_set_as_top_level()) { - break; // do not go further up + // Lastly, fall back on the items defined in the default Theme, if they exist. + for (const StringName &E : p_theme_types) { + if (Theme::get_default()->has_theme_item(p_data_type, p_name, E)) { + return Theme::get_default()->get_theme_item(p_data_type, p_name, E); } - if (!invalidate->data.parent && get_parent()) { - Window *parent_window = Object::cast_to<Window>(get_parent()); - if (parent_window && parent_window->is_wrapping_controls()) { - parent_window->child_controls_changed(); + } + // If they don't exist, use any type to return the default/empty value. + return Theme::get_default()->get_theme_item(p_data_type, p_name, p_theme_types[0]); +} + +bool Control::has_theme_item_in_types(Control *p_theme_owner, Window *p_theme_owner_window, Theme::DataType p_data_type, const StringName &p_name, List<StringName> p_theme_types) { + ERR_FAIL_COND_V_MSG(p_theme_types.size() == 0, false, "At least one theme type must be specified."); + + // First, look through each control or window node in the branch, until no valid parent can be found. + // Only nodes with a theme resource attached are considered. + Control *theme_owner = p_theme_owner; + Window *theme_owner_window = p_theme_owner_window; + + while (theme_owner || theme_owner_window) { + // For each theme resource check the theme types provided and see if p_name exists with any of them. + for (const StringName &E : p_theme_types) { + if (theme_owner && theme_owner->data.theme->has_theme_item(p_data_type, p_name, E)) { + return true; + } + + if (theme_owner_window && theme_owner_window->theme->has_theme_item(p_data_type, p_name, E)) { + return true; + } + } + + Node *parent = theme_owner ? theme_owner->get_parent() : theme_owner_window->get_parent(); + Control *parent_c = Object::cast_to<Control>(parent); + if (parent_c) { + theme_owner = parent_c->data.theme_owner; + theme_owner_window = parent_c->data.theme_owner_window; + } else { + Window *parent_w = Object::cast_to<Window>(parent); + if (parent_w) { + theme_owner = parent_w->theme_owner; + theme_owner_window = parent_w->theme_owner_window; + } else { + theme_owner = nullptr; + theme_owner_window = nullptr; } } - invalidate = invalidate->data.parent; } - if (!is_visible_in_tree()) { - return; + // Secondly, check the project-defined Theme resource. + if (Theme::get_project_default().is_valid()) { + for (const StringName &E : p_theme_types) { + if (Theme::get_project_default()->has_theme_item(p_data_type, p_name, E)) { + return true; + } + } } - if (data.updating_last_minimum_size) { - return; + // Lastly, fall back on the items defined in the default Theme, if they exist. + for (const StringName &E : p_theme_types) { + if (Theme::get_default()->has_theme_item(p_data_type, p_name, E)) { + return true; + } } + return false; +} - data.updating_last_minimum_size = true; +void Control::_get_theme_type_dependencies(const StringName &p_theme_type, List<StringName> *p_list) const { + if (p_theme_type == StringName() || p_theme_type == get_class_name() || p_theme_type == data.theme_type_variation) { + if (Theme::get_project_default().is_valid() && Theme::get_project_default()->get_type_variation_base(data.theme_type_variation) != StringName()) { + Theme::get_project_default()->get_type_dependencies(get_class_name(), data.theme_type_variation, p_list); + } else { + Theme::get_default()->get_type_dependencies(get_class_name(), data.theme_type_variation, p_list); + } + } else { + Theme::get_default()->get_type_dependencies(p_theme_type, StringName(), p_list); + } +} - MessageQueue::get_singleton()->push_call(this, "_update_minimum_size"); +Ref<Texture2D> Control::get_theme_icon(const StringName &p_name, const StringName &p_theme_type) const { + if (p_theme_type == StringName() || p_theme_type == get_class_name() || p_theme_type == data.theme_type_variation) { + const Ref<Texture2D> *tex = data.icon_override.getptr(p_name); + if (tex) { + return *tex; + } + } + + List<StringName> theme_types; + _get_theme_type_dependencies(p_theme_type, &theme_types); + return get_theme_item_in_types<Ref<Texture2D>>(data.theme_owner, data.theme_owner_window, Theme::DATA_TYPE_ICON, p_name, theme_types); } -int Control::get_v_size_flags() const { - return data.v_size_flags; +Ref<StyleBox> Control::get_theme_stylebox(const StringName &p_name, const StringName &p_theme_type) const { + if (p_theme_type == StringName() || p_theme_type == get_class_name() || p_theme_type == data.theme_type_variation) { + const Ref<StyleBox> *style = data.style_override.getptr(p_name); + if (style) { + return *style; + } + } + + List<StringName> theme_types; + _get_theme_type_dependencies(p_theme_type, &theme_types); + return get_theme_item_in_types<Ref<StyleBox>>(data.theme_owner, data.theme_owner_window, Theme::DATA_TYPE_STYLEBOX, p_name, theme_types); } -void Control::set_mouse_filter(MouseFilter p_filter) { - ERR_FAIL_INDEX(p_filter, 3); - data.mouse_filter = p_filter; - notify_property_list_changed(); - update_configuration_warnings(); +Ref<Font> Control::get_theme_font(const StringName &p_name, const StringName &p_theme_type) const { + if (p_theme_type == StringName() || p_theme_type == get_class_name() || p_theme_type == data.theme_type_variation) { + const Ref<Font> *font = data.font_override.getptr(p_name); + if (font) { + return *font; + } + } + + List<StringName> theme_types; + _get_theme_type_dependencies(p_theme_type, &theme_types); + return get_theme_item_in_types<Ref<Font>>(data.theme_owner, data.theme_owner_window, Theme::DATA_TYPE_FONT, p_name, theme_types); } -Control::MouseFilter Control::get_mouse_filter() const { - return data.mouse_filter; +int Control::get_theme_font_size(const StringName &p_name, const StringName &p_theme_type) const { + if (p_theme_type == StringName() || p_theme_type == get_class_name() || p_theme_type == data.theme_type_variation) { + const int *font_size = data.font_size_override.getptr(p_name); + if (font_size && (*font_size) > 0) { + return *font_size; + } + } + + List<StringName> theme_types; + _get_theme_type_dependencies(p_theme_type, &theme_types); + return get_theme_item_in_types<int>(data.theme_owner, data.theme_owner_window, Theme::DATA_TYPE_FONT_SIZE, p_name, theme_types); } -void Control::set_force_pass_scroll_events(bool p_force_pass_scroll_events) { - data.force_pass_scroll_events = p_force_pass_scroll_events; +Color Control::get_theme_color(const StringName &p_name, const StringName &p_theme_type) const { + if (p_theme_type == StringName() || p_theme_type == get_class_name() || p_theme_type == data.theme_type_variation) { + const Color *color = data.color_override.getptr(p_name); + if (color) { + return *color; + } + } + + List<StringName> theme_types; + _get_theme_type_dependencies(p_theme_type, &theme_types); + return get_theme_item_in_types<Color>(data.theme_owner, data.theme_owner_window, Theme::DATA_TYPE_COLOR, p_name, theme_types); } -bool Control::is_force_pass_scroll_events() const { - return data.force_pass_scroll_events; +int Control::get_theme_constant(const StringName &p_name, const StringName &p_theme_type) const { + if (p_theme_type == StringName() || p_theme_type == get_class_name() || p_theme_type == data.theme_type_variation) { + const int *constant = data.constant_override.getptr(p_name); + if (constant) { + return *constant; + } + } + + List<StringName> theme_types; + _get_theme_type_dependencies(p_theme_type, &theme_types); + return get_theme_item_in_types<int>(data.theme_owner, data.theme_owner_window, Theme::DATA_TYPE_CONSTANT, p_name, theme_types); } -void Control::warp_mouse(const Point2 &p_position) { - ERR_FAIL_COND(!is_inside_tree()); - get_viewport()->warp_mouse(get_global_transform_with_canvas().xform(p_position)); +bool Control::has_theme_icon(const StringName &p_name, const StringName &p_theme_type) const { + if (p_theme_type == StringName() || p_theme_type == get_class_name() || p_theme_type == data.theme_type_variation) { + if (has_theme_icon_override(p_name)) { + return true; + } + } + + List<StringName> theme_types; + _get_theme_type_dependencies(p_theme_type, &theme_types); + return has_theme_item_in_types(data.theme_owner, data.theme_owner_window, Theme::DATA_TYPE_ICON, p_name, theme_types); } -bool Control::is_text_field() const { - return false; +bool Control::has_theme_stylebox(const StringName &p_name, const StringName &p_theme_type) const { + if (p_theme_type == StringName() || p_theme_type == get_class_name() || p_theme_type == data.theme_type_variation) { + if (has_theme_stylebox_override(p_name)) { + return true; + } + } + + List<StringName> theme_types; + _get_theme_type_dependencies(p_theme_type, &theme_types); + return has_theme_item_in_types(data.theme_owner, data.theme_owner_window, Theme::DATA_TYPE_STYLEBOX, p_name, theme_types); } -Array Control::structured_text_parser(TextServer::StructuredTextParser p_parser_type, const Array &p_args, const String &p_text) const { - if (p_parser_type == TextServer::STRUCTURED_TEXT_CUSTOM) { - Array ret; - if (GDVIRTUAL_CALL(_structured_text_parser, p_args, p_text, ret)) { - return ret; - } else { - return Array(); +bool Control::has_theme_font(const StringName &p_name, const StringName &p_theme_type) const { + if (p_theme_type == StringName() || p_theme_type == get_class_name() || p_theme_type == data.theme_type_variation) { + if (has_theme_font_override(p_name)) { + return true; } - } else { - return TS->parse_structured_text(p_parser_type, p_args, p_text); } + + List<StringName> theme_types; + _get_theme_type_dependencies(p_theme_type, &theme_types); + return has_theme_item_in_types(data.theme_owner, data.theme_owner_window, Theme::DATA_TYPE_FONT, p_name, theme_types); } -void Control::set_rotation(real_t p_radians) { - data.rotation = p_radians; - update(); - _notify_transform(); +bool Control::has_theme_font_size(const StringName &p_name, const StringName &p_theme_type) const { + if (p_theme_type == StringName() || p_theme_type == get_class_name() || p_theme_type == data.theme_type_variation) { + if (has_theme_font_size_override(p_name)) { + return true; + } + } + + List<StringName> theme_types; + _get_theme_type_dependencies(p_theme_type, &theme_types); + return has_theme_item_in_types(data.theme_owner, data.theme_owner_window, Theme::DATA_TYPE_FONT_SIZE, p_name, theme_types); } -real_t Control::get_rotation() const { - return data.rotation; +bool Control::has_theme_color(const StringName &p_name, const StringName &p_theme_type) const { + if (p_theme_type == StringName() || p_theme_type == get_class_name() || p_theme_type == data.theme_type_variation) { + if (has_theme_color_override(p_name)) { + return true; + } + } + + List<StringName> theme_types; + _get_theme_type_dependencies(p_theme_type, &theme_types); + return has_theme_item_in_types(data.theme_owner, data.theme_owner_window, Theme::DATA_TYPE_COLOR, p_name, theme_types); } -void Control::_override_changed() { - notification(NOTIFICATION_THEME_CHANGED); - emit_signal(SceneStringNames::get_singleton()->theme_changed); - update_minimum_size(); // Overrides are likely to affect minimum size. +bool Control::has_theme_constant(const StringName &p_name, const StringName &p_theme_type) const { + if (p_theme_type == StringName() || p_theme_type == get_class_name() || p_theme_type == data.theme_type_variation) { + if (has_theme_constant_override(p_name)) { + return true; + } + } + + List<StringName> theme_types; + _get_theme_type_dependencies(p_theme_type, &theme_types); + return has_theme_item_in_types(data.theme_owner, data.theme_owner_window, Theme::DATA_TYPE_CONSTANT, p_name, theme_types); } -void Control::set_pivot_offset(const Vector2 &p_pivot) { - data.pivot_offset = p_pivot; - update(); - _notify_transform(); +/// Local property overrides. + +void Control::add_theme_icon_override(const StringName &p_name, const Ref<Texture2D> &p_icon) { + ERR_FAIL_COND(!p_icon.is_valid()); + + if (data.icon_override.has(p_name)) { + data.icon_override[p_name]->disconnect("changed", callable_mp(this, &Control::_theme_property_override_changed)); + } + + data.icon_override[p_name] = p_icon; + data.icon_override[p_name]->connect("changed", callable_mp(this, &Control::_theme_property_override_changed), CONNECT_REFERENCE_COUNTED); + _notify_theme_changed(); } -Vector2 Control::get_pivot_offset() const { - return data.pivot_offset; +void Control::add_theme_style_override(const StringName &p_name, const Ref<StyleBox> &p_style) { + ERR_FAIL_COND(!p_style.is_valid()); + + if (data.style_override.has(p_name)) { + data.style_override[p_name]->disconnect("changed", callable_mp(this, &Control::_theme_property_override_changed)); + } + + data.style_override[p_name] = p_style; + data.style_override[p_name]->connect("changed", callable_mp(this, &Control::_theme_property_override_changed), CONNECT_REFERENCE_COUNTED); + _notify_theme_changed(); } -void Control::set_scale(const Vector2 &p_scale) { - data.scale = p_scale; - // Avoid having 0 scale values, can lead to errors in physics and rendering. - if (data.scale.x == 0) { - data.scale.x = CMP_EPSILON; +void Control::add_theme_font_override(const StringName &p_name, const Ref<Font> &p_font) { + ERR_FAIL_COND(!p_font.is_valid()); + + if (data.font_override.has(p_name)) { + data.font_override[p_name]->disconnect("changed", callable_mp(this, &Control::_theme_property_override_changed)); } - if (data.scale.y == 0) { - data.scale.y = CMP_EPSILON; + + data.font_override[p_name] = p_font; + data.font_override[p_name]->connect("changed", callable_mp(this, &Control::_theme_property_override_changed), CONNECT_REFERENCE_COUNTED); + _notify_theme_changed(); +} + +void Control::add_theme_font_size_override(const StringName &p_name, int p_font_size) { + data.font_size_override[p_name] = p_font_size; + _notify_theme_changed(); +} + +void Control::add_theme_color_override(const StringName &p_name, const Color &p_color) { + data.color_override[p_name] = p_color; + _notify_theme_changed(); +} + +void Control::add_theme_constant_override(const StringName &p_name, int p_constant) { + data.constant_override[p_name] = p_constant; + _notify_theme_changed(); +} + +void Control::remove_theme_icon_override(const StringName &p_name) { + if (data.icon_override.has(p_name)) { + data.icon_override[p_name]->disconnect("changed", callable_mp(this, &Control::_theme_property_override_changed)); } - update(); - _notify_transform(); + + data.icon_override.erase(p_name); + _notify_theme_changed(); } -Vector2 Control::get_scale() const { - return data.scale; +void Control::remove_theme_style_override(const StringName &p_name) { + if (data.style_override.has(p_name)) { + data.style_override[p_name]->disconnect("changed", callable_mp(this, &Control::_theme_property_override_changed)); + } + + data.style_override.erase(p_name); + _notify_theme_changed(); } -Control *Control::get_root_parent_control() const { - const CanvasItem *ci = this; - const Control *root = this; +void Control::remove_theme_font_override(const StringName &p_name) { + if (data.font_override.has(p_name)) { + data.font_override[p_name]->disconnect("changed", callable_mp(this, &Control::_theme_property_override_changed)); + } - while (ci) { - const Control *c = Object::cast_to<Control>(ci); - if (c) { - root = c; + data.font_override.erase(p_name); + _notify_theme_changed(); +} - if (c->data.RI || c->is_top_level_control()) { - break; +void Control::remove_theme_font_size_override(const StringName &p_name) { + data.font_size_override.erase(p_name); + _notify_theme_changed(); +} + +void Control::remove_theme_color_override(const StringName &p_name) { + data.color_override.erase(p_name); + _notify_theme_changed(); +} + +void Control::remove_theme_constant_override(const StringName &p_name) { + data.constant_override.erase(p_name); + _notify_theme_changed(); +} + +bool Control::has_theme_icon_override(const StringName &p_name) const { + const Ref<Texture2D> *tex = data.icon_override.getptr(p_name); + return tex != nullptr; +} + +bool Control::has_theme_stylebox_override(const StringName &p_name) const { + const Ref<StyleBox> *style = data.style_override.getptr(p_name); + return style != nullptr; +} + +bool Control::has_theme_font_override(const StringName &p_name) const { + const Ref<Font> *font = data.font_override.getptr(p_name); + return font != nullptr; +} + +bool Control::has_theme_font_size_override(const StringName &p_name) const { + const int *font_size = data.font_size_override.getptr(p_name); + return font_size != nullptr; +} + +bool Control::has_theme_color_override(const StringName &p_name) const { + const Color *color = data.color_override.getptr(p_name); + return color != nullptr; +} + +bool Control::has_theme_constant_override(const StringName &p_name) const { + const int *constant = data.constant_override.getptr(p_name); + return constant != nullptr; +} + +/// Default theme properties. + +float Control::fetch_theme_default_base_scale(Control *p_theme_owner, Window *p_theme_owner_window) { + // First, look through each control or window node in the branch, until no valid parent can be found. + // Only nodes with a theme resource attached are considered. + // For each theme resource see if their assigned theme has the default value defined and valid. + Control *theme_owner = p_theme_owner; + Window *theme_owner_window = p_theme_owner_window; + + while (theme_owner || theme_owner_window) { + if (theme_owner && theme_owner->data.theme->has_default_base_scale()) { + return theme_owner->data.theme->get_default_base_scale(); + } + + if (theme_owner_window && theme_owner_window->theme->has_default_base_scale()) { + return theme_owner_window->theme->get_default_base_scale(); + } + + Node *parent = theme_owner ? theme_owner->get_parent() : theme_owner_window->get_parent(); + Control *parent_c = Object::cast_to<Control>(parent); + if (parent_c) { + theme_owner = parent_c->data.theme_owner; + theme_owner_window = parent_c->data.theme_owner_window; + } else { + Window *parent_w = Object::cast_to<Window>(parent); + if (parent_w) { + theme_owner = parent_w->theme_owner; + theme_owner_window = parent_w->theme_owner_window; + } else { + theme_owner = nullptr; + theme_owner_window = nullptr; } } + } - ci = ci->get_parent_item(); + // Secondly, check the project-defined Theme resource. + if (Theme::get_project_default().is_valid()) { + if (Theme::get_project_default()->has_default_base_scale()) { + return Theme::get_project_default()->get_default_base_scale(); + } } - return const_cast<Control *>(root); + // Lastly, fall back on the default Theme. + if (Theme::get_default()->has_default_base_scale()) { + return Theme::get_default()->get_default_base_scale(); + } + return Theme::get_fallback_base_scale(); } -void Control::set_block_minimum_size_adjust(bool p_block) { - data.block_minimum_size_adjust = p_block; +float Control::get_theme_default_base_scale() const { + return fetch_theme_default_base_scale(data.theme_owner, data.theme_owner_window); } -bool Control::is_minimum_size_adjust_blocked() const { - return data.block_minimum_size_adjust; -} +Ref<Font> Control::fetch_theme_default_font(Control *p_theme_owner, Window *p_theme_owner_window) { + // First, look through each control or window node in the branch, until no valid parent can be found. + // Only nodes with a theme resource attached are considered. + // For each theme resource see if their assigned theme has the default value defined and valid. + Control *theme_owner = p_theme_owner; + Window *theme_owner_window = p_theme_owner_window; -void Control::set_disable_visibility_clip(bool p_ignore) { - data.disable_visibility_clip = p_ignore; - update(); + while (theme_owner || theme_owner_window) { + if (theme_owner && theme_owner->data.theme->has_default_font()) { + return theme_owner->data.theme->get_default_font(); + } + + if (theme_owner_window && theme_owner_window->theme->has_default_font()) { + return theme_owner_window->theme->get_default_font(); + } + + Node *parent = theme_owner ? theme_owner->get_parent() : theme_owner_window->get_parent(); + Control *parent_c = Object::cast_to<Control>(parent); + if (parent_c) { + theme_owner = parent_c->data.theme_owner; + theme_owner_window = parent_c->data.theme_owner_window; + } else { + Window *parent_w = Object::cast_to<Window>(parent); + if (parent_w) { + theme_owner = parent_w->theme_owner; + theme_owner_window = parent_w->theme_owner_window; + } else { + theme_owner = nullptr; + theme_owner_window = nullptr; + } + } + } + + // Secondly, check the project-defined Theme resource. + if (Theme::get_project_default().is_valid()) { + if (Theme::get_project_default()->has_default_font()) { + return Theme::get_project_default()->get_default_font(); + } + } + + // Lastly, fall back on the default Theme. + if (Theme::get_default()->has_default_font()) { + return Theme::get_default()->get_default_font(); + } + return Theme::get_fallback_font(); } -bool Control::is_visibility_clip_disabled() const { - return data.disable_visibility_clip; +Ref<Font> Control::get_theme_default_font() const { + return fetch_theme_default_font(data.theme_owner, data.theme_owner_window); } -void Control::get_argument_options(const StringName &p_function, int p_idx, List<String> *r_options) const { - Node::get_argument_options(p_function, p_idx, r_options); +int Control::fetch_theme_default_font_size(Control *p_theme_owner, Window *p_theme_owner_window) { + // First, look through each control or window node in the branch, until no valid parent can be found. + // Only nodes with a theme resource attached are considered. + // For each theme resource see if their assigned theme has the default value defined and valid. + Control *theme_owner = p_theme_owner; + Window *theme_owner_window = p_theme_owner_window; - if (p_idx == 0) { - List<StringName> sn; - String pf = p_function; - if (pf == "add_theme_color_override" || pf == "has_theme_color" || pf == "has_theme_color_override" || pf == "get_theme_color") { - Theme::get_default()->get_color_list(get_class(), &sn); - } else if (pf == "add_theme_style_override" || pf == "has_theme_style" || pf == "has_theme_style_override" || pf == "get_theme_style") { - Theme::get_default()->get_stylebox_list(get_class(), &sn); - } else if (pf == "add_theme_font_override" || pf == "has_theme_font" || pf == "has_theme_font_override" || pf == "get_theme_font") { - Theme::get_default()->get_font_list(get_class(), &sn); - } else if (pf == "add_theme_font_size_override" || pf == "has_theme_font_size" || pf == "has_theme_font_size_override" || pf == "get_theme_font_size") { - Theme::get_default()->get_font_size_list(get_class(), &sn); - } else if (pf == "add_theme_constant_override" || pf == "has_theme_constant" || pf == "has_theme_constant_override" || pf == "get_theme_constant") { - Theme::get_default()->get_constant_list(get_class(), &sn); + while (theme_owner || theme_owner_window) { + if (theme_owner && theme_owner->data.theme->has_default_font_size()) { + return theme_owner->data.theme->get_default_font_size(); } - sn.sort_custom<StringName::AlphCompare>(); - for (const StringName &name : sn) { - r_options->push_back(String(name).quote()); + if (theme_owner_window && theme_owner_window->theme->has_default_font_size()) { + return theme_owner_window->theme->get_default_font_size(); + } + + Node *parent = theme_owner ? theme_owner->get_parent() : theme_owner_window->get_parent(); + Control *parent_c = Object::cast_to<Control>(parent); + if (parent_c) { + theme_owner = parent_c->data.theme_owner; + theme_owner_window = parent_c->data.theme_owner_window; + } else { + Window *parent_w = Object::cast_to<Window>(parent); + if (parent_w) { + theme_owner = parent_w->theme_owner; + theme_owner_window = parent_w->theme_owner_window; + } else { + theme_owner = nullptr; + theme_owner_window = nullptr; + } } } + + // Secondly, check the project-defined Theme resource. + if (Theme::get_project_default().is_valid()) { + if (Theme::get_project_default()->has_default_font_size()) { + return Theme::get_project_default()->get_default_font_size(); + } + } + + // Lastly, fall back on the default Theme. + if (Theme::get_default()->has_default_font_size()) { + return Theme::get_default()->get_default_font_size(); + } + return Theme::get_fallback_font_size(); } -TypedArray<String> Control::get_configuration_warnings() const { - TypedArray<String> warnings = Node::get_configuration_warnings(); +int Control::get_theme_default_font_size() const { + return fetch_theme_default_font_size(data.theme_owner, data.theme_owner_window); +} - if (data.mouse_filter == MOUSE_FILTER_IGNORE && !data.tooltip.is_empty()) { - warnings.push_back(RTR("The Hint Tooltip won't be displayed as the control's Mouse Filter is set to \"Ignore\". To solve this, set the Mouse Filter to \"Stop\" or \"Pass\".")); +/// Bulk actions. + +void Control::begin_bulk_theme_override() { + data.bulk_theme_override = true; +} + +void Control::end_bulk_theme_override() { + ERR_FAIL_COND(!data.bulk_theme_override); + + data.bulk_theme_override = false; + _notify_theme_changed(); +} + +// Internationalization. + +Array Control::structured_text_parser(TextServer::StructuredTextParser p_parser_type, const Array &p_args, const String &p_text) const { + if (p_parser_type == TextServer::STRUCTURED_TEXT_CUSTOM) { + Array ret; + if (GDVIRTUAL_CALL(_structured_text_parser, p_args, p_text, ret)) { + return ret; + } else { + return Array(); + } + } else { + return TS->parse_structured_text(p_parser_type, p_args, p_text); } +} - return warnings; +void Control::set_layout_direction(Control::LayoutDirection p_direction) { + ERR_FAIL_INDEX((int)p_direction, 4); + + data.layout_dir = p_direction; + data.is_rtl_dirty = true; + + propagate_notification(NOTIFICATION_LAYOUT_DIRECTION_CHANGED); } -void Control::set_clip_contents(bool p_clip) { - data.clip_contents = p_clip; - update(); +Control::LayoutDirection Control::get_layout_direction() const { + return data.layout_dir; } -bool Control::is_clipping_contents() { - return data.clip_contents; +bool Control::is_layout_rtl() const { + if (data.is_rtl_dirty) { + const_cast<Control *>(this)->data.is_rtl_dirty = false; + if (data.layout_dir == LAYOUT_DIRECTION_INHERITED) { + Window *parent_window = get_parent_window(); + Control *parent_control = get_parent_control(); + if (parent_control) { + const_cast<Control *>(this)->data.is_rtl = parent_control->is_layout_rtl(); + } else if (parent_window) { + const_cast<Control *>(this)->data.is_rtl = parent_window->is_layout_rtl(); + } else { + if (GLOBAL_GET(SNAME("internationalization/rendering/force_right_to_left_layout_direction"))) { + const_cast<Control *>(this)->data.is_rtl = true; + } else { + String locale = TranslationServer::get_singleton()->get_tool_locale(); + const_cast<Control *>(this)->data.is_rtl = TS->is_locale_right_to_left(locale); + } + } + } else if (data.layout_dir == LAYOUT_DIRECTION_LOCALE) { + if (GLOBAL_GET(SNAME("internationalization/rendering/force_right_to_left_layout_direction"))) { + const_cast<Control *>(this)->data.is_rtl = true; + } else { + String locale = TranslationServer::get_singleton()->get_tool_locale(); + const_cast<Control *>(this)->data.is_rtl = TS->is_locale_right_to_left(locale); + } + } else { + const_cast<Control *>(this)->data.is_rtl = (data.layout_dir == LAYOUT_DIRECTION_RTL); + } + } + return data.is_rtl; } -void Control::set_h_grow_direction(GrowDirection p_direction) { - ERR_FAIL_INDEX((int)p_direction, 3); +void Control::set_auto_translate(bool p_enable) { + if (p_enable == data.auto_translate) { + return; + } - data.h_grow = p_direction; - _size_changed(); + data.auto_translate = p_enable; + + notification(MainLoop::NOTIFICATION_TRANSLATION_CHANGED); } -Control::GrowDirection Control::get_h_grow_direction() const { - return data.h_grow; +bool Control::is_auto_translating() const { + return data.auto_translate; } -void Control::set_v_grow_direction(GrowDirection p_direction) { - ERR_FAIL_INDEX((int)p_direction, 3); +// Extra properties. - data.v_grow = p_direction; - _size_changed(); +void Control::set_tooltip(const String &p_tooltip) { + data.tooltip = p_tooltip; + update_configuration_warnings(); } -Control::GrowDirection Control::get_v_grow_direction() const { - return data.v_grow; +String Control::_get_tooltip() const { + return data.tooltip; +} + +String Control::get_tooltip(const Point2 &p_pos) const { + return data.tooltip; +} + +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; +} + +// Base object overrides. + +void Control::add_child_notify(Node *p_child) { + Control *child_c = Object::cast_to<Control>(p_child); + + if (child_c && child_c->data.theme.is_null() && (data.theme_owner || data.theme_owner_window)) { + _propagate_theme_changed(child_c, data.theme_owner, data.theme_owner_window); //need to propagate here, since many controls may require setting up stuff + } + + Window *child_w = Object::cast_to<Window>(p_child); + + if (child_w && child_w->theme.is_null() && (data.theme_owner || data.theme_owner_window)) { + _propagate_theme_changed(child_w, data.theme_owner, data.theme_owner_window); //need to propagate here, since many controls may require setting up stuff + } +} + +void Control::remove_child_notify(Node *p_child) { + Control *child_c = Object::cast_to<Control>(p_child); + + if (child_c && (child_c->data.theme_owner || child_c->data.theme_owner_window) && child_c->data.theme.is_null()) { + _propagate_theme_changed(child_c, nullptr, nullptr); + } + + Window *child_w = Object::cast_to<Window>(p_child); + + if (child_w && (child_w->theme_owner || child_w->theme_owner_window) && child_w->theme.is_null()) { + _propagate_theme_changed(child_w, nullptr, nullptr); + } +} + +void Control::_notification(int p_notification) { + switch (p_notification) { + case NOTIFICATION_POST_ENTER_TREE: { + data.minimum_size_valid = false; + data.is_rtl_dirty = true; + _size_changed(); + } break; + + case NOTIFICATION_EXIT_TREE: { + release_focus(); + get_viewport()->_gui_remove_control(this); + } break; + + case NOTIFICATION_READY: { +#ifdef DEBUG_ENABLED + connect("ready", callable_mp(this, &Control::_clear_size_warning), CONNECT_DEFERRED | CONNECT_ONESHOT); +#endif + } break; + + case NOTIFICATION_ENTER_CANVAS: { + data.parent = Object::cast_to<Control>(get_parent()); + data.parent_window = Object::cast_to<Window>(get_parent()); + data.is_rtl_dirty = true; + + if (data.theme.is_null()) { + if (data.parent && (data.parent->data.theme_owner || data.parent->data.theme_owner_window)) { + data.theme_owner = data.parent->data.theme_owner; + data.theme_owner_window = data.parent->data.theme_owner_window; + notification(NOTIFICATION_THEME_CHANGED); + } else if (data.parent_window && (data.parent_window->theme_owner || data.parent_window->theme_owner_window)) { + data.theme_owner = data.parent_window->theme_owner; + data.theme_owner_window = data.parent_window->theme_owner_window; + notification(NOTIFICATION_THEME_CHANGED); + } + } + + CanvasItem *node = this; + bool has_parent_control = false; + + while (!node->is_set_as_top_level()) { + CanvasItem *parent = Object::cast_to<CanvasItem>(node->get_parent()); + if (!parent) { + break; + } + + Control *parent_control = Object::cast_to<Control>(parent); + if (parent_control) { + has_parent_control = true; + break; + } + + node = parent; + } + + if (has_parent_control) { + // Do nothing, has a parent control. + } else { + // Is a regular root control or top_level. + Viewport *viewport = get_viewport(); + ERR_FAIL_COND(!viewport); + data.RI = viewport->_gui_add_root_control(this); + } + + data.parent_canvas_item = get_parent_item(); + + if (data.parent_canvas_item) { + data.parent_canvas_item->connect("item_rect_changed", callable_mp(this, &Control::_size_changed)); + } else { + // Connect viewport. + Viewport *viewport = get_viewport(); + ERR_FAIL_COND(!viewport); + viewport->connect("size_changed", callable_mp(this, &Control::_size_changed)); + } + } break; + + case NOTIFICATION_EXIT_CANVAS: { + if (data.parent_canvas_item) { + data.parent_canvas_item->disconnect("item_rect_changed", callable_mp(this, &Control::_size_changed)); + data.parent_canvas_item = nullptr; + } else if (!is_set_as_top_level()) { + //disconnect viewport + Viewport *viewport = get_viewport(); + ERR_FAIL_COND(!viewport); + viewport->disconnect("size_changed", callable_mp(this, &Control::_size_changed)); + } + + if (data.RI) { + get_viewport()->_gui_remove_root_control(data.RI); + data.RI = nullptr; + } + + data.parent = nullptr; + data.parent_canvas_item = nullptr; + data.parent_window = nullptr; + data.is_rtl_dirty = true; + } break; + + case NOTIFICATION_MOVED_IN_PARENT: { + // some parents need to know the order of the children to draw (like TabContainer) + // update if necessary + if (data.parent) { + data.parent->update(); + } + update(); + + if (data.RI) { + get_viewport()->_gui_set_root_order_dirty(); + } + } break; + + case NOTIFICATION_RESIZED: { + emit_signal(SceneStringNames::get_singleton()->resized); + } break; + + case NOTIFICATION_DRAW: { + _update_canvas_item_transform(); + RenderingServer::get_singleton()->canvas_item_set_custom_rect(get_canvas_item(), !data.disable_visibility_clip, Rect2(Point2(), get_size())); + RenderingServer::get_singleton()->canvas_item_set_clip(get_canvas_item(), data.clip_contents); + } break; + + case NOTIFICATION_MOUSE_ENTER: { + emit_signal(SceneStringNames::get_singleton()->mouse_entered); + } break; + + case NOTIFICATION_MOUSE_EXIT: { + emit_signal(SceneStringNames::get_singleton()->mouse_exited); + } break; + + case NOTIFICATION_FOCUS_ENTER: { + emit_signal(SceneStringNames::get_singleton()->focus_entered); + update(); + } break; + + case NOTIFICATION_FOCUS_EXIT: { + emit_signal(SceneStringNames::get_singleton()->focus_exited); + update(); + } break; + + case NOTIFICATION_THEME_CHANGED: { + update_minimum_size(); + update(); + } break; + + case NOTIFICATION_VISIBILITY_CHANGED: { + if (!is_visible_in_tree()) { + if (get_viewport() != nullptr) { + get_viewport()->_gui_hide_control(this); + } + } else { + data.minimum_size_valid = false; + _update_minimum_size(); + _size_changed(); + } + } break; + + case NOTIFICATION_TRANSLATION_CHANGED: + case NOTIFICATION_LAYOUT_DIRECTION_CHANGED: { + if (is_inside_tree()) { + data.is_rtl_dirty = true; + _size_changed(); + } + } break; + } } void Control::_bind_methods() { - //ClassDB::bind_method(D_METHOD("_window_resize_event"),&Control::_window_resize_event); ClassDB::bind_method(D_METHOD("_update_minimum_size"), &Control::_update_minimum_size); ClassDB::bind_method(D_METHOD("accept_event"), &Control::accept_event); @@ -3300,7 +3343,7 @@ void Control::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::INT, "layout_mode", PROPERTY_HINT_ENUM, "Position,Anchors,Container,Uncontrolled", PROPERTY_USAGE_EDITOR | PROPERTY_USAGE_INTERNAL), "_set_layout_mode", "_get_layout_mode"); ADD_PROPERTY_DEFAULT("layout_mode", LayoutMode::LAYOUT_MODE_POSITION); - const String anchors_presets_options = "Custom:-1,PresetWide:15," + const String anchors_presets_options = "Custom:-1,PresetFullRect:15," "PresetTopLeft:0,PresetTopRight:1,PresetBottomRight:3,PresetBottomLeft:2," "PresetCenterLeft:4,PresetCenterTop:5,PresetCenterRight:6,PresetCenterBottom:7,PresetCenter:8," "PresetLeftWide:9,PresetTopWide:10,PresetRightWide:11,PresetBottomWide:12,PresetVCenterWide:13,PresetHCenterWide:14"; @@ -3408,7 +3451,7 @@ void Control::_bind_methods() { BIND_ENUM_CONSTANT(PRESET_BOTTOM_WIDE); BIND_ENUM_CONSTANT(PRESET_VCENTER_WIDE); BIND_ENUM_CONSTANT(PRESET_HCENTER_WIDE); - BIND_ENUM_CONSTANT(PRESET_WIDE); + BIND_ENUM_CONSTANT(PRESET_FULL_RECT); BIND_ENUM_CONSTANT(PRESET_MODE_MINSIZE); BIND_ENUM_CONSTANT(PRESET_MODE_KEEP_WIDTH); diff --git a/scene/gui/control.h b/scene/gui/control.h index f18dd99bff..9f17eccc3b 100644 --- a/scene/gui/control.h +++ b/scene/gui/control.h @@ -116,7 +116,7 @@ public: PRESET_BOTTOM_WIDE, PRESET_VCENTER_WIDE, PRESET_HCENTER_WIDE, - PRESET_WIDE + PRESET_FULL_RECT }; enum LayoutPresetMode { @@ -159,13 +159,16 @@ private: }; struct Data { - Point2 pos_cache; - Size2 size_cache; - Size2 minimum_size_cache; - bool minimum_size_valid = false; + // Global relations. - Size2 last_minimum_size; - bool updating_last_minimum_size = false; + List<Control *>::Element *RI = nullptr; + + Control *parent = nullptr; + Window *parent_window = nullptr; + CanvasItem *parent_canvas_item = nullptr; + ObjectID drag_owner; + + // Positioning and sizing. real_t offset[4] = { 0.0, 0.0, 0.0, 0.0 }; real_t anchor[4] = { ANCHOR_BEGIN, ANCHOR_BEGIN, ANCHOR_BEGIN, ANCHOR_BEGIN }; @@ -173,49 +176,51 @@ private: GrowDirection h_grow = GROW_DIRECTION_END; GrowDirection v_grow = GROW_DIRECTION_END; - LayoutDirection layout_dir = LAYOUT_DIRECTION_INHERITED; - bool is_rtl_dirty = true; - bool is_rtl = false; - - bool auto_translate = true; - real_t rotation = 0.0; Vector2 scale = Vector2(1, 1); Vector2 pivot_offset; + + Point2 pos_cache; + Size2 size_cache; + Size2 minimum_size_cache; + bool minimum_size_valid = false; + + Size2 last_minimum_size; + bool updating_last_minimum_size = false; + bool block_minimum_size_adjust = false; + bool size_warning = true; + // Container sizing. + int h_size_flags = SIZE_FILL; int v_size_flags = SIZE_FILL; real_t expand = 1.0; Point2 custom_minimum_size; + // Input events and rendering. + MouseFilter mouse_filter = MOUSE_FILTER_STOP; bool force_pass_scroll_events = true; bool clip_contents = false; - - bool block_minimum_size_adjust = false; bool disable_visibility_clip = false; - Control *parent = nullptr; - ObjectID drag_owner; - Ref<Theme> theme; - Control *theme_owner = nullptr; - Window *theme_owner_window = nullptr; - Window *parent_window = nullptr; - StringName theme_type_variation; - - String tooltip; CursorShape default_cursor = CURSOR_ARROW; - List<Control *>::Element *RI = nullptr; - - CanvasItem *parent_canvas_item = nullptr; + // Focus. NodePath focus_neighbor[4]; NodePath focus_next; NodePath focus_prev; + // Theming. + + Ref<Theme> theme; + Control *theme_owner = nullptr; + Window *theme_owner_window = nullptr; + StringName theme_type_variation; + bool bulk_theme_override = false; Theme::ThemeIconMap icon_override; Theme::ThemeStyleMap style_override; @@ -224,50 +229,69 @@ private: Theme::ThemeColorMap color_override; Theme::ThemeConstantMap constant_override; + // Internationalization. + + LayoutDirection layout_dir = LAYOUT_DIRECTION_INHERITED; + bool is_rtl_dirty = true; + bool is_rtl = false; + + bool auto_translate = true; + + // Extra properties. + + String tooltip; + } data; + // Dynamic properties. + static constexpr unsigned properties_managed_by_container_count = 12; static String properties_managed_by_container[properties_managed_by_container_count]; - void _window_find_focus_neighbor(const Vector2 &p_dir, Node *p_at, const Point2 *p_points, real_t p_min, real_t &r_closest_dist, Control **r_closest); - Control *_get_focus_neighbor(Side p_side, int p_count = 0); + // Global relations. + + friend class Viewport; + friend class Window; + + // Positioning and sizing. + + void _update_canvas_item_transform(); + Transform2D _get_internal_transform() const; void _set_anchor(Side p_side, real_t p_anchor); void _set_position(const Point2 &p_point); void _set_global_position(const Point2 &p_point); void _set_size(const Size2 &p_size); + void _compute_offsets(Rect2 p_rect, const real_t p_anchors[4], real_t (&r_offsets)[4]); + void _compute_anchors(Rect2 p_rect, const real_t p_offsets[4], real_t (&r_anchors)[4]); + void _set_layout_mode(LayoutMode p_mode); LayoutMode _get_layout_mode() const; - void _set_anchors_layout_preset(int p_preset); int _get_anchors_layout_preset() const; - void _theme_changed(); - void _notify_theme_changed(); - + void _update_minimum_size_cache(); void _update_minimum_size(); + void _size_changed(); void _clear_size_warning(); - void _compute_offsets(Rect2 p_rect, const real_t p_anchors[4], real_t (&r_offsets)[4]); - void _compute_anchors(Rect2 p_rect, const real_t p_offsets[4], real_t (&r_anchors)[4]); - - void _size_changed(); - String _get_tooltip() const; + // Input events. - void _override_changed(); + void _call_gui_input(const Ref<InputEvent> &p_event); - void _update_canvas_item_transform(); + // Focus. - Transform2D _get_internal_transform() const; + void _window_find_focus_neighbor(const Vector2 &p_dir, Node *p_at, const Point2 *p_points, real_t p_min, real_t &r_closest_dist, Control **r_closest); + Control *_get_focus_neighbor(Side p_side, int p_count = 0); - friend class Viewport; + // Theming. - void _call_gui_input(const Ref<InputEvent> &p_event); + void _theme_changed(); + void _theme_property_override_changed(); + void _notify_theme_changed(); - void _update_minimum_size_cache(); - friend class Window; static void _propagate_theme_changed(Node *p_at, Control *p_owner, Window *p_owner_window, bool p_assign = true); template <class T> @@ -275,24 +299,31 @@ private: static bool has_theme_item_in_types(Control *p_theme_owner, Window *p_theme_owner_window, Theme::DataType p_data_type, const StringName &p_name, List<StringName> p_theme_types); _FORCE_INLINE_ void _get_theme_type_dependencies(const StringName &p_theme_type, List<StringName> *p_list) const; -protected: - virtual void add_child_notify(Node *p_child) override; - virtual void remove_child_notify(Node *p_child) override; + // Extra properties. - //virtual void _window_gui_input(InputEvent p_event); + String _get_tooltip() const; - virtual Array structured_text_parser(TextServer::StructuredTextParser p_parser_type, const Array &p_args, const String &p_text) const; +protected: + // Dynamic properties. bool _set(const StringName &p_name, const Variant &p_value); bool _get(const StringName &p_name, Variant &r_ret) const; void _get_property_list(List<PropertyInfo> *p_list) const; - virtual void _validate_property(PropertyInfo &property) const override; + // Internationalization. + + virtual Array structured_text_parser(TextServer::StructuredTextParser p_parser_type, const Array &p_args, const String &p_text) const; + + // Base object overrides. + + virtual void add_child_notify(Node *p_child) override; + virtual void remove_child_notify(Node *p_child) override; + void _notification(int p_notification); static void _bind_methods(); - //bind helpers + // Exposed virtual methods. GDVIRTUAL1RC(bool, _has_point, Vector2) GDVIRTUAL2RC(Array, _structured_text_parser, Array, String) @@ -307,8 +338,6 @@ protected: public: enum { - /* NOTIFICATION_DRAW=30, - NOTIFICATION_VISIBILITY_CHANGED=38*/ NOTIFICATION_RESIZED = 40, NOTIFICATION_MOUSE_ENTER = 41, NOTIFICATION_MOUSE_EXIT = 42, @@ -318,10 +347,11 @@ public: NOTIFICATION_SCROLL_BEGIN = 47, NOTIFICATION_SCROLL_END = 48, NOTIFICATION_LAYOUT_DIRECTION_CHANGED = 49, - }; - /* EDITOR */ + // Editor plugin interoperability. + + // TODO: Decouple controls from their editor plugin and get rid of this. #ifdef TOOLS_ENABLED virtual Dictionary _edit_get_state() const override; virtual void _edit_set_state(const Dictionary &p_state) override; @@ -347,56 +377,50 @@ public: virtual Size2 _edit_get_minimum_size() const override; #endif - virtual void gui_input(const Ref<InputEvent> &p_event); + // Editor integration. - void accept_event(); + virtual void get_argument_options(const StringName &p_function, int p_idx, List<String> *r_options) const override; + TypedArray<String> get_configuration_warnings() const override; - virtual Size2 get_minimum_size() const; - virtual Size2 get_combined_minimum_size() const; - virtual bool has_point(const Point2 &p_point) const; - virtual void set_drag_forwarding(Object *p_target); - virtual Variant get_drag_data(const Point2 &p_point); - virtual bool can_drop_data(const Point2 &p_point, const Variant &p_data) const; - virtual void drop_data(const Point2 &p_point, const Variant &p_data); - void set_drag_preview(Control *p_control); - void force_drag(const Variant &p_data, Control *p_control); - bool is_drag_successful() const; + virtual bool is_text_field() const; - void set_custom_minimum_size(const Size2 &p_custom); - Size2 get_custom_minimum_size() const; + // Global relations. + + bool is_top_level_control() const; Control *get_parent_control() const; Window *get_parent_window() const; + Control *get_root_parent_control() const; - void set_layout_direction(LayoutDirection p_direction); - LayoutDirection get_layout_direction() const; - virtual bool is_layout_rtl() const; - - void set_auto_translate(bool p_enable); - bool is_auto_translating() const; - _FORCE_INLINE_ String atr(const String p_string) const { return is_auto_translating() ? tr(p_string) : p_string; }; + Size2 get_parent_area_size() const; + Rect2 get_parent_anchorable_rect() const; - /* POSITIONING */ + // Positioning and sizing. - void set_anchors_preset(LayoutPreset p_preset, bool p_keep_offsets = true); - void set_offsets_preset(LayoutPreset p_preset, LayoutPresetMode p_resize_mode = PRESET_MODE_MINSIZE, int p_margin = 0); - void set_anchors_and_offsets_preset(LayoutPreset p_preset, LayoutPresetMode p_resize_mode = PRESET_MODE_MINSIZE, int p_margin = 0); - void set_grow_direction_preset(LayoutPreset p_preset); + virtual Transform2D get_transform() const override; void set_anchor(Side p_side, real_t p_anchor, bool p_keep_offset = true, bool p_push_opposite_anchor = true); real_t get_anchor(Side p_side) const; - void set_offset(Side p_side, real_t p_value); real_t get_offset(Side p_side) const; - void set_anchor_and_offset(Side p_side, real_t p_anchor, real_t p_pos, bool p_push_opposite_anchor = true); - void set_begin(const Point2 &p_point); // helper - void set_end(const Point2 &p_point); // helper - + // TODO: Rename to set_begin/end_offsets ? + void set_begin(const Point2 &p_point); Point2 get_begin() const; + void set_end(const Point2 &p_point); Point2 get_end() const; + void set_h_grow_direction(GrowDirection p_direction); + GrowDirection get_h_grow_direction() const; + void set_v_grow_direction(GrowDirection p_direction); + GrowDirection get_v_grow_direction() const; + + void set_anchors_preset(LayoutPreset p_preset, bool p_keep_offsets = true); + void set_offsets_preset(LayoutPreset p_preset, LayoutPresetMode p_resize_mode = PRESET_MODE_MINSIZE, int p_margin = 0); + void set_anchors_and_offsets_preset(LayoutPreset p_preset, LayoutPresetMode p_resize_mode = PRESET_MODE_MINSIZE, int p_margin = 0); + void set_grow_direction_preset(LayoutPreset p_preset); + void set_position(const Point2 &p_point, bool p_keep_offsets = false); void set_global_position(const Point2 &p_point, bool p_keep_offsets = false); Point2 get_position() const; @@ -407,52 +431,72 @@ public: Size2 get_size() const; void reset_size(); + void set_rect(const Rect2 &p_rect); // Reset anchors to begin and set rect, for faster container children sorting. 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_rect(const Rect2 &p_rect); // Reset anchors to begin and set rect, for faster container children sorting. - + void set_scale(const Vector2 &p_scale); + Vector2 get_scale() const; void set_rotation(real_t p_radians); real_t get_rotation() const; - - void set_h_grow_direction(GrowDirection p_direction); - GrowDirection get_h_grow_direction() const; - - void set_v_grow_direction(GrowDirection p_direction); - GrowDirection get_v_grow_direction() const; - void set_pivot_offset(const Vector2 &p_pivot); Vector2 get_pivot_offset() const; - void set_scale(const Vector2 &p_scale); - Vector2 get_scale() const; + void update_minimum_size(); - void set_theme(const Ref<Theme> &p_theme); - Ref<Theme> get_theme() const; + void set_block_minimum_size_adjust(bool p_block); + bool is_minimum_size_adjust_blocked() const; - void set_theme_type_variation(const StringName &p_theme_type); - StringName get_theme_type_variation() const; + virtual Size2 get_minimum_size() const; + virtual Size2 get_combined_minimum_size() const; + + void set_custom_minimum_size(const Size2 &p_custom); + Size2 get_custom_minimum_size() const; + + // Container sizing. void set_h_size_flags(int p_flags); int get_h_size_flags() const; - void set_v_size_flags(int p_flags); int get_v_size_flags() const; - void set_stretch_ratio(real_t p_ratio); real_t get_stretch_ratio() const; - void update_minimum_size(); + // Input events. + + virtual void gui_input(const Ref<InputEvent> &p_event); + void accept_event(); + + virtual bool has_point(const Point2 &p_point) const; + + void set_mouse_filter(MouseFilter p_filter); + MouseFilter get_mouse_filter() const; + + void set_force_pass_scroll_events(bool p_force_pass_scroll_events); + bool is_force_pass_scroll_events() const; + + void warp_mouse(const Point2 &p_position); + + // Drag and drop handling. + + virtual void set_drag_forwarding(Object *p_target); + virtual Variant get_drag_data(const Point2 &p_point); + virtual bool can_drop_data(const Point2 &p_point, const Variant &p_data) const; + virtual void drop_data(const Point2 &p_point, const Variant &p_data); + void set_drag_preview(Control *p_control); + void force_drag(const Variant &p_data, Control *p_control); + bool is_drag_successful() const; - /* FOCUS */ + // Focus. void set_focus_mode(FocusMode p_focus_mode); FocusMode get_focus_mode() const; bool has_focus() const; void grab_focus(); + void grab_click_focus(); void release_focus(); Control *find_next_valid_focus() const; @@ -466,13 +510,25 @@ public: void set_focus_previous(const NodePath &p_prev); NodePath get_focus_previous() const; - void set_mouse_filter(MouseFilter p_filter); - MouseFilter get_mouse_filter() const; + // Rendering. - void set_force_pass_scroll_events(bool p_force_pass_scroll_events); - bool is_force_pass_scroll_events() const; + void set_default_cursor_shape(CursorShape p_shape); + CursorShape get_default_cursor_shape() const; + virtual CursorShape get_cursor_shape(const Point2 &p_pos = Point2i()) const; + + void set_clip_contents(bool p_clip); + bool is_clipping_contents(); - /* SKINNING */ + void set_disable_visibility_clip(bool p_ignore); + bool is_visibility_clip_disabled() const; + + // Theming. + + void set_theme(const Ref<Theme> &p_theme); + Ref<Theme> get_theme() const; + + void set_theme_type_variation(const StringName &p_theme_type); + StringName get_theme_type_variation() const; void begin_bulk_theme_override(); void end_bulk_theme_override(); @@ -520,44 +576,23 @@ public: Ref<Font> get_theme_default_font() const; int get_theme_default_font_size() const; - /* TOOLTIP */ - - void set_tooltip(const String &p_tooltip); - virtual String get_tooltip(const Point2 &p_pos) const; - virtual Control *make_custom_tooltip(const String &p_text) const; - - /* CURSOR */ - - void set_default_cursor_shape(CursorShape p_shape); - CursorShape get_default_cursor_shape() const; - virtual CursorShape get_cursor_shape(const Point2 &p_pos = Point2i()) const; - - virtual Transform2D get_transform() const override; - - bool is_top_level_control() const; + // Internationalization. - Size2 get_parent_area_size() const; - Rect2 get_parent_anchorable_rect() const; - - void grab_click_focus(); - - void warp_mouse(const Point2 &p_position); - - virtual bool is_text_field() const; - - Control *get_root_parent_control() const; - - void set_clip_contents(bool p_clip); - bool is_clipping_contents(); + void set_layout_direction(LayoutDirection p_direction); + LayoutDirection get_layout_direction() const; + virtual bool is_layout_rtl() const; - void set_block_minimum_size_adjust(bool p_block); - bool is_minimum_size_adjust_blocked() const; + void set_auto_translate(bool p_enable); + bool is_auto_translating() const; + _FORCE_INLINE_ String atr(const String p_string) const { + return is_auto_translating() ? tr(p_string) : p_string; + }; - void set_disable_visibility_clip(bool p_ignore); - bool is_visibility_clip_disabled() const; + // Extra properties. - virtual void get_argument_options(const StringName &p_function, int p_idx, List<String> *r_options) const override; - TypedArray<String> get_configuration_warnings() const override; + void set_tooltip(const String &p_tooltip); + virtual String get_tooltip(const Point2 &p_pos) const; + virtual Control *make_custom_tooltip(const String &p_text) const; Control() {} }; @@ -574,4 +609,4 @@ VARIANT_ENUM_CAST(Control::LayoutMode); VARIANT_ENUM_CAST(Control::LayoutDirection); VARIANT_ENUM_CAST(Control::TextDirection); -#endif +#endif // CONTROL_H diff --git a/scene/gui/dialogs.cpp b/scene/gui/dialogs.cpp index a2b05ee50d..942b7d0bf9 100644 --- a/scene/gui/dialogs.cpp +++ b/scene/gui/dialogs.cpp @@ -161,6 +161,14 @@ bool AcceptDialog::has_autowrap() { return label->get_autowrap_mode() != TextServer::AUTOWRAP_OFF; } +void AcceptDialog::set_ok_button_text(String p_ok_button_text) { + ok->set_text(p_ok_button_text); +} + +String AcceptDialog::get_ok_button_text() const { + return ok->get_text(); +} + void AcceptDialog::register_text_enter(Control *p_line_edit) { ERR_FAIL_NULL(p_line_edit); LineEdit *line_edit = Object::cast_to<LineEdit>(p_line_edit); @@ -253,7 +261,7 @@ Button *AcceptDialog::add_button(const String &p_text, bool p_right, const Strin } if (!p_action.is_empty()) { - button->connect("pressed", callable_mp(this, &AcceptDialog::_custom_action), varray(p_action)); + button->connect("pressed", callable_mp(this, &AcceptDialog::_custom_action).bind(p_action)); } return button; @@ -262,7 +270,7 @@ Button *AcceptDialog::add_button(const String &p_text, bool p_right, const Strin Button *AcceptDialog::add_cancel_button(const String &p_cancel) { String c = p_cancel; if (p_cancel.is_empty()) { - c = RTR("Cancel"); + c = "Cancel"; } Button *b = swap_cancel_ok ? add_button(c, true) : add_button(c); b->connect("pressed", callable_mp(this, &AcceptDialog::_cancel_pressed)); @@ -306,11 +314,15 @@ void AcceptDialog::_bind_methods() { ClassDB::bind_method(D_METHOD("get_text"), &AcceptDialog::get_text); ClassDB::bind_method(D_METHOD("set_autowrap", "autowrap"), &AcceptDialog::set_autowrap); ClassDB::bind_method(D_METHOD("has_autowrap"), &AcceptDialog::has_autowrap); + ClassDB::bind_method(D_METHOD("set_ok_button_text", "text"), &AcceptDialog::set_ok_button_text); + ClassDB::bind_method(D_METHOD("get_ok_button_text"), &AcceptDialog::get_ok_button_text); ADD_SIGNAL(MethodInfo("confirmed")); ADD_SIGNAL(MethodInfo("cancelled")); ADD_SIGNAL(MethodInfo("custom_action", PropertyInfo(Variant::STRING_NAME, "action"))); + ADD_PROPERTY(PropertyInfo(Variant::STRING, "ok_button_text"), "set_ok_button_text", "get_ok_button_text"); + 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"); @@ -349,7 +361,7 @@ AcceptDialog::AcceptDialog() { hbc->add_spacer(); ok = memnew(Button); - ok->set_text(RTR("OK")); + ok->set_text("OK"); hbc->add_child(ok); hbc->add_spacer(); @@ -365,8 +377,20 @@ AcceptDialog::~AcceptDialog() { // ConfirmationDialog +void ConfirmationDialog::set_cancel_button_text(String p_cancel_button_text) { + cancel->set_text(p_cancel_button_text); +} + +String ConfirmationDialog::get_cancel_button_text() const { + return cancel->get_text(); +} + void ConfirmationDialog::_bind_methods() { ClassDB::bind_method(D_METHOD("get_cancel_button"), &ConfirmationDialog::get_cancel_button); + ClassDB::bind_method(D_METHOD("set_cancel_button_text"), &ConfirmationDialog::set_cancel_button_text); + ClassDB::bind_method(D_METHOD("get_cancel_button_text"), &ConfirmationDialog::get_cancel_button_text); + + ADD_PROPERTY(PropertyInfo(Variant::STRING, "cancel_button_text"), "set_cancel_button_text", "get_cancel_button_text"); } Button *ConfirmationDialog::get_cancel_button() { diff --git a/scene/gui/dialogs.h b/scene/gui/dialogs.h index 41fd9c0a10..9ebf5ddfb2 100644 --- a/scene/gui/dialogs.h +++ b/scene/gui/dialogs.h @@ -97,6 +97,9 @@ public: void set_autowrap(bool p_autowrap); bool has_autowrap(); + void set_ok_button_text(String p_ok_button_text); + String get_ok_button_text() const; + AcceptDialog(); ~AcceptDialog(); }; @@ -110,7 +113,11 @@ protected: public: Button *get_cancel_button(); + + void set_cancel_button_text(String p_cancel_button_text); + String get_cancel_button_text() const; + ConfirmationDialog(); }; -#endif +#endif // DIALOGS_H diff --git a/scene/gui/file_dialog.cpp b/scene/gui/file_dialog.cpp index 0e0f8bde83..e26976a402 100644 --- a/scene/gui/file_dialog.cpp +++ b/scene/gui/file_dialog.cpp @@ -170,7 +170,11 @@ Vector<String> FileDialog::get_selected_files() const { }; void FileDialog::update_dir() { - dir->set_text(dir_access->get_current_dir(false)); + if (root_prefix.is_empty()) { + dir->set_text(dir_access->get_current_dir(false)); + } else { + dir->set_text(dir_access->get_current_dir(false).trim_prefix(root_prefix).trim_prefix("/")); + } if (drives->is_visible()) { if (dir_access->get_current_dir().is_network_share_path()) { @@ -188,10 +192,8 @@ void FileDialog::update_dir() { } void FileDialog::_dir_submitted(String p_dir) { - dir_access->change_dir(p_dir); + _change_dir(root_prefix.plus_file(p_dir)); file->set_text(""); - invalidate(); - update_dir(); _push_history(); } @@ -378,9 +380,7 @@ bool FileDialog::_is_open_should_be_disabled() { } void FileDialog::_go_up() { - dir_access->change_dir(".."); - update_file_list(); - update_dir(); + _change_dir(".."); _push_history(); } @@ -390,9 +390,7 @@ void FileDialog::_go_back() { } local_history_pos--; - dir_access->change_dir(local_history[local_history_pos]); - update_file_list(); - update_dir(); + _change_dir(local_history[local_history_pos]); dir_prev->set_disabled(local_history_pos == 0); dir_next->set_disabled(local_history_pos == local_history.size() - 1); @@ -404,9 +402,7 @@ void FileDialog::_go_forward() { } local_history_pos++; - dir_access->change_dir(local_history[local_history_pos]); - update_file_list(); - update_dir(); + _change_dir(local_history[local_history_pos]); dir_prev->set_disabled(local_history_pos == 0); dir_next->set_disabled(local_history_pos == local_history.size() - 1); @@ -423,10 +419,10 @@ void FileDialog::deselect_all() { switch (mode) { case FILE_MODE_OPEN_FILE: case FILE_MODE_OPEN_FILES: - get_ok_button()->set_text(RTR("Open")); + set_ok_button_text(RTR("Open")); break; case FILE_MODE_OPEN_DIR: - get_ok_button()->set_text(RTR("Select Current Folder")); + set_ok_button_text(RTR("Select Current Folder")); break; case FILE_MODE_OPEN_ANY: case FILE_MODE_SAVE_FILE: @@ -450,7 +446,7 @@ void FileDialog::_tree_selected() { if (!d["dir"]) { file->set_text(d["name"]); } else if (mode == FILE_MODE_OPEN_DIR) { - get_ok_button()->set_text(RTR("Select This Folder")); + set_ok_button_text(RTR("Select This Folder")); } get_ok_button()->set_disabled(_is_open_should_be_disabled()); @@ -465,12 +461,10 @@ void FileDialog::_tree_item_activated() { Dictionary d = ti->get_metadata(0); if (d["dir"]) { - dir_access->change_dir(d["name"]); + _change_dir(d["name"]); if (mode == FILE_MODE_OPEN_FILE || mode == FILE_MODE_OPEN_FILES || mode == FILE_MODE_OPEN_DIR || mode == FILE_MODE_OPEN_ANY) { file->set_text(""); } - call_deferred(SNAME("_update_file_list")); - call_deferred(SNAME("_update_dir")); _push_history(); } else { _action_pressed(); @@ -486,7 +480,12 @@ void FileDialog::update_file_name() { String filter_str = filters[idx]; String file_str = file->get_text(); String base_name = file_str.get_basename(); - file_str = base_name + "." + filter_str.strip_edges().to_lower(); + Vector<String> filter_substr = filter_str.split(";"); + if (filter_substr.size() >= 2) { + file_str = base_name + "." + filter_substr[0].strip_edges().get_extension().to_lower(); + } else { + file_str = base_name + "." + filter_str.strip_edges().get_extension().to_lower(); + } file->set_text(file_str); } } @@ -674,9 +673,13 @@ void FileDialog::clear_filters() { invalidate(); } -void FileDialog::add_filter(const String &p_filter) { +void FileDialog::add_filter(const String &p_filter, const String &p_description) { ERR_FAIL_COND_MSG(p_filter.begins_with("."), "Filter must be \"filename.extension\", can't start with dot."); - filters.push_back(p_filter); + if (p_description.is_empty()) { + filters.push_back(p_filter); + } else { + filters.push_back(vformat("%s ; %s", p_filter, p_description)); + } update_filters(); invalidate(); } @@ -704,9 +707,7 @@ String FileDialog::get_current_path() const { } void FileDialog::set_current_dir(const String &p_dir) { - dir_access->change_dir(p_dir); - update_dir(); - invalidate(); + _change_dir(p_dir); _push_history(); } @@ -732,6 +733,27 @@ void FileDialog::set_current_path(const String &p_path) { } } +void FileDialog::set_root_subfolder(const String &p_root) { + root_subfolder = p_root; + ERR_FAIL_COND_MSG(!dir_access->dir_exists(p_root), "root_subfolder must be an existing sub-directory."); + + local_history.clear(); + local_history_pos = -1; + + dir_access->change_dir(root_subfolder); + if (root_subfolder.is_empty()) { + root_prefix = ""; + } else { + root_prefix = dir_access->get_current_dir(); + } + invalidate(); + update_dir(); +} + +String FileDialog::get_root_subfolder() const { + return root_subfolder; +} + void FileDialog::set_mode_overrides_title(bool p_override) { mode_overrides_title = p_override; } @@ -746,35 +768,35 @@ void FileDialog::set_file_mode(FileMode p_mode) { mode = p_mode; switch (mode) { case FILE_MODE_OPEN_FILE: - get_ok_button()->set_text(RTR("Open")); + set_ok_button_text(RTR("Open")); if (mode_overrides_title) { set_title(TTRC("Open a File")); } makedir->hide(); break; case FILE_MODE_OPEN_FILES: - get_ok_button()->set_text(RTR("Open")); + set_ok_button_text(RTR("Open")); if (mode_overrides_title) { set_title(TTRC("Open File(s)")); } makedir->hide(); break; case FILE_MODE_OPEN_DIR: - get_ok_button()->set_text(RTR("Select Current Folder")); + set_ok_button_text(RTR("Select Current Folder")); if (mode_overrides_title) { set_title(TTRC("Open a Directory")); } makedir->show(); break; case FILE_MODE_OPEN_ANY: - get_ok_button()->set_text(RTR("Open")); + set_ok_button_text(RTR("Open")); if (mode_overrides_title) { set_title(TTRC("Open a File or Directory")); } makedir->show(); break; case FILE_MODE_SAVE_FILE: - get_ok_button()->set_text(RTR("Save")); + set_ok_button_text(RTR("Save")); if (mode_overrides_title) { set_title(TTRC("Save a File")); } @@ -810,6 +832,8 @@ void FileDialog::set_access(Access p_access) { } break; } access = p_access; + root_prefix = ""; + root_subfolder = ""; _update_drives(); invalidate(); update_filters(); @@ -832,10 +856,8 @@ FileDialog::Access FileDialog::get_access() const { void FileDialog::_make_dir_confirm() { Error err = dir_access->make_dir(makedirname->get_text().strip_edges()); if (err == OK) { - dir_access->change_dir(makedirname->get_text().strip_edges()); - invalidate(); + _change_dir(makedirname->get_text().strip_edges()); update_filters(); - update_dir(); _push_history(); } else { mkdirerr->popup_centered(Size2(250, 50)); @@ -850,11 +872,25 @@ void FileDialog::_make_dir() { void FileDialog::_select_drive(int p_idx) { String d = drives->get_item_text(p_idx); - dir_access->change_dir(d); + _change_dir(d); file->set_text(""); + _push_history(); +} + +void FileDialog::_change_dir(const String &p_new_dir) { + if (root_prefix.is_empty()) { + dir_access->change_dir(p_new_dir); + } else { + String old_dir = dir_access->get_current_dir(); + dir_access->change_dir(p_new_dir); + if (!dir_access->get_current_dir(false).begins_with(root_prefix)) { + dir_access->change_dir(old_dir); + return; + } + } + invalidate(); update_dir(); - _push_history(); } void FileDialog::_update_drives(bool p_select) { @@ -887,7 +923,7 @@ void FileDialog::_bind_methods() { ClassDB::bind_method(D_METHOD("_cancel_pressed"), &FileDialog::_cancel_pressed); ClassDB::bind_method(D_METHOD("clear_filters"), &FileDialog::clear_filters); - ClassDB::bind_method(D_METHOD("add_filter", "filter"), &FileDialog::add_filter); + ClassDB::bind_method(D_METHOD("add_filter", "filter", "description"), &FileDialog::add_filter, DEFVAL("")); ClassDB::bind_method(D_METHOD("set_filters", "filters"), &FileDialog::set_filters); ClassDB::bind_method(D_METHOD("get_filters"), &FileDialog::get_filters); ClassDB::bind_method(D_METHOD("get_current_dir"), &FileDialog::get_current_dir); @@ -904,6 +940,8 @@ void FileDialog::_bind_methods() { ClassDB::bind_method(D_METHOD("get_line_edit"), &FileDialog::get_line_edit); ClassDB::bind_method(D_METHOD("set_access", "access"), &FileDialog::set_access); ClassDB::bind_method(D_METHOD("get_access"), &FileDialog::get_access); + ClassDB::bind_method(D_METHOD("set_root_subfolder", "dir"), &FileDialog::set_root_subfolder); + ClassDB::bind_method(D_METHOD("get_root_subfolder"), &FileDialog::get_root_subfolder); ClassDB::bind_method(D_METHOD("set_show_hidden_files", "show"), &FileDialog::set_show_hidden_files); ClassDB::bind_method(D_METHOD("is_showing_hidden_files"), &FileDialog::is_showing_hidden_files); ClassDB::bind_method(D_METHOD("_update_file_name"), &FileDialog::update_file_name); @@ -916,6 +954,7 @@ void FileDialog::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::BOOL, "mode_overrides_title"), "set_mode_overrides_title", "is_mode_overriding_title"); ADD_PROPERTY(PropertyInfo(Variant::INT, "file_mode", PROPERTY_HINT_ENUM, "Open File,Open Files,Open Folder,Open Any,Save"), "set_file_mode", "get_file_mode"); ADD_PROPERTY(PropertyInfo(Variant::INT, "access", PROPERTY_HINT_ENUM, "Resources,User Data,File System"), "set_access", "get_access"); + ADD_PROPERTY(PropertyInfo(Variant::STRING, "root_subfolder"), "set_root_subfolder", "get_root_subfolder"); ADD_PROPERTY(PropertyInfo(Variant::PACKED_STRING_ARRAY, "filters"), "set_filters", "get_filters"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "show_hidden_files"), "set_show_hidden_files", "is_showing_hidden_files"); ADD_PROPERTY(PropertyInfo(Variant::STRING, "current_dir", PROPERTY_HINT_DIR, "", PROPERTY_USAGE_NONE), "set_current_dir", "get_current_dir"); @@ -1021,7 +1060,7 @@ FileDialog::FileDialog() { message = memnew(Label); message->hide(); - message->set_anchors_and_offsets_preset(Control::PRESET_WIDE); + message->set_anchors_and_offsets_preset(Control::PRESET_FULL_RECT); message->set_horizontal_alignment(HORIZONTAL_ALIGNMENT_CENTER); message->set_vertical_alignment(VERTICAL_ALIGNMENT_CENTER); tree->add_child(message); @@ -1044,9 +1083,9 @@ FileDialog::FileDialog() { _update_drives(); connect("confirmed", callable_mp(this, &FileDialog::_action_pressed)); - tree->connect("multi_selected", callable_mp(this, &FileDialog::_tree_multi_selected), varray(), CONNECT_DEFERRED); - tree->connect("cell_selected", callable_mp(this, &FileDialog::_tree_selected), varray(), CONNECT_DEFERRED); - tree->connect("item_activated", callable_mp(this, &FileDialog::_tree_item_activated), varray()); + tree->connect("multi_selected", callable_mp(this, &FileDialog::_tree_multi_selected), CONNECT_DEFERRED); + tree->connect("cell_selected", callable_mp(this, &FileDialog::_tree_selected), CONNECT_DEFERRED); + tree->connect("item_activated", callable_mp(this, &FileDialog::_tree_item_activated)); tree->connect("nothing_selected", callable_mp(this, &FileDialog::deselect_all)); dir->connect("text_submitted", callable_mp(this, &FileDialog::_dir_submitted)); file->connect("text_submitted", callable_mp(this, &FileDialog::_file_submitted)); diff --git a/scene/gui/file_dialog.h b/scene/gui/file_dialog.h index 2e326d2949..4945094086 100644 --- a/scene/gui/file_dialog.h +++ b/scene/gui/file_dialog.h @@ -101,6 +101,8 @@ private: void _push_history(); bool mode_overrides_title = true; + String root_subfolder; + String root_prefix; static bool default_show_hidden_files; bool show_hidden_files = false; @@ -131,6 +133,7 @@ private: void _go_back(); void _go_forward(); + void _change_dir(const String &p_new_dir); void _update_drives(bool p_select = true); virtual void shortcut_input(const Ref<InputEvent> &p_event) override; @@ -148,7 +151,7 @@ protected: public: void popup_file_dialog(); void clear_filters(); - void add_filter(const String &p_filter); + void add_filter(const String &p_filter, const String &p_description = ""); void set_filters(const Vector<String> &p_filters); Vector<String> get_filters() const; @@ -162,6 +165,9 @@ public: void set_current_file(const String &p_file); void set_current_path(const String &p_path); + void set_root_subfolder(const String &p_root); + String get_root_subfolder() const; + void set_mode_overrides_title(bool p_override); bool is_mode_overriding_title() const; @@ -190,4 +196,4 @@ public: VARIANT_ENUM_CAST(FileDialog::FileMode); VARIANT_ENUM_CAST(FileDialog::Access); -#endif +#endif // FILE_DIALOG_H diff --git a/scene/gui/gradient_edit.cpp b/scene/gui/gradient_edit.cpp index 0690acbe16..cc27a6b7c2 100644 --- a/scene/gui/gradient_edit.cpp +++ b/scene/gui/gradient_edit.cpp @@ -382,7 +382,7 @@ void GradientEdit::_color_changed(const Color &p_color) { emit_signal(SNAME("ramp_changed")); } -void GradientEdit::set_ramp(const Vector<real_t> &p_offsets, const Vector<Color> &p_colors) { +void GradientEdit::set_ramp(const Vector<float> &p_offsets, const Vector<Color> &p_colors) { ERR_FAIL_COND(p_offsets.size() != p_colors.size()); points.clear(); for (int i = 0; i < p_offsets.size(); i++) { @@ -396,8 +396,8 @@ void GradientEdit::set_ramp(const Vector<real_t> &p_offsets, const Vector<Color> update(); } -Vector<real_t> GradientEdit::get_offsets() const { - Vector<real_t> ret; +Vector<float> GradientEdit::get_offsets() const { + Vector<float> ret; for (int i = 0; i < points.size(); i++) { ret.push_back(points[i].offset); } @@ -437,6 +437,10 @@ ColorPicker *GradientEdit::get_picker() { return picker; } +PopupPanel *GradientEdit::get_popup() { + return popup; +} + void GradientEdit::_bind_methods() { ADD_SIGNAL(MethodInfo("ramp_changed")); } diff --git a/scene/gui/gradient_edit.h b/scene/gui/gradient_edit.h index 4e3c6525f9..b7c99f1f1c 100644 --- a/scene/gui/gradient_edit.h +++ b/scene/gui/gradient_edit.h @@ -67,14 +67,15 @@ protected: static void _bind_methods(); public: - void set_ramp(const Vector<real_t> &p_offsets, const Vector<Color> &p_colors); - Vector<real_t> get_offsets() const; + void set_ramp(const Vector<float> &p_offsets, const Vector<Color> &p_colors); + Vector<float> get_offsets() const; Vector<Color> get_colors() const; void set_points(Vector<Gradient::Point> &p_points); Vector<Gradient::Point> &get_points(); void set_interpolation_mode(Gradient::InterpolationMode p_interp_mode); Gradient::InterpolationMode get_interpolation_mode(); ColorPicker *get_picker(); + PopupPanel *get_popup(); virtual Size2 get_minimum_size() const override; diff --git a/scene/gui/graph_edit.cpp b/scene/gui/graph_edit.cpp index c219eafbf7..42f434f0ac 100644 --- a/scene/gui/graph_edit.cpp +++ b/scene/gui/graph_edit.cpp @@ -382,9 +382,9 @@ void GraphEdit::add_child_notify(Node *p_child) { GraphNode *gn = Object::cast_to<GraphNode>(p_child); if (gn) { gn->set_scale(Vector2(zoom, zoom)); - gn->connect("position_offset_changed", callable_mp(this, &GraphEdit::_graph_node_moved), varray(gn)); - gn->connect("slot_updated", callable_mp(this, &GraphEdit::_graph_node_slot_updated), varray(gn)); - gn->connect("raise_request", callable_mp(this, &GraphEdit::_graph_node_raised), varray(gn)); + gn->connect("position_offset_changed", callable_mp(this, &GraphEdit::_graph_node_moved).bind(gn)); + gn->connect("slot_updated", callable_mp(this, &GraphEdit::_graph_node_slot_updated).bind(gn)); + gn->connect("raise_request", callable_mp(this, &GraphEdit::_graph_node_raised).bind(gn)); gn->connect("item_rect_changed", callable_mp((CanvasItem *)connections_layer, &CanvasItem::update)); gn->connect("item_rect_changed", callable_mp((CanvasItem *)minimap, &GraphEditMinimap::update)); _graph_node_moved(gn); @@ -710,6 +710,9 @@ void GraphEdit::_top_layer_input(const Ref<InputEvent> &p_ev) { int type = gn->get_connection_output_type(j); if ((type == connecting_type || valid_connection_types.has(ConnType(connecting_type, type))) && is_in_output_hotzone(gn, j, mpos, port_size)) { + if (!is_node_hover_valid(gn->get_name(), j, connecting_from, connecting_index)) { + continue; + } connecting_target = true; connecting_to = pos; connecting_target_to = gn->get_name(); @@ -725,6 +728,9 @@ void GraphEdit::_top_layer_input(const Ref<InputEvent> &p_ev) { int type = gn->get_connection_input_type(j); if ((type == connecting_type || valid_connection_types.has(ConnType(connecting_type, type))) && is_in_input_hotzone(gn, j, mpos, port_size)) { + if (!is_node_hover_valid(connecting_from, connecting_index, gn->get_name(), j)) { + continue; + } connecting_target = true; connecting_to = pos; connecting_target_to = gn->get_name(); @@ -1453,6 +1459,14 @@ void GraphEdit::force_connection_drag_end() { emit_signal(SNAME("connection_drag_ended")); } +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; +} + void GraphEdit::set_panning_scheme(PanningScheme p_scheme) { panning_scheme = p_scheme; panner->set_control_scheme((ViewPanner::ControlScheme)p_scheme); @@ -2315,6 +2329,7 @@ void GraphEdit::_bind_methods() { ClassDB::bind_method(D_METHOD("set_selected", "node"), &GraphEdit::set_selected); GDVIRTUAL_BIND(_get_connection_line, "from", "to") + GDVIRTUAL_BIND(_is_node_hover_valid, "from", "from_slot", "to", "to_slot"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "right_disconnects"), "set_right_disconnects", "is_right_disconnects_enabled"); ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "scroll_offset", PROPERTY_HINT_NONE, "suffix:px"), "set_scroll_ofs", "get_scroll_ofs"); @@ -2376,7 +2391,7 @@ GraphEdit::GraphEdit() { top_layer = memnew(GraphEditFilter(this)); add_child(top_layer, false, INTERNAL_MODE_BACK); top_layer->set_mouse_filter(MOUSE_FILTER_PASS); - top_layer->set_anchors_and_offsets_preset(Control::PRESET_WIDE); + top_layer->set_anchors_and_offsets_preset(Control::PRESET_FULL_RECT); top_layer->connect("draw", callable_mp(this, &GraphEdit::_top_layer_draw)); top_layer->connect("gui_input", callable_mp(this, &GraphEdit::_top_layer_input)); top_layer->connect("focus_exited", callable_mp(panner.ptr(), &ViewPanner::release_pan_key)); diff --git a/scene/gui/graph_edit.h b/scene/gui/graph_edit.h index 02e90e4717..cf35aeb8b2 100644 --- a/scene/gui/graph_edit.h +++ b/scene/gui/graph_edit.h @@ -280,6 +280,7 @@ protected: GDVIRTUAL2RC(Vector<Vector2>, _get_connection_line, Vector2, Vector2) GDVIRTUAL3R(bool, _is_in_input_hotzone, Object *, int, Vector2) GDVIRTUAL3R(bool, _is_in_output_hotzone, Object *, int, Vector2) + GDVIRTUAL4R(bool, _is_node_hover_valid, StringName, int, StringName, int); public: Error connect_node(const StringName &p_from, int p_from_port, const StringName &p_to, int p_to_port); @@ -287,6 +288,7 @@ public: void disconnect_node(const StringName &p_from, int p_from_port, const StringName &p_to, int p_to_port); void clear_connections(); void force_connection_drag_end(); + virtual bool is_node_hover_valid(const StringName &p_from, int p_from_port, const StringName &p_to, int p_to_port); void set_connection_activity(const StringName &p_from, int p_from_port, const StringName &p_to, int p_to_port, float p_activity); @@ -365,4 +367,4 @@ public: VARIANT_ENUM_CAST(GraphEdit::PanningScheme); -#endif // GRAPHEdit_H +#endif // GRAPH_EDIT_H diff --git a/scene/gui/graph_node.cpp b/scene/gui/graph_node.cpp index 87706cd0d7..92016ca42e 100644 --- a/scene/gui/graph_node.cpp +++ b/scene/gui/graph_node.cpp @@ -44,26 +44,6 @@ struct _MinSizeCache { bool GraphNode::_set(const StringName &p_name, const Variant &p_value) { String str = p_name; - if (str.begins_with("opentype_features/")) { - String name = str.get_slicec('/', 1); - int32_t tag = TS->name_to_tag(name); - int value = p_value; - if (value == -1) { - if (opentype_features.has(tag)) { - opentype_features.erase(tag); - _shape(); - update(); - } - } else { - if (!opentype_features.has(tag) || (int)opentype_features[tag] != value) { - opentype_features[tag] = value; - _shape(); - update(); - } - } - notify_property_list_changed(); - return true; - } if (!str.begins_with("slot/")) { return false; @@ -106,18 +86,6 @@ bool GraphNode::_set(const StringName &p_name, const Variant &p_value) { bool GraphNode::_get(const StringName &p_name, Variant &r_ret) const { String str = p_name; - if (str.begins_with("opentype_features/")) { - String name = str.get_slicec('/', 1); - int32_t tag = TS->name_to_tag(name); - if (opentype_features.has(tag)) { - r_ret = opentype_features[tag]; - return true; - } else { - r_ret = -1; - return true; - } - } - if (!str.begins_with("slot/")) { return false; } @@ -156,12 +124,6 @@ bool GraphNode::_get(const StringName &p_name, Variant &r_ret) const { } void GraphNode::_get_property_list(List<PropertyInfo> *p_list) const { - for (const Variant *ftr = opentype_features.next(nullptr); ftr != nullptr; ftr = opentype_features.next(ftr)) { - String name = TS->tag_to_name(*ftr); - p_list->push_back(PropertyInfo(Variant::INT, "opentype_features/" + name)); - } - p_list->push_back(PropertyInfo(Variant::NIL, "opentype_features/_new", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_EDITOR)); - int idx = 0; for (int i = 0; i < get_child_count(); i++) { Control *c = Object::cast_to<Control>(get_child(i)); @@ -471,7 +433,7 @@ void GraphNode::_shape() { } else { title_buf->set_direction((TextServer::Direction)text_direction); } - title_buf->add_string(title, font, font_size, opentype_features, (!language.is_empty()) ? language : TranslationServer::get_singleton()->get_tool_locale()); + title_buf->add_string(title, font, font_size, language); } #ifdef TOOLS_ENABLED @@ -726,29 +688,6 @@ Control::TextDirection GraphNode::get_text_direction() const { return text_direction; } -void GraphNode::clear_opentype_features() { - opentype_features.clear(); - _shape(); - update(); -} - -void GraphNode::set_opentype_feature(const String &p_name, int p_value) { - int32_t tag = TS->name_to_tag(p_name); - if (!opentype_features.has(tag) || (int)opentype_features[tag] != p_value) { - opentype_features[tag] = p_value; - _shape(); - update(); - } -} - -int GraphNode::get_opentype_feature(const String &p_name) const { - int32_t tag = TS->name_to_tag(p_name); - if (!opentype_features.has(tag)) { - return -1; - } - return opentype_features[tag]; -} - void GraphNode::set_language(const String &p_language) { if (language != p_language) { language = p_language; @@ -1043,9 +982,6 @@ void GraphNode::_bind_methods() { ClassDB::bind_method(D_METHOD("get_title"), &GraphNode::get_title); ClassDB::bind_method(D_METHOD("set_text_direction", "direction"), &GraphNode::set_text_direction); ClassDB::bind_method(D_METHOD("get_text_direction"), &GraphNode::get_text_direction); - ClassDB::bind_method(D_METHOD("set_opentype_feature", "tag", "value"), &GraphNode::set_opentype_feature); - ClassDB::bind_method(D_METHOD("get_opentype_feature", "tag"), &GraphNode::get_opentype_feature); - ClassDB::bind_method(D_METHOD("clear_opentype_features"), &GraphNode::clear_opentype_features); ClassDB::bind_method(D_METHOD("set_language", "language"), &GraphNode::set_language); ClassDB::bind_method(D_METHOD("get_language"), &GraphNode::get_language); @@ -1105,8 +1041,6 @@ void GraphNode::_bind_methods() { ClassDB::bind_method(D_METHOD("get_overlay"), &GraphNode::get_overlay); ADD_PROPERTY(PropertyInfo(Variant::STRING, "title"), "set_title", "get_title"); - ADD_PROPERTY(PropertyInfo(Variant::INT, "text_direction", PROPERTY_HINT_ENUM, "Auto,Left-to-Right,Right-to-Left,Inherited"), "set_text_direction", "get_text_direction"); - ADD_PROPERTY(PropertyInfo(Variant::STRING, "language", PROPERTY_HINT_LOCALE_ID, ""), "set_language", "get_language"); ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "position_offset", PROPERTY_HINT_NONE, "suffix:px"), "set_position_offset", "get_position_offset"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "show_close"), "set_show_close_button", "is_close_button_visible"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "resizable"), "set_resizable", "is_resizable"); @@ -1114,6 +1048,10 @@ void GraphNode::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::BOOL, "comment"), "set_comment", "is_comment"); ADD_PROPERTY(PropertyInfo(Variant::INT, "overlay", PROPERTY_HINT_ENUM, "Disabled,Breakpoint,Position"), "set_overlay", "get_overlay"); + ADD_GROUP("BiDi", ""); + ADD_PROPERTY(PropertyInfo(Variant::INT, "text_direction", PROPERTY_HINT_ENUM, "Auto,Left-to-Right,Right-to-Left,Inherited"), "set_text_direction", "get_text_direction"); + ADD_PROPERTY(PropertyInfo(Variant::STRING, "language", PROPERTY_HINT_LOCALE_ID, ""), "set_language", "get_language"); + ADD_SIGNAL(MethodInfo("position_offset_changed")); ADD_SIGNAL(MethodInfo("slot_updated", PropertyInfo(Variant::INT, "idx"))); ADD_SIGNAL(MethodInfo("dragged", PropertyInfo(Variant::VECTOR2, "from"), PropertyInfo(Variant::VECTOR2, "to"))); diff --git a/scene/gui/graph_node.h b/scene/gui/graph_node.h index f6c943dc89..0651eb5cc9 100644 --- a/scene/gui/graph_node.h +++ b/scene/gui/graph_node.h @@ -60,7 +60,6 @@ private: String title; Ref<TextLine> title_buf; - Dictionary opentype_features; String language; TextDirection text_direction = TEXT_DIRECTION_AUTO; @@ -148,10 +147,6 @@ public: void set_text_direction(TextDirection p_text_direction); TextDirection get_text_direction() const; - void set_opentype_feature(const String &p_name, int p_value); - int get_opentype_feature(const String &p_name) const; - void clear_opentype_features(); - void set_language(const String &p_language); String get_language() const; diff --git a/scene/gui/grid_container.cpp b/scene/gui/grid_container.cpp index 6f8518a7b0..eaa6943ad2 100644 --- a/scene/gui/grid_container.cpp +++ b/scene/gui/grid_container.cpp @@ -41,8 +41,6 @@ void GridContainer::_notification(int p_what) { int hsep = get_theme_constant(SNAME("h_separation")); int vsep = get_theme_constant(SNAME("v_separation")); - int max_col = MIN(get_child_count(), columns); - int max_row = ceil((float)get_child_count() / (float)columns); // Compute the per-column/per-row data. int valid_controls_index = 0; @@ -79,6 +77,9 @@ void GridContainer::_notification(int p_what) { } } + int max_col = MIN(valid_controls_index, columns); + int max_row = ceil((float)valid_controls_index / (float)columns); + // Consider all empty columns expanded. for (int i = valid_controls_index; i < columns; i++) { col_expanded.insert(i); diff --git a/scene/gui/item_list.cpp b/scene/gui/item_list.cpp index aeb5338022..d0a25972f8 100644 --- a/scene/gui/item_list.cpp +++ b/scene/gui/item_list.cpp @@ -43,11 +43,11 @@ void ItemList::_shape(int p_idx) { } else { item.text_buf->set_direction((TextServer::Direction)item.text_direction); } - item.text_buf->add_string(item.text, get_theme_font(SNAME("font")), get_theme_font_size(SNAME("font_size")), item.opentype_features, (!item.language.is_empty()) ? item.language : TranslationServer::get_singleton()->get_tool_locale()); + item.text_buf->add_string(item.text, get_theme_font(SNAME("font")), get_theme_font_size(SNAME("font_size")), item.language); if (icon_mode == ICON_MODE_TOP && max_text_lines > 0) { - item.text_buf->set_flags(TextServer::BREAK_MANDATORY | TextServer::BREAK_WORD_BOUND | TextServer::BREAK_GRAPHEME_BOUND); + item.text_buf->set_break_flags(TextServer::BREAK_MANDATORY | TextServer::BREAK_WORD_BOUND | TextServer::BREAK_GRAPHEME_BOUND); } else { - item.text_buf->set_flags(TextServer::BREAK_NONE); + item.text_buf->set_break_flags(TextServer::BREAK_NONE); } item.text_buf->set_text_overrun_behavior(text_overrun_behavior); item.text_buf->set_max_lines_visible(max_text_lines); @@ -117,35 +117,6 @@ Control::TextDirection ItemList::get_item_text_direction(int p_idx) const { return items[p_idx].text_direction; } -void ItemList::clear_item_opentype_features(int p_idx) { - ERR_FAIL_INDEX(p_idx, items.size()); - items.write[p_idx].opentype_features.clear(); - _shape(p_idx); - update(); -} - -void ItemList::set_item_opentype_feature(int p_idx, const String &p_name, int p_value) { - if (p_idx < 0) { - p_idx += get_item_count(); - } - ERR_FAIL_INDEX(p_idx, items.size()); - int32_t tag = TS->name_to_tag(p_name); - if (!items[p_idx].opentype_features.has(tag) || (int)items[p_idx].opentype_features[tag] != p_value) { - items.write[p_idx].opentype_features[tag] = p_value; - _shape(p_idx); - update(); - } -} - -int ItemList::get_item_opentype_feature(int p_idx, const String &p_name) const { - ERR_FAIL_INDEX_V(p_idx, items.size(), -1); - int32_t tag = TS->name_to_tag(p_name); - if (!items[p_idx].opentype_features.has(tag)) { - return -1; - } - return items[p_idx].opentype_features[tag]; -} - void ItemList::set_item_language(int p_idx, const String &p_language) { if (p_idx < 0) { p_idx += get_item_count(); @@ -499,10 +470,10 @@ void ItemList::set_max_text_lines(int p_lines) { max_text_lines = p_lines; for (int i = 0; i < items.size(); i++) { if (icon_mode == ICON_MODE_TOP && max_text_lines > 0) { - items.write[i].text_buf->set_flags(TextServer::BREAK_MANDATORY | TextServer::BREAK_WORD_BOUND | TextServer::BREAK_GRAPHEME_BOUND); + items.write[i].text_buf->set_break_flags(TextServer::BREAK_MANDATORY | TextServer::BREAK_WORD_BOUND | TextServer::BREAK_GRAPHEME_BOUND); items.write[i].text_buf->set_max_lines_visible(p_lines); } else { - items.write[i].text_buf->set_flags(TextServer::BREAK_NONE); + items.write[i].text_buf->set_break_flags(TextServer::BREAK_NONE); } } shape_changed = true; @@ -540,9 +511,9 @@ void ItemList::set_icon_mode(IconMode p_mode) { icon_mode = p_mode; for (int i = 0; i < items.size(); i++) { if (icon_mode == ICON_MODE_TOP && max_text_lines > 0) { - items.write[i].text_buf->set_flags(TextServer::BREAK_MANDATORY | TextServer::BREAK_WORD_BOUND | TextServer::BREAK_GRAPHEME_BOUND); + items.write[i].text_buf->set_break_flags(TextServer::BREAK_MANDATORY | TextServer::BREAK_WORD_BOUND | TextServer::BREAK_GRAPHEME_BOUND); } else { - items.write[i].text_buf->set_flags(TextServer::BREAK_NONE); + items.write[i].text_buf->set_break_flags(TextServer::BREAK_NONE); } } shape_changed = true; @@ -1656,10 +1627,6 @@ void ItemList::_bind_methods() { ClassDB::bind_method(D_METHOD("set_item_text_direction", "idx", "direction"), &ItemList::set_item_text_direction); ClassDB::bind_method(D_METHOD("get_item_text_direction", "idx"), &ItemList::get_item_text_direction); - ClassDB::bind_method(D_METHOD("set_item_opentype_feature", "idx", "tag", "value"), &ItemList::set_item_opentype_feature); - ClassDB::bind_method(D_METHOD("get_item_opentype_feature", "idx", "tag"), &ItemList::get_item_opentype_feature); - ClassDB::bind_method(D_METHOD("clear_item_opentype_features", "idx"), &ItemList::clear_item_opentype_features); - ClassDB::bind_method(D_METHOD("set_item_language", "idx", "language"), &ItemList::set_item_language); ClassDB::bind_method(D_METHOD("get_item_language", "idx"), &ItemList::get_item_language); diff --git a/scene/gui/item_list.h b/scene/gui/item_list.h index a15b090149..21bd22759c 100644 --- a/scene/gui/item_list.h +++ b/scene/gui/item_list.h @@ -28,8 +28,8 @@ /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /*************************************************************************/ -#ifndef ITEMLIST_H -#define ITEMLIST_H +#ifndef ITEM_LIST_H +#define ITEM_LIST_H #include "scene/gui/control.h" #include "scene/gui/scroll_bar.h" @@ -58,7 +58,6 @@ private: Ref<Texture2D> tag_icon; String text; Ref<TextParagraph> text_buf; - Dictionary opentype_features; String language; TextDirection text_direction = TEXT_DIRECTION_AUTO; @@ -145,10 +144,6 @@ public: void set_item_text_direction(int p_idx, TextDirection p_text_direction); TextDirection get_item_text_direction(int p_idx) const; - void set_item_opentype_feature(int p_idx, const String &p_name, int p_value); - int get_item_opentype_feature(int p_idx, const String &p_name) const; - void clear_item_opentype_features(int p_idx); - void set_item_language(int p_idx, const String &p_language); String get_item_language(int p_idx) const; @@ -264,4 +259,4 @@ public: VARIANT_ENUM_CAST(ItemList::SelectMode); VARIANT_ENUM_CAST(ItemList::IconMode); -#endif // ITEMLIST_H +#endif // ITEM_LIST_H diff --git a/scene/gui/label.cpp b/scene/gui/label.cpp index 82ab7c2e18..e7f48beb00 100644 --- a/scene/gui/label.cpp +++ b/scene/gui/label.cpp @@ -31,6 +31,7 @@ #include "label.h" #include "core/config/project_settings.h" +#include "core/core_string_names.h" #include "core/string/print_string.h" #include "core/string/translation.h" @@ -64,17 +65,18 @@ bool Label::is_uppercase() const { } int Label::get_line_height(int p_line) const { - Ref<Font> font = get_theme_font(SNAME("font")); + Ref<Font> font = (settings.is_valid() && settings->get_font().is_valid()) ? settings->get_font() : get_theme_font(SNAME("font")); if (p_line >= 0 && p_line < lines_rid.size()) { - return TS->shaped_text_get_size(lines_rid[p_line]).y + font->get_spacing(TextServer::SPACING_TOP) + font->get_spacing(TextServer::SPACING_BOTTOM); + return TS->shaped_text_get_size(lines_rid[p_line]).y; } else if (lines_rid.size() > 0) { int h = 0; for (int i = 0; i < lines_rid.size(); i++) { - h = MAX(h, TS->shaped_text_get_size(lines_rid[i]).y) + font->get_spacing(TextServer::SPACING_TOP) + font->get_spacing(TextServer::SPACING_BOTTOM); + h = MAX(h, TS->shaped_text_get_size(lines_rid[i]).y); } return h; } else { - return font->get_height(get_theme_font_size(SNAME("font_size"))); + int font_size = settings.is_valid() ? settings->get_font_size() : get_theme_font_size(SNAME("font_size")); + return font->get_height(font_size); } } @@ -83,7 +85,6 @@ void Label::_shape() { int width = (get_size().width - style->get_minimum_size().width); if (dirty || font_dirty) { - String lang = (!language.is_empty()) ? language : TranslationServer::get_singleton()->get_tool_locale(); if (dirty) { TS->shaped_text_clear(text_rid); } @@ -92,21 +93,24 @@ void Label::_shape() { } else { TS->shaped_text_set_direction(text_rid, (TextServer::Direction)text_direction); } - const Ref<Font> &font = get_theme_font(SNAME("font")); - int font_size = get_theme_font_size(SNAME("font_size")); + const Ref<Font> &font = (settings.is_valid() && settings->get_font().is_valid()) ? settings->get_font() : get_theme_font(SNAME("font")); + int font_size = settings.is_valid() ? settings->get_font_size() : get_theme_font_size(SNAME("font_size")); ERR_FAIL_COND(font.is_null()); - String text = (uppercase) ? TS->string_to_upper(xl_text, lang) : xl_text; + String text = (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); } if (dirty) { - TS->shaped_text_add_string(text_rid, text, font->get_rids(), font_size, opentype_features, lang); + TS->shaped_text_add_string(text_rid, text, 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++) { - TS->shaped_set_span_update_font(text_rid, i, font->get_rids(), font_size, opentype_features); + TS->shaped_set_span_update_font(text_rid, i, font->get_rids(), font_size, font->get_opentype_features()); } } + 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)); dirty = false; font_dirty = false; @@ -119,10 +123,10 @@ void Label::_shape() { } lines_rid.clear(); - uint16_t autowrap_flags = TextServer::BREAK_MANDATORY; + BitField<TextServer::LineBreakFlag> autowrap_flags = TextServer::BREAK_MANDATORY; switch (autowrap_mode) { case TextServer::AUTOWRAP_WORD_SMART: - autowrap_flags = TextServer::BREAK_WORD_BOUND_ADAPTIVE | TextServer::BREAK_MANDATORY; + autowrap_flags = TextServer::BREAK_WORD_BOUND | TextServer::BREAK_ADAPTIVE | TextServer::BREAK_MANDATORY; break; case TextServer::AUTOWRAP_WORD: autowrap_flags = TextServer::BREAK_WORD_BOUND | TextServer::BREAK_MANDATORY; @@ -156,23 +160,23 @@ void Label::_shape() { } if (lines_dirty) { - uint16_t overrun_flags = TextServer::OVERRUN_NO_TRIM; + BitField<TextServer::TextOverrunFlag> overrun_flags = TextServer::OVERRUN_NO_TRIM; switch (overrun_behavior) { case TextServer::OVERRUN_TRIM_WORD_ELLIPSIS: - overrun_flags |= TextServer::OVERRUN_TRIM; - overrun_flags |= TextServer::OVERRUN_TRIM_WORD_ONLY; - overrun_flags |= TextServer::OVERRUN_ADD_ELLIPSIS; + overrun_flags.set_flag(TextServer::OVERRUN_TRIM); + overrun_flags.set_flag(TextServer::OVERRUN_TRIM_WORD_ONLY); + overrun_flags.set_flag(TextServer::OVERRUN_ADD_ELLIPSIS); break; case TextServer::OVERRUN_TRIM_ELLIPSIS: - overrun_flags |= TextServer::OVERRUN_TRIM; - overrun_flags |= TextServer::OVERRUN_ADD_ELLIPSIS; + overrun_flags.set_flag(TextServer::OVERRUN_TRIM); + overrun_flags.set_flag(TextServer::OVERRUN_ADD_ELLIPSIS); break; case TextServer::OVERRUN_TRIM_WORD: - overrun_flags |= TextServer::OVERRUN_TRIM; - overrun_flags |= TextServer::OVERRUN_TRIM_WORD_ONLY; + overrun_flags.set_flag(TextServer::OVERRUN_TRIM); + overrun_flags.set_flag(TextServer::OVERRUN_TRIM_WORD_ONLY); break; case TextServer::OVERRUN_TRIM_CHAR: - overrun_flags |= TextServer::OVERRUN_TRIM; + overrun_flags.set_flag(TextServer::OVERRUN_TRIM); break; case TextServer::OVERRUN_NO_TRIMMING: break; @@ -184,7 +188,7 @@ void Label::_shape() { int visible_lines = get_visible_line_count(); bool lines_hidden = visible_lines > 0 && visible_lines < lines_rid.size(); if (lines_hidden) { - overrun_flags |= TextServer::OVERRUN_ENFORCE_ELLIPSIS; + overrun_flags.set_flag(TextServer::OVERRUN_ENFORCE_ELLIPSIS); } if (horizontal_alignment == HORIZONTAL_ALIGNMENT_FILL) { for (int i = 0; i < lines_rid.size(); i++) { @@ -202,7 +206,7 @@ void Label::_shape() { for (int i = 0; i < lines_rid.size(); i++) { if (horizontal_alignment == HORIZONTAL_ALIGNMENT_FILL) { TS->shaped_text_fit_to_width(lines_rid[i], width); - overrun_flags |= TextServer::OVERRUN_JUSTIFICATION_AWARE; + overrun_flags.set_flag(TextServer::OVERRUN_JUSTIFICATION_AWARE); TS->shaped_text_overrun_trim_to_width(lines_rid[i], width, overrun_flags); TS->shaped_text_fit_to_width(lines_rid[i], width, TextServer::JUSTIFICATION_WORD_BOUND | TextServer::JUSTIFICATION_KASHIDA | TextServer::JUSTIFICATION_CONSTRAIN_ELLIPSIS); } else { @@ -221,9 +225,8 @@ void Label::_shape() { } void Label::_update_visible() { - int line_spacing = get_theme_constant(SNAME("line_spacing"), SNAME("Label")); + int line_spacing = settings.is_valid() ? settings->get_line_spacing() : get_theme_constant(SNAME("line_spacing"), SNAME("Label")); Ref<StyleBox> style = get_theme_stylebox(SNAME("normal"), SNAME("Label")); - Ref<Font> font = get_theme_font(SNAME("font")); int lines_visible = lines_rid.size(); if (max_lines_visible >= 0 && lines_visible > max_lines_visible) { @@ -233,7 +236,7 @@ void Label::_update_visible() { minsize.height = 0; int last_line = MIN(lines_rid.size(), lines_visible + lines_skipped); for (int64_t i = lines_skipped; i < last_line; i++) { - minsize.height += TS->shaped_text_get_size(lines_rid[i]).y + font->get_spacing(TextServer::SPACING_TOP) + font->get_spacing(TextServer::SPACING_BOTTOM) + line_spacing; + minsize.height += TS->shaped_text_get_size(lines_rid[i]).y + line_spacing; if (minsize.height > (get_size().height - style->get_minimum_size().height + line_spacing)) { break; } @@ -293,17 +296,19 @@ void Label::_notification(int p_what) { RID ci = get_canvas_item(); + bool has_settings = settings.is_valid(); + Size2 string_size; Size2 size = get_size(); Ref<StyleBox> style = get_theme_stylebox(SNAME("normal")); - Ref<Font> font = get_theme_font(SNAME("font")); - Color font_color = get_theme_color(SNAME("font_color")); - Color font_shadow_color = get_theme_color(SNAME("font_shadow_color")); - Point2 shadow_ofs(get_theme_constant(SNAME("shadow_offset_x")), get_theme_constant(SNAME("shadow_offset_y"))); - int line_spacing = get_theme_constant(SNAME("line_spacing")); - Color font_outline_color = get_theme_color(SNAME("font_outline_color")); - int outline_size = get_theme_constant(SNAME("outline_size")); - int shadow_outline_size = get_theme_constant(SNAME("shadow_outline_size")); + Ref<Font> font = (has_settings && settings->get_font().is_valid()) ? settings->get_font() : get_theme_font(SNAME("font")); + Color font_color = has_settings ? settings->get_font_color() : get_theme_color(SNAME("font_color")); + Color font_shadow_color = has_settings ? settings->get_shadow_color() : get_theme_color(SNAME("font_shadow_color")); + Point2 shadow_ofs = has_settings ? settings->get_shadow_offset() : Point2(get_theme_constant(SNAME("shadow_offset_x")), get_theme_constant(SNAME("shadow_offset_y"))); + int line_spacing = has_settings ? settings->get_line_spacing() : get_theme_constant(SNAME("line_spacing")); + Color font_outline_color = has_settings ? settings->get_outline_color() : get_theme_color(SNAME("font_outline_color")); + int outline_size = has_settings ? settings->get_outline_size() : get_theme_constant(SNAME("outline_size")); + int shadow_outline_size = has_settings ? settings->get_shadow_size() : get_theme_constant(SNAME("shadow_outline_size")); bool rtl = (TS->shaped_text_get_inferred_direction(text_rid) == TextServer::DIRECTION_RTL); bool rtl_layout = is_layout_rtl(); @@ -314,7 +319,7 @@ void Label::_notification(int p_what) { // Get number of lines to fit to the height. for (int64_t i = lines_skipped; i < lines_rid.size(); i++) { - total_h += TS->shaped_text_get_size(lines_rid[i]).y + font->get_spacing(TextServer::SPACING_TOP) + font->get_spacing(TextServer::SPACING_BOTTOM) + line_spacing; + total_h += TS->shaped_text_get_size(lines_rid[i]).y + line_spacing; if (total_h > (get_size().height - style->get_minimum_size().height + line_spacing)) { break; } @@ -334,7 +339,7 @@ void Label::_notification(int p_what) { int total_glyphs = 0; total_h = 0; for (int64_t i = lines_skipped; i < last_line; i++) { - total_h += TS->shaped_text_get_size(lines_rid[i]).y + font->get_spacing(TextServer::SPACING_TOP) + font->get_spacing(TextServer::SPACING_BOTTOM) + line_spacing; + total_h += TS->shaped_text_get_size(lines_rid[i]).y + line_spacing; total_glyphs += TS->shaped_text_get_glyph_count(lines_rid[i]) + TS->shaped_text_get_ellipsis_glyph_count(lines_rid[i]); } int visible_glyphs = total_glyphs * percent_visible; @@ -374,7 +379,7 @@ void Label::_notification(int p_what) { for (int i = lines_skipped; i < last_line; i++) { Size2 line_size = TS->shaped_text_get_size(lines_rid[i]); ofs.x = 0; - ofs.y += TS->shaped_text_get_ascent(lines_rid[i]) + font->get_spacing(TextServer::SPACING_TOP); + ofs.y += TS->shaped_text_get_ascent(lines_rid[i]); switch (horizontal_alignment) { case HORIZONTAL_ALIGNMENT_FILL: if (rtl && autowrap_mode != TextServer::AUTOWRAP_OFF) { @@ -527,7 +532,7 @@ void Label::_notification(int p_what) { } } } - ofs.y += TS->shaped_text_get_descent(lines_rid[i]) + vsep + line_spacing + font->get_spacing(TextServer::SPACING_BOTTOM); + ofs.y += TS->shaped_text_get_descent(lines_rid[i]) + vsep + line_spacing; } } break; @@ -550,8 +555,10 @@ Size2 Label::get_minimum_size() const { Size2 min_size = minsize; - Ref<Font> font = get_theme_font(SNAME("font")); - min_size.height = MAX(min_size.height, font->get_height(get_theme_font_size(SNAME("font_size"))) + font->get_spacing(TextServer::SPACING_TOP) + font->get_spacing(TextServer::SPACING_BOTTOM)); + const Ref<Font> &font = (settings.is_valid() && settings->get_font().is_valid()) ? settings->get_font() : get_theme_font(SNAME("font")); + int font_size = settings.is_valid() ? settings->get_font_size() : get_theme_font_size(SNAME("font_size")); + + min_size.height = MAX(min_size.height, font->get_height(font_size) + font->get_spacing(TextServer::SPACING_TOP) + font->get_spacing(TextServer::SPACING_BOTTOM)); Size2 min_style = get_theme_stylebox(SNAME("normal"))->get_minimum_size(); if (autowrap_mode != TextServer::AUTOWRAP_OFF) { @@ -576,13 +583,12 @@ int Label::get_line_count() const { } int Label::get_visible_line_count() const { - Ref<Font> font = get_theme_font(SNAME("font")); Ref<StyleBox> style = get_theme_stylebox(SNAME("normal")); - int line_spacing = get_theme_constant(SNAME("line_spacing")); + int line_spacing = settings.is_valid() ? settings->get_line_spacing() : get_theme_constant(SNAME("line_spacing")); int lines_visible = 0; float total_h = 0.0; for (int64_t i = lines_skipped; i < lines_rid.size(); i++) { - total_h += TS->shaped_text_get_size(lines_rid[i]).y + font->get_spacing(TextServer::SPACING_TOP) + font->get_spacing(TextServer::SPACING_BOTTOM) + line_spacing; + total_h += TS->shaped_text_get_size(lines_rid[i]).y + line_spacing; if (total_h > (get_size().height - style->get_minimum_size().height + line_spacing)) { break; } @@ -639,6 +645,28 @@ void Label::set_text(const String &p_string) { update_minimum_size(); } +void Label::_invalidate() { + font_dirty = true; + update(); +} + +void Label::set_label_settings(const Ref<LabelSettings> &p_settings) { + if (settings != p_settings) { + if (settings.is_valid()) { + settings->disconnect(CoreStringNames::get_singleton()->changed, callable_mp(this, &Label::_invalidate)); + } + settings = p_settings; + if (settings.is_valid()) { + settings->connect(CoreStringNames::get_singleton()->changed, callable_mp(this, &Label::_invalidate), CONNECT_REFERENCE_COUNTED); + } + _invalidate(); + } +} + +Ref<LabelSettings> Label::get_label_settings() const { + return settings; +} + void Label::set_text_direction(Control::TextDirection p_text_direction) { ERR_FAIL_COND((int)p_text_direction < -1 || (int)p_text_direction > 3); if (text_direction != p_text_direction) { @@ -674,29 +702,6 @@ Control::TextDirection Label::get_text_direction() const { return text_direction; } -void Label::clear_opentype_features() { - opentype_features.clear(); - font_dirty = true; - update(); -} - -void Label::set_opentype_feature(const String &p_name, int p_value) { - int32_t tag = TS->name_to_tag(p_name); - if (!opentype_features.has(tag) || (int)opentype_features[tag] != p_value) { - opentype_features[tag] = p_value; - font_dirty = true; - update(); - } -} - -int Label::get_opentype_feature(const String &p_name) const { - int32_t tag = TS->name_to_tag(p_name); - if (!opentype_features.has(tag)) { - return -1; - } - return opentype_features[tag]; -} - void Label::set_language(const String &p_language) { if (language != p_language) { language = p_language; @@ -818,56 +823,6 @@ int Label::get_total_character_count() const { return xl_text.length(); } -bool Label::_set(const StringName &p_name, const Variant &p_value) { - String str = p_name; - if (str.begins_with("opentype_features/")) { - String name = str.get_slicec('/', 1); - int32_t tag = TS->name_to_tag(name); - int value = p_value; - if (value == -1) { - if (opentype_features.has(tag)) { - opentype_features.erase(tag); - font_dirty = true; - update(); - } - } else { - if (!opentype_features.has(tag) || (int)opentype_features[tag] != value) { - opentype_features[tag] = value; - font_dirty = true; - update(); - } - } - notify_property_list_changed(); - return true; - } - - return false; -} - -bool Label::_get(const StringName &p_name, Variant &r_ret) const { - String str = p_name; - if (str.begins_with("opentype_features/")) { - String name = str.get_slicec('/', 1); - int32_t tag = TS->name_to_tag(name); - if (opentype_features.has(tag)) { - r_ret = opentype_features[tag]; - return true; - } else { - r_ret = -1; - return true; - } - } - return false; -} - -void Label::_get_property_list(List<PropertyInfo> *p_list) const { - for (const Variant *ftr = opentype_features.next(nullptr); ftr != nullptr; ftr = opentype_features.next(ftr)) { - String name = TS->tag_to_name(*ftr); - p_list->push_back(PropertyInfo(Variant::INT, "opentype_features/" + name)); - } - p_list->push_back(PropertyInfo(Variant::NIL, "opentype_features/_new", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_EDITOR)); -} - void Label::_bind_methods() { ClassDB::bind_method(D_METHOD("set_horizontal_alignment", "alignment"), &Label::set_horizontal_alignment); ClassDB::bind_method(D_METHOD("get_horizontal_alignment"), &Label::get_horizontal_alignment); @@ -875,11 +830,10 @@ void Label::_bind_methods() { ClassDB::bind_method(D_METHOD("get_vertical_alignment"), &Label::get_vertical_alignment); ClassDB::bind_method(D_METHOD("set_text", "text"), &Label::set_text); ClassDB::bind_method(D_METHOD("get_text"), &Label::get_text); + ClassDB::bind_method(D_METHOD("set_label_settings", "settings"), &Label::set_label_settings); + ClassDB::bind_method(D_METHOD("get_label_settings"), &Label::get_label_settings); ClassDB::bind_method(D_METHOD("set_text_direction", "direction"), &Label::set_text_direction); ClassDB::bind_method(D_METHOD("get_text_direction"), &Label::get_text_direction); - ClassDB::bind_method(D_METHOD("set_opentype_feature", "tag", "value"), &Label::set_opentype_feature); - ClassDB::bind_method(D_METHOD("get_opentype_feature", "tag"), &Label::get_opentype_feature); - ClassDB::bind_method(D_METHOD("clear_opentype_features"), &Label::clear_opentype_features); ClassDB::bind_method(D_METHOD("set_language", "language"), &Label::set_language); ClassDB::bind_method(D_METHOD("get_language"), &Label::get_language); ClassDB::bind_method(D_METHOD("set_autowrap_mode", "autowrap_mode"), &Label::set_autowrap_mode); @@ -910,6 +864,7 @@ void Label::_bind_methods() { ClassDB::bind_method(D_METHOD("get_structured_text_bidi_override_options"), &Label::get_structured_text_bidi_override_options); ADD_PROPERTY(PropertyInfo(Variant::STRING, "text", PROPERTY_HINT_MULTILINE_TEXT, "", PROPERTY_USAGE_DEFAULT_INTL), "set_text", "get_text"); + ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "label_settings", PROPERTY_HINT_RESOURCE_TYPE, "LabelSettings"), "set_label_settings", "get_label_settings"); ADD_PROPERTY(PropertyInfo(Variant::INT, "horizontal_alignment", PROPERTY_HINT_ENUM, "Left,Center,Right,Fill"), "set_horizontal_alignment", "get_horizontal_alignment"); ADD_PROPERTY(PropertyInfo(Variant::INT, "vertical_alignment", PROPERTY_HINT_ENUM, "Top,Center,Bottom,Fill"), "set_vertical_alignment", "get_vertical_alignment"); ADD_PROPERTY(PropertyInfo(Variant::INT, "autowrap_mode", PROPERTY_HINT_ENUM, "Off,Arbitrary,Word,Word (Smart)"), "set_autowrap_mode", "get_autowrap_mode"); @@ -924,11 +879,9 @@ void Label::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::INT, "visible_characters_behavior", PROPERTY_HINT_ENUM, "Characters Before Shaping,Characters After Shaping,Glyphs (Layout Direction),Glyphs (Left-to-Right),Glyphs (Right-to-Left)"), "set_visible_characters_behavior", "get_visible_characters_behavior"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "percent_visible", PROPERTY_HINT_RANGE, "0,1,0.001"), "set_percent_visible", "get_percent_visible"); - ADD_GROUP("Locale", ""); + ADD_GROUP("BiDi", ""); ADD_PROPERTY(PropertyInfo(Variant::INT, "text_direction", PROPERTY_HINT_ENUM, "Auto,Left-to-Right,Right-to-Left,Inherited"), "set_text_direction", "get_text_direction"); ADD_PROPERTY(PropertyInfo(Variant::STRING, "language", PROPERTY_HINT_LOCALE_ID, ""), "set_language", "get_language"); - - ADD_GROUP("Structured Text", "structured_text_"); ADD_PROPERTY(PropertyInfo(Variant::INT, "structured_text_bidi_override", PROPERTY_HINT_ENUM, "Default,URI,File,Email,List,None,Custom"), "set_structured_text_bidi_override", "get_structured_text_bidi_override"); ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "structured_text_bidi_override_options"), "set_structured_text_bidi_override_options", "get_structured_text_bidi_override_options"); } diff --git a/scene/gui/label.h b/scene/gui/label.h index fac3d75a1b..cab5b36d68 100644 --- a/scene/gui/label.h +++ b/scene/gui/label.h @@ -32,6 +32,7 @@ #define LABEL_H #include "scene/gui/control.h" +#include "scene/resources/label_settings.h" class Label : public Control { GDCLASS(Label, Control); @@ -53,7 +54,6 @@ private: RID text_rid; Vector<RID> lines_rid; - Dictionary opentype_features; String language; TextDirection text_direction = TEXT_DIRECTION_AUTO; TextServer::StructuredTextParser st_parser = TextServer::STRUCTURED_TEXT_DEFAULT; @@ -66,18 +66,17 @@ private: int lines_skipped = 0; int max_lines_visible = -1; + Ref<LabelSettings> settings; + void _update_visible(); void _shape(); + void _invalidate(); protected: void _notification(int p_what); static void _bind_methods(); - bool _set(const StringName &p_name, const Variant &p_value); - bool _get(const StringName &p_name, Variant &r_ret) const; - void _get_property_list(List<PropertyInfo> *p_list) const; - public: virtual Size2 get_minimum_size() const override; @@ -90,13 +89,12 @@ public: void set_text(const String &p_string); String get_text() const; + void set_label_settings(const Ref<LabelSettings> &p_settings); + Ref<LabelSettings> get_label_settings() const; + void set_text_direction(TextDirection p_text_direction); TextDirection get_text_direction() const; - void set_opentype_feature(const String &p_name, int p_value); - int get_opentype_feature(const String &p_name) const; - void clear_opentype_features(); - void set_language(const String &p_language); String get_language() const; @@ -142,4 +140,4 @@ public: ~Label(); }; -#endif +#endif // LABEL_H diff --git a/scene/gui/line_edit.cpp b/scene/gui/line_edit.cpp index 540250c8e9..39f8f23cd8 100644 --- a/scene/gui/line_edit.cpp +++ b/scene/gui/line_edit.cpp @@ -744,6 +744,17 @@ void LineEdit::_notification(int p_what) { update(); } break; + case NOTIFICATION_INTERNAL_PROCESS: { + if (caret_blinking) { + caret_blink_timer += get_process_delta_time(); + + if (caret_blink_timer >= caret_blink_speed) { + caret_blink_timer = 0.0; + _toggle_draw_caret(); + } + } + } break; + case NOTIFICATION_DRAW: { if ((!has_focus() && !(menu && menu->has_focus()) && !caret_force_displayed) || !window_has_focus) { draw_caret = false; @@ -776,7 +787,7 @@ void LineEdit::_notification(int p_what) { int x_ofs = 0; bool using_placeholder = text.is_empty() && ime_text.is_empty(); float text_width = TS->shaped_text_get_size(text_rid).x; - float text_height = TS->shaped_text_get_size(text_rid).y + font->get_spacing(TextServer::SPACING_TOP) + font->get_spacing(TextServer::SPACING_BOTTOM); + float text_height = TS->shaped_text_get_size(text_rid).y; switch (alignment) { case HORIZONTAL_ALIGNMENT_FILL: @@ -991,8 +1002,9 @@ void LineEdit::_notification(int p_what) { case NOTIFICATION_FOCUS_ENTER: { if (!caret_force_displayed) { if (caret_blink_enabled) { - if (caret_blink_timer->is_stopped()) { - caret_blink_timer->start(); + if (!caret_blinking) { + caret_blinking = true; + caret_blink_timer = 0.0; } } else { draw_caret = true; @@ -1010,7 +1022,7 @@ void LineEdit::_notification(int p_what) { case NOTIFICATION_FOCUS_EXIT: { if (caret_blink_enabled && !caret_force_displayed) { - caret_blink_timer->stop(); + caret_blinking = false; } if (get_viewport()->get_window_id() != DisplayServer::INVALID_WINDOW_ID && DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_IME)) { @@ -1318,14 +1330,16 @@ bool LineEdit::is_caret_blink_enabled() const { void LineEdit::set_caret_blink_enabled(const bool p_enabled) { caret_blink_enabled = p_enabled; + set_process_internal(p_enabled); if (has_focus() || caret_force_displayed) { if (p_enabled) { - if (caret_blink_timer->is_stopped()) { - caret_blink_timer->start(); + if (!caret_blinking) { + caret_blinking = true; + caret_blink_timer = 0.0; } } else { - caret_blink_timer->stop(); + caret_blinking = false; } } @@ -1345,20 +1359,19 @@ void LineEdit::set_caret_force_displayed(const bool p_enabled) { } float LineEdit::get_caret_blink_speed() const { - return caret_blink_timer->get_wait_time(); + return caret_blink_speed; } void LineEdit::set_caret_blink_speed(const float p_speed) { ERR_FAIL_COND(p_speed <= 0); - caret_blink_timer->set_wait_time(p_speed); + caret_blink_speed = p_speed; } void LineEdit::_reset_caret_blink_timer() { if (caret_blink_enabled) { draw_caret = true; if (has_focus()) { - caret_blink_timer->stop(); - caret_blink_timer->start(); + caret_blink_timer = 0.0; update(); } } @@ -1438,29 +1451,6 @@ Control::TextDirection LineEdit::get_text_direction() const { return text_direction; } -void LineEdit::clear_opentype_features() { - opentype_features.clear(); - _shape(); - update(); -} - -void LineEdit::set_opentype_feature(const String &p_name, int p_value) { - int32_t tag = TS->name_to_tag(p_name); - if (!opentype_features.has(tag) || (int)opentype_features[tag] != p_value) { - opentype_features[tag] = p_value; - _shape(); - update(); - } -} - -int LineEdit::get_opentype_feature(const String &p_name) const { - int32_t tag = TS->name_to_tag(p_name); - if (!opentype_features.has(tag)) { - return -1; - } - return opentype_features[tag]; -} - void LineEdit::set_language(const String &p_language) { if (language != p_language) { language = p_language; @@ -1675,7 +1665,7 @@ Size2 LineEdit::get_minimum_size() const { Size2 min_size; // Minimum size of text. - float em_space_size = font->get_char_size('M', 0, font_size).x; + float em_space_size = font->get_char_size('M', font_size).x; min_size.width = get_theme_constant(SNAME("minimum_character_width")) * em_space_size; if (expand_to_text_length) { @@ -1683,7 +1673,7 @@ Size2 LineEdit::get_minimum_size() const { min_size.width = MAX(min_size.width, full_width + em_space_size); } - min_size.height = MAX(TS->shaped_text_get_size(text_rid).y + font->get_spacing(TextServer::SPACING_TOP) + font->get_spacing(TextServer::SPACING_BOTTOM), font->get_height(font_size)); + min_size.height = MAX(TS->shaped_text_get_size(text_rid).y, font->get_height(font_size)); // Take icons into account. int icon_max_width = 0; @@ -2142,7 +2132,10 @@ void LineEdit::_shape() { const Ref<Font> &font = get_theme_font(SNAME("font")); int font_size = get_theme_font_size(SNAME("font_size")); ERR_FAIL_COND(font.is_null()); - TS->shaped_text_add_string(text_rid, t, font->get_rids(), font_size, opentype_features, (!language.is_empty()) ? language : TranslationServer::get_singleton()->get_tool_locale()); + 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))); + } TS->shaped_text_set_bidi_override(text_rid, structured_text_parser(st_parser, st_args, t)); full_width = TS->shaped_text_get_size(text_rid).x; @@ -2223,56 +2216,6 @@ Key LineEdit::_get_menu_action_accelerator(const String &p_action) { } } -bool LineEdit::_set(const StringName &p_name, const Variant &p_value) { - String str = p_name; - if (str.begins_with("opentype_features/")) { - String name = str.get_slicec('/', 1); - int32_t tag = TS->name_to_tag(name); - int value = p_value; - if (value == -1) { - if (opentype_features.has(tag)) { - opentype_features.erase(tag); - _shape(); - update(); - } - } else { - if (!opentype_features.has(tag) || (int)opentype_features[tag] != value) { - opentype_features[tag] = value; - _shape(); - update(); - } - } - notify_property_list_changed(); - return true; - } - - return false; -} - -bool LineEdit::_get(const StringName &p_name, Variant &r_ret) const { - String str = p_name; - if (str.begins_with("opentype_features/")) { - String name = str.get_slicec('/', 1); - int32_t tag = TS->name_to_tag(name); - if (opentype_features.has(tag)) { - r_ret = opentype_features[tag]; - return true; - } else { - r_ret = -1; - return true; - } - } - return false; -} - -void LineEdit::_get_property_list(List<PropertyInfo> *p_list) const { - for (const Variant *ftr = opentype_features.next(nullptr); ftr != nullptr; ftr = opentype_features.next(ftr)) { - String name = TS->tag_to_name(*ftr); - p_list->push_back(PropertyInfo(Variant::INT, "opentype_features/" + name)); - } - p_list->push_back(PropertyInfo(Variant::NIL, "opentype_features/_new", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_EDITOR)); -} - void LineEdit::_validate_property(PropertyInfo &property) const { if (!caret_blink_enabled && property.name == "caret_blink_speed") { property.usage = PROPERTY_USAGE_NO_EDITOR; @@ -2298,9 +2241,6 @@ void LineEdit::_bind_methods() { ClassDB::bind_method(D_METHOD("set_draw_control_chars", "enable"), &LineEdit::set_draw_control_chars); ClassDB::bind_method(D_METHOD("set_text_direction", "direction"), &LineEdit::set_text_direction); ClassDB::bind_method(D_METHOD("get_text_direction"), &LineEdit::get_text_direction); - ClassDB::bind_method(D_METHOD("set_opentype_feature", "tag", "value"), &LineEdit::set_opentype_feature); - ClassDB::bind_method(D_METHOD("get_opentype_feature", "tag"), &LineEdit::get_opentype_feature); - ClassDB::bind_method(D_METHOD("clear_opentype_features"), &LineEdit::clear_opentype_features); ClassDB::bind_method(D_METHOD("set_language", "language"), &LineEdit::set_language); ClassDB::bind_method(D_METHOD("get_language"), &LineEdit::get_language); ClassDB::bind_method(D_METHOD("set_structured_text_bidi_override", "parser"), &LineEdit::set_structured_text_bidi_override); @@ -2406,18 +2346,20 @@ void LineEdit::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::BOOL, "deselect_on_focus_loss_enabled"), "set_deselect_on_focus_loss_enabled", "is_deselect_on_focus_loss_enabled"); ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "right_icon", PROPERTY_HINT_RESOURCE_TYPE, "Texture"), "set_right_icon", "get_right_icon"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "flat"), "set_flat", "is_flat"); - ADD_PROPERTY(PropertyInfo(Variant::INT, "text_direction", PROPERTY_HINT_ENUM, "Auto,Left-to-Right,Right-to-Left,Inherited"), "set_text_direction", "get_text_direction"); - ADD_PROPERTY(PropertyInfo(Variant::STRING, "language", PROPERTY_HINT_LOCALE_ID, ""), "set_language", "get_language"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "draw_control_chars"), "set_draw_control_chars", "get_draw_control_chars"); - ADD_GROUP("Structured Text", "structured_text_"); - ADD_PROPERTY(PropertyInfo(Variant::INT, "structured_text_bidi_override", PROPERTY_HINT_ENUM, "Default,URI,File,Email,List,None,Custom"), "set_structured_text_bidi_override", "get_structured_text_bidi_override"); - ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "structured_text_bidi_override_options"), "set_structured_text_bidi_override_options", "get_structured_text_bidi_override_options"); + ADD_GROUP("Caret", "caret_"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "caret_blink"), "set_caret_blink_enabled", "is_caret_blink_enabled"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "caret_blink_speed", PROPERTY_HINT_RANGE, "0.1,10,0.01"), "set_caret_blink_speed", "get_caret_blink_speed"); ADD_PROPERTY(PropertyInfo(Variant::INT, "caret_column", PROPERTY_HINT_RANGE, "0,1000,1,or_greater"), "set_caret_column", "get_caret_column"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "caret_force_displayed"), "set_caret_force_displayed", "is_caret_force_displayed"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "caret_mid_grapheme"), "set_caret_mid_grapheme_enabled", "is_caret_mid_grapheme_enabled"); + + ADD_GROUP("BiDi", ""); + ADD_PROPERTY(PropertyInfo(Variant::INT, "text_direction", PROPERTY_HINT_ENUM, "Auto,Left-to-Right,Right-to-Left,Inherited"), "set_text_direction", "get_text_direction"); + ADD_PROPERTY(PropertyInfo(Variant::STRING, "language", PROPERTY_HINT_LOCALE_ID, ""), "set_language", "get_language"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "structured_text_bidi_override", PROPERTY_HINT_ENUM, "Default,URI,File,Email,List,None,Custom"), "set_structured_text_bidi_override", "get_structured_text_bidi_override"); + ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "structured_text_bidi_override_options"), "set_structured_text_bidi_override_options", "get_structured_text_bidi_override_options"); } void LineEdit::_ensure_menu() { @@ -2508,10 +2450,6 @@ LineEdit::LineEdit(const String &p_placeholder) { set_mouse_filter(MOUSE_FILTER_STOP); set_process_unhandled_key_input(true); - caret_blink_timer = memnew(Timer); - add_child(caret_blink_timer, false, INTERNAL_MODE_FRONT); - caret_blink_timer->set_wait_time(0.65); - caret_blink_timer->connect("timeout", callable_mp(this, &LineEdit::_toggle_draw_caret)); set_caret_blink_enabled(false); set_placeholder(p_placeholder); diff --git a/scene/gui/line_edit.h b/scene/gui/line_edit.h index 0fb178fca4..6aa1694f1f 100644 --- a/scene/gui/line_edit.h +++ b/scene/gui/line_edit.h @@ -105,7 +105,6 @@ private: int scroll_offset = 0; int max_length = 0; // 0 for no maximum. - Dictionary opentype_features; String language; TextDirection text_direction = TEXT_DIRECTION_AUTO; TextDirection input_direction = TEXT_DIRECTION_LTR; @@ -160,7 +159,9 @@ private: bool caret_blink_enabled = false; bool caret_force_displayed = false; bool draw_caret = true; - Timer *caret_blink_timer = nullptr; + float caret_blink_speed = 0.65; + double caret_blink_timer = 0.0; + bool caret_blinking = false; bool _is_over_clear_button(const Point2 &p_pos) const; @@ -208,9 +209,6 @@ protected: virtual void unhandled_key_input(const Ref<InputEvent> &p_event) override; virtual void gui_input(const Ref<InputEvent> &p_event) override; - bool _set(const StringName &p_name, const Variant &p_value); - bool _get(const StringName &p_name, Variant &r_ret) const; - void _get_property_list(List<PropertyInfo> *p_list) const; void _validate_property(PropertyInfo &property) const override; public: @@ -246,10 +244,6 @@ public: void set_text_direction(TextDirection p_text_direction); TextDirection get_text_direction() const; - void set_opentype_feature(const String &p_name, int p_value); - int get_opentype_feature(const String &p_name) const; - void clear_opentype_features(); - void set_language(const String &p_language); String get_language() const; @@ -342,4 +336,4 @@ public: VARIANT_ENUM_CAST(LineEdit::MenuItems); -#endif +#endif // LINE_EDIT_H diff --git a/scene/gui/link_button.cpp b/scene/gui/link_button.cpp index dca6437519..30c0bb3321 100644 --- a/scene/gui/link_button.cpp +++ b/scene/gui/link_button.cpp @@ -43,7 +43,7 @@ void LinkButton::_shape() { text_buf->set_direction((TextServer::Direction)text_direction); } TS->shaped_text_set_bidi_override(text_buf->get_rid(), structured_text_parser(st_parser, st_args, xl_text)); - text_buf->add_string(xl_text, font, font_size, opentype_features, (!language.is_empty()) ? language : TranslationServer::get_singleton()->get_tool_locale()); + text_buf->add_string(xl_text, font, font_size, language); } void LinkButton::set_text(const String &p_text) { @@ -96,29 +96,6 @@ Control::TextDirection LinkButton::get_text_direction() const { return text_direction; } -void LinkButton::clear_opentype_features() { - opentype_features.clear(); - _shape(); - update(); -} - -void LinkButton::set_opentype_feature(const String &p_name, int p_value) { - int32_t tag = TS->name_to_tag(p_name); - if (!opentype_features.has(tag) || (int)opentype_features[tag] != p_value) { - opentype_features[tag] = p_value; - _shape(); - update(); - } -} - -int LinkButton::get_opentype_feature(const String &p_name) const { - int32_t tag = TS->name_to_tag(p_name); - if (!opentype_features.has(tag)) { - return -1; - } - return opentype_features[tag]; -} - void LinkButton::set_language(const String &p_language) { if (language != p_language) { language = p_language; @@ -237,64 +214,11 @@ void LinkButton::_notification(int p_what) { } } -bool LinkButton::_set(const StringName &p_name, const Variant &p_value) { - String str = p_name; - if (str.begins_with("opentype_features/")) { - String name = str.get_slicec('/', 1); - int32_t tag = TS->name_to_tag(name); - int value = p_value; - if (value == -1) { - if (opentype_features.has(tag)) { - opentype_features.erase(tag); - _shape(); - update(); - } - } else { - if (!opentype_features.has(tag) || (int)opentype_features[tag] != value) { - opentype_features[tag] = value; - _shape(); - update(); - } - } - notify_property_list_changed(); - return true; - } - - return false; -} - -bool LinkButton::_get(const StringName &p_name, Variant &r_ret) const { - String str = p_name; - if (str.begins_with("opentype_features/")) { - String name = str.get_slicec('/', 1); - int32_t tag = TS->name_to_tag(name); - if (opentype_features.has(tag)) { - r_ret = opentype_features[tag]; - return true; - } else { - r_ret = -1; - return true; - } - } - return false; -} - -void LinkButton::_get_property_list(List<PropertyInfo> *p_list) const { - for (const Variant *ftr = opentype_features.next(nullptr); ftr != nullptr; ftr = opentype_features.next(ftr)) { - String name = TS->tag_to_name(*ftr); - p_list->push_back(PropertyInfo(Variant::INT, "opentype_features/" + name)); - } - p_list->push_back(PropertyInfo(Variant::NIL, "opentype_features/_new", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_EDITOR)); -} - void LinkButton::_bind_methods() { ClassDB::bind_method(D_METHOD("set_text", "text"), &LinkButton::set_text); ClassDB::bind_method(D_METHOD("get_text"), &LinkButton::get_text); ClassDB::bind_method(D_METHOD("set_text_direction", "direction"), &LinkButton::set_text_direction); ClassDB::bind_method(D_METHOD("get_text_direction"), &LinkButton::get_text_direction); - ClassDB::bind_method(D_METHOD("set_opentype_feature", "tag", "value"), &LinkButton::set_opentype_feature); - ClassDB::bind_method(D_METHOD("get_opentype_feature", "tag"), &LinkButton::get_opentype_feature); - ClassDB::bind_method(D_METHOD("clear_opentype_features"), &LinkButton::clear_opentype_features); ClassDB::bind_method(D_METHOD("set_language", "language"), &LinkButton::set_language); ClassDB::bind_method(D_METHOD("get_language"), &LinkButton::get_language); ClassDB::bind_method(D_METHOD("set_underline_mode", "underline_mode"), &LinkButton::set_underline_mode); @@ -309,10 +233,11 @@ void LinkButton::_bind_methods() { BIND_ENUM_CONSTANT(UNDERLINE_MODE_NEVER); ADD_PROPERTY(PropertyInfo(Variant::STRING, "text"), "set_text", "get_text"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "underline", PROPERTY_HINT_ENUM, "Always,On Hover,Never"), "set_underline_mode", "get_underline_mode"); + + ADD_GROUP("BiDi", ""); ADD_PROPERTY(PropertyInfo(Variant::INT, "text_direction", PROPERTY_HINT_ENUM, "Auto,Left-to-Right,Right-to-Left,Inherited"), "set_text_direction", "get_text_direction"); ADD_PROPERTY(PropertyInfo(Variant::STRING, "language", PROPERTY_HINT_LOCALE_ID, ""), "set_language", "get_language"); - ADD_PROPERTY(PropertyInfo(Variant::INT, "underline", PROPERTY_HINT_ENUM, "Always,On Hover,Never"), "set_underline_mode", "get_underline_mode"); - ADD_GROUP("Structured Text", "structured_text_"); ADD_PROPERTY(PropertyInfo(Variant::INT, "structured_text_bidi_override", PROPERTY_HINT_ENUM, "Default,URI,File,Email,List,None,Custom"), "set_structured_text_bidi_override", "get_structured_text_bidi_override"); ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "structured_text_bidi_override_options"), "set_structured_text_bidi_override_options", "get_structured_text_bidi_override_options"); } diff --git a/scene/gui/link_button.h b/scene/gui/link_button.h index 6d2dcbde84..12a6a7618f 100644 --- a/scene/gui/link_button.h +++ b/scene/gui/link_button.h @@ -28,8 +28,8 @@ /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /*************************************************************************/ -#ifndef LINKBUTTON_H -#define LINKBUTTON_H +#ifndef LINK_BUTTON_H +#define LINK_BUTTON_H #include "scene/gui/base_button.h" #include "scene/resources/text_line.h" @@ -50,7 +50,6 @@ private: Ref<TextLine> text_buf; UnderlineMode underline_mode = UNDERLINE_MODE_ALWAYS; - Dictionary opentype_features; String language; TextDirection text_direction = TEXT_DIRECTION_AUTO; TextServer::StructuredTextParser st_parser = TextServer::STRUCTURED_TEXT_DEFAULT; @@ -63,10 +62,6 @@ protected: void _notification(int p_what); static void _bind_methods(); - bool _set(const StringName &p_name, const Variant &p_value); - bool _get(const StringName &p_name, Variant &r_ret) const; - void _get_property_list(List<PropertyInfo> *p_list) const; - public: void set_text(const String &p_text); String get_text() const; @@ -80,10 +75,6 @@ public: void set_text_direction(TextDirection p_text_direction); TextDirection get_text_direction() const; - void set_opentype_feature(const String &p_name, int p_value); - int get_opentype_feature(const String &p_name) const; - void clear_opentype_features(); - void set_language(const String &p_language); String get_language() const; @@ -95,4 +86,4 @@ public: VARIANT_ENUM_CAST(LinkButton::UnderlineMode); -#endif // LINKBUTTON_H +#endif // LINK_BUTTON_H diff --git a/scene/gui/menu_button.cpp b/scene/gui/menu_button.cpp index 316fee53fe..069a31d9d2 100644 --- a/scene/gui/menu_button.cpp +++ b/scene/gui/menu_button.cpp @@ -239,8 +239,8 @@ MenuButton::MenuButton(const String &p_text) : popup = memnew(PopupMenu); popup->hide(); add_child(popup, false, INTERNAL_MODE_FRONT); - popup->connect("about_to_popup", callable_mp(this, &MenuButton::_popup_visibility_changed), varray(true)); - popup->connect("popup_hide", callable_mp(this, &MenuButton::_popup_visibility_changed), varray(false)); + popup->connect("about_to_popup", callable_mp(this, &MenuButton::_popup_visibility_changed).bind(true)); + popup->connect("popup_hide", callable_mp(this, &MenuButton::_popup_visibility_changed).bind(false)); } MenuButton::~MenuButton() { diff --git a/scene/gui/menu_button.h b/scene/gui/menu_button.h index 0a6b46c796..97c0d21f1e 100644 --- a/scene/gui/menu_button.h +++ b/scene/gui/menu_button.h @@ -71,4 +71,4 @@ public: ~MenuButton(); }; -#endif +#endif // MENU_BUTTON_H diff --git a/scene/gui/option_button.cpp b/scene/gui/option_button.cpp index a86f2bdbc1..a10ec1db06 100644 --- a/scene/gui/option_button.cpp +++ b/scene/gui/option_button.cpp @@ -481,7 +481,7 @@ OptionButton::OptionButton(const String &p_text) : add_child(popup, false, INTERNAL_MODE_FRONT); popup->connect("index_pressed", callable_mp(this, &OptionButton::_selected)); popup->connect("id_focused", callable_mp(this, &OptionButton::_focused)); - popup->connect("popup_hide", callable_mp((BaseButton *)this, &BaseButton::set_pressed), varray(false)); + popup->connect("popup_hide", callable_mp((BaseButton *)this, &BaseButton::set_pressed).bind(false)); } OptionButton::~OptionButton() { diff --git a/scene/gui/option_button.h b/scene/gui/option_button.h index 7896132626..5665296699 100644 --- a/scene/gui/option_button.h +++ b/scene/gui/option_button.h @@ -105,4 +105,4 @@ public: ~OptionButton(); }; -#endif +#endif // OPTION_BUTTON_H diff --git a/scene/gui/popup.h b/scene/gui/popup.h index b53c8be50f..70eb8722d0 100644 --- a/scene/gui/popup.h +++ b/scene/gui/popup.h @@ -79,4 +79,4 @@ public: PopupPanel(); }; -#endif +#endif // POPUP_H diff --git a/scene/gui/popup_menu.cpp b/scene/gui/popup_menu.cpp index 5931c112eb..928bab8842 100644 --- a/scene/gui/popup_menu.cpp +++ b/scene/gui/popup_menu.cpp @@ -760,11 +760,11 @@ void PopupMenu::_shape_item(int p_item) { } else { items.write[p_item].text_buf->set_direction((TextServer::Direction)items[p_item].text_direction); } - items.write[p_item].text_buf->add_string(items.write[p_item].xl_text, font, font_size, items[p_item].opentype_features, !items[p_item].language.is_empty() ? items[p_item].language : TranslationServer::get_singleton()->get_tool_locale()); + items.write[p_item].text_buf->add_string(items.write[p_item].xl_text, font, font_size, items[p_item].language); items.write[p_item].accel_text_buf->clear(); items.write[p_item].accel_text_buf->set_direction(is_layout_rtl() ? TextServer::DIRECTION_RTL : TextServer::DIRECTION_LTR); - items.write[p_item].accel_text_buf->add_string(_get_accel_text(items.write[p_item]), font, font_size, Dictionary(), TranslationServer::get_singleton()->get_tool_locale()); + items.write[p_item].accel_text_buf->add_string(_get_accel_text(items.write[p_item]), font, font_size); items.write[p_item].dirty = false; } } @@ -1067,29 +1067,6 @@ void PopupMenu::set_item_text_direction(int p_item, Control::TextDirection p_tex } } -void PopupMenu::clear_item_opentype_features(int p_item) { - if (p_item < 0) { - p_item += get_item_count(); - } - ERR_FAIL_INDEX(p_item, items.size()); - items.write[p_item].opentype_features.clear(); - items.write[p_item].dirty = true; - control->update(); -} - -void PopupMenu::set_item_opentype_feature(int p_item, const String &p_name, int p_value) { - if (p_item < 0) { - p_item += get_item_count(); - } - ERR_FAIL_INDEX(p_item, items.size()); - int32_t tag = TS->name_to_tag(p_name); - if (!items[p_item].opentype_features.has(tag) || (int)items[p_item].opentype_features[tag] != p_value) { - items.write[p_item].opentype_features[tag] = p_value; - items.write[p_item].dirty = true; - control->update(); - } -} - void PopupMenu::set_item_language(int p_item, const String &p_language) { if (p_item < 0) { p_item += get_item_count(); @@ -1195,15 +1172,6 @@ Control::TextDirection PopupMenu::get_item_text_direction(int p_item) const { return items[p_item].text_direction; } -int PopupMenu::get_item_opentype_feature(int p_item, const String &p_name) const { - ERR_FAIL_INDEX_V(p_item, items.size(), -1); - int32_t tag = TS->name_to_tag(p_name); - if (!items[p_item].opentype_features.has(tag)) { - return -1; - } - return items[p_item].opentype_features[tag]; -} - String PopupMenu::get_item_language(int p_item) const { ERR_FAIL_INDEX_V(p_item, items.size(), ""); return items[p_item].language; @@ -1853,7 +1821,6 @@ void PopupMenu::_bind_methods() { ClassDB::bind_method(D_METHOD("set_item_text", "index", "text"), &PopupMenu::set_item_text); ClassDB::bind_method(D_METHOD("set_item_text_direction", "index", "direction"), &PopupMenu::set_item_text_direction); - ClassDB::bind_method(D_METHOD("set_item_opentype_feature", "index", "tag", "value"), &PopupMenu::set_item_opentype_feature); ClassDB::bind_method(D_METHOD("set_item_language", "index", "language"), &PopupMenu::set_item_language); ClassDB::bind_method(D_METHOD("set_item_icon", "index", "icon"), &PopupMenu::set_item_icon); ClassDB::bind_method(D_METHOD("set_item_checked", "index", "checked"), &PopupMenu::set_item_checked); @@ -1876,8 +1843,6 @@ void PopupMenu::_bind_methods() { ClassDB::bind_method(D_METHOD("get_item_text", "index"), &PopupMenu::get_item_text); ClassDB::bind_method(D_METHOD("get_item_text_direction", "index"), &PopupMenu::get_item_text_direction); - ClassDB::bind_method(D_METHOD("get_item_opentype_feature", "index", "tag"), &PopupMenu::get_item_opentype_feature); - ClassDB::bind_method(D_METHOD("clear_item_opentype_features", "index"), &PopupMenu::clear_item_opentype_features); ClassDB::bind_method(D_METHOD("get_item_language", "index"), &PopupMenu::get_item_language); ClassDB::bind_method(D_METHOD("get_item_icon", "index"), &PopupMenu::get_item_icon); ClassDB::bind_method(D_METHOD("is_item_checked", "index"), &PopupMenu::is_item_checked); @@ -1944,7 +1909,7 @@ void PopupMenu::popup(const Rect2 &p_bounds) { PopupMenu::PopupMenu() { // Margin Container margin_container = memnew(MarginContainer); - margin_container->set_anchors_and_offsets_preset(Control::PRESET_WIDE); + margin_container->set_anchors_and_offsets_preset(Control::PRESET_FULL_RECT); add_child(margin_container, false, INTERNAL_MODE_FRONT); margin_container->connect("draw", callable_mp(this, &PopupMenu::_draw_background)); @@ -1956,7 +1921,7 @@ PopupMenu::PopupMenu() { // The control which will display the items control = memnew(Control); control->set_clip_contents(false); - control->set_anchors_and_offsets_preset(Control::PRESET_WIDE); + control->set_anchors_and_offsets_preset(Control::PRESET_FULL_RECT); control->set_h_size_flags(Control::SIZE_EXPAND_FILL); control->set_v_size_flags(Control::SIZE_EXPAND_FILL); scroll_container->add_child(control, false, INTERNAL_MODE_FRONT); diff --git a/scene/gui/popup_menu.h b/scene/gui/popup_menu.h index 8218c6122e..e203793c2e 100644 --- a/scene/gui/popup_menu.h +++ b/scene/gui/popup_menu.h @@ -47,7 +47,6 @@ class PopupMenu : public Popup { Ref<TextLine> text_buf; Ref<TextLine> accel_text_buf; - Dictionary opentype_features; String language; Control::TextDirection text_direction = Control::TEXT_DIRECTION_AUTO; @@ -171,8 +170,6 @@ public: void set_item_text(int p_idx, const String &p_text); void set_item_text_direction(int p_idx, Control::TextDirection p_text_direction); - void set_item_opentype_feature(int p_idx, const String &p_name, int p_value); - void clear_item_opentype_features(int p_idx); void set_item_language(int p_idx, const String &p_language); void set_item_icon(int p_idx, const Ref<Texture2D> &p_icon); void set_item_checked(int p_idx, bool p_checked); @@ -195,7 +192,6 @@ public: String get_item_text(int p_idx) const; Control::TextDirection get_item_text_direction(int p_idx) const; - int get_item_opentype_feature(int p_idx, const String &p_name) const; String get_item_language(int p_idx) const; int get_item_idx_from_text(const String &text) const; Ref<Texture2D> get_item_icon(int p_idx) const; @@ -264,4 +260,4 @@ public: ~PopupMenu(); }; -#endif +#endif // POPUP_MENU_H diff --git a/scene/gui/range.h b/scene/gui/range.h index 1274821bd1..87bd0d88af 100644 --- a/scene/gui/range.h +++ b/scene/gui/range.h @@ -106,4 +106,4 @@ public: ~Range(); }; -#endif +#endif // RANGE_H diff --git a/scene/gui/rich_text_label.cpp b/scene/gui/rich_text_label.cpp index 0516c8e722..8e424977c4 100644 --- a/scene/gui/rich_text_label.cpp +++ b/scene/gui/rich_text_label.cpp @@ -149,7 +149,12 @@ RichTextLabel::Item *RichTextLabel::_get_item_at_pos(RichTextLabel::Item *p_item return it; } } break; - case ITEM_NEWLINE: + case ITEM_NEWLINE: { + offset += 1; + if (offset == p_position) { + return it; + } + } break; case ITEM_IMAGE: case ITEM_TABLE: { offset += 1; @@ -219,16 +224,26 @@ void RichTextLabel::_update_line_font(ItemFrame *p_frame, int p_line, const Ref< for (int i = 0; i < spans; i++) { ItemText *it = reinterpret_cast<ItemText *>((uint64_t)TS->shaped_get_span_meta(t, i)); if (it) { - Ref<Font> font = _find_font(it); - if (font.is_null()) { - font = p_base_font; + Ref<Font> font = p_base_font; + int font_size = p_base_font_size; + + ItemFont *font_it = _find_font(it); + if (font_it) { + if (font_it->font.is_valid()) { + font = font_it->font; + } + if (font_it->font_size > 0) { + font_size = font_it->font_size; + } + } + ItemFontSize *font_size_it = _find_font_size(it); + if (font_size_it && font_size_it->font_size > 0) { + font_size = font_size_it->font_size; } - int font_size = _find_font_size(it); - if (font_size == -1) { - font_size = p_base_font_size; + TS->shaped_set_span_update_font(t, i, font->get_rids(), font_size, font->get_opentype_features()); + for (int j = 0; j < TextServer::SPACING_MAX; j++) { + TS->shaped_text_set_spacing(t, TextServer::SpacingType(j), font->get_spacing(TextServer::SpacingType(j))); } - Dictionary font_ftr = _find_font_features(it); - TS->shaped_set_span_update_font(t, i, font->get_rids(), font_size, font_ftr); } } @@ -263,7 +278,7 @@ float RichTextLabel::_resize_line(ItemFrame *p_frame, int p_line, const Ref<Font if (tab_size > 0) { // Align inline tabs. Vector<float> tabs; - tabs.push_back(tab_size * p_base_font->get_char_size(' ', 0, p_base_font_size).width); + tabs.push_back(tab_size * p_base_font->get_char_size(' ', p_base_font_size).width); l.text_buf->tab_align(tabs); } @@ -424,10 +439,10 @@ float RichTextLabel::_shape_line(ItemFrame *p_frame, int p_line, const Ref<Font> Line &l = p_frame->lines[p_line]; MutexLock lock(l.text_buf->get_mutex()); - uint16_t autowrap_flags = TextServer::BREAK_MANDATORY; + BitField<TextServer::LineBreakFlag> autowrap_flags = TextServer::BREAK_MANDATORY; switch (autowrap_mode) { case TextServer::AUTOWRAP_WORD_SMART: - autowrap_flags = TextServer::BREAK_WORD_BOUND_ADAPTIVE | TextServer::BREAK_MANDATORY; + autowrap_flags = TextServer::BREAK_WORD_BOUND | TextServer::BREAK_ADAPTIVE | TextServer::BREAK_MANDATORY; break; case TextServer::AUTOWRAP_WORD: autowrap_flags = TextServer::BREAK_WORD_BOUND | TextServer::BREAK_MANDATORY; @@ -441,7 +456,8 @@ float RichTextLabel::_shape_line(ItemFrame *p_frame, int p_line, const Ref<Font> // Clear cache. l.text_buf->clear(); - l.text_buf->set_flags(autowrap_flags | TextServer::JUSTIFICATION_KASHIDA | TextServer::JUSTIFICATION_WORD_BOUND | TextServer::JUSTIFICATION_TRIM_EDGE_SPACES); + l.text_buf->set_break_flags(autowrap_flags); + l.text_buf->set_justification_flags(TextServer::JUSTIFICATION_KASHIDA | TextServer::JUSTIFICATION_WORD_BOUND | TextServer::JUSTIFICATION_TRIM_EDGE_SPACES); l.char_offset = *r_char_offset; l.char_count = 0; @@ -453,7 +469,7 @@ float RichTextLabel::_shape_line(ItemFrame *p_frame, int p_line, const Ref<Font> if (tab_size > 0) { // Align inline tabs. Vector<float> tabs; - tabs.push_back(tab_size * p_base_font->get_char_size(' ', 0, p_base_font_size).width); + tabs.push_back(tab_size * p_base_font->get_char_size(' ', p_base_font_size).width); l.text_buf->tab_align(tabs); } @@ -475,30 +491,45 @@ float RichTextLabel::_shape_line(ItemFrame *p_frame, int p_line, const Ref<Font> l.dc_ol_color = dc->ol_color; } break; case ITEM_NEWLINE: { - Ref<Font> font = _find_font(it); - if (font.is_null()) { - font = p_base_font; + Ref<Font> font = p_base_font; + int font_size = p_base_font_size; + + ItemFont *font_it = _find_font(it); + if (font_it) { + if (font_it->font.is_valid()) { + font = font_it->font; + } + if (font_it->font_size > 0) { + font_size = font_it->font_size; + } } - int font_size = _find_font_size(it); - if (font_size == -1) { - font_size = p_base_font_size; + ItemFontSize *font_size_it = _find_font_size(it); + if (font_size_it && font_size_it->font_size > 0) { + font_size = font_size_it->font_size; } - l.text_buf->add_string("\n", font, font_size, Dictionary(), ""); + l.text_buf->add_string("\n", font, font_size); text += "\n"; l.char_count++; remaining_characters--; } break; case ITEM_TEXT: { ItemText *t = static_cast<ItemText *>(it); - Ref<Font> font = _find_font(it); - if (font.is_null()) { - font = p_base_font; + Ref<Font> font = p_base_font; + int font_size = p_base_font_size; + + ItemFont *font_it = _find_font(it); + if (font_it) { + if (font_it->font.is_valid()) { + font = font_it->font; + } + if (font_it->font_size > 0) { + font_size = font_it->font_size; + } } - int font_size = _find_font_size(it); - if (font_size == -1) { - font_size = p_base_font_size; + ItemFontSize *font_size_it = _find_font_size(it); + if (font_size_it && font_size_it->font_size > 0) { + font_size = font_size_it->font_size; } - Dictionary font_ftr = _find_font_features(it); String lang = _find_language(it); String tx = t->text; if (visible_chars_behavior == TextServer::VC_CHARS_BEFORE_SHAPING && visible_characters >= 0 && remaining_characters >= 0) { @@ -506,7 +537,7 @@ 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, font_ftr, lang, (uint64_t)it); + l.text_buf->add_string(tx, font, font_size, lang, (uint64_t)it); text += tx; l.char_count += tx.length(); } break; @@ -743,13 +774,21 @@ int RichTextLabel::_draw_line(ItemFrame *p_frame, int p_line, const Vector2 &p_o } } if (!prefix.is_empty()) { - Ref<Font> font = _find_font(l.from); - if (font.is_null()) { - font = get_theme_font(SNAME("normal_font")); + Ref<Font> font = get_theme_font(SNAME("normal_font")); + int font_size = get_theme_font_size(SNAME("normal_font_size")); + + ItemFont *font_it = _find_font(l.from); + if (font_it) { + if (font_it->font.is_valid()) { + font = font_it->font; + } + if (font_it->font_size > 0) { + font_size = font_it->font_size; + } } - int font_size = _find_font_size(l.from); - if (font_size == -1) { - font_size = get_theme_font_size(SNAME("normal_font_size")); + ItemFontSize *font_size_it = _find_font_size(l.from); + if (font_size_it && font_size_it->font_size > 0) { + font_size = font_size_it->font_size; } if (rtl) { float offx = 0.0f; @@ -837,7 +876,7 @@ int RichTextLabel::_draw_line(ItemFrame *p_frame, int p_line, const Vector2 &p_o //draw_rect(Rect2(p_ofs + off, TS->shaped_text_get_size(rid)), Color(1,0,0), false, 2); //DEBUG_RECTS - off.y += TS->shaped_text_get_ascent(rid) + l.text_buf->get_spacing_top(); + off.y += TS->shaped_text_get_ascent(rid); // Draw inlined objects. Array objects = TS->shaped_text_get_objects(rid); for (int i = 0; i < objects.size(); i++) { @@ -1299,7 +1338,7 @@ int RichTextLabel::_draw_line(ItemFrame *p_frame, int p_line, const Vector2 &p_o // Draw foreground color box _draw_fbg_boxes(ci, rid, fbg_line_off, it_from, it_to, chr_range.x, chr_range.y, 1); - off.y += TS->shaped_text_get_descent(rid) + l.text_buf->get_spacing_bottom(); + off.y += TS->shaped_text_get_descent(rid); } return line_count; @@ -1343,6 +1382,8 @@ void RichTextLabel::_find_click(ItemFrame *p_frame, const Point2i &p_click, Item float RichTextLabel::_find_click_in_line(ItemFrame *p_frame, int p_line, const Vector2 &p_ofs, int p_width, const Point2i &p_click, ItemFrame **r_click_frame, int *r_click_line, Item **r_click_item, int *r_click_char, bool p_table) { Vector2 off; + bool line_clicked = false; + float text_rect_begin = 0.0; int char_pos = -1; Line &l = p_frame->lines[p_line]; MutexLock lock(l.text_buf->get_mutex()); @@ -1394,7 +1435,7 @@ float RichTextLabel::_find_click_in_line(ItemFrame *p_frame, int p_line, const V } break; } - off.y += TS->shaped_text_get_ascent(rid) + l.text_buf->get_spacing_top(); + off.y += TS->shaped_text_get_ascent(rid); Array objects = TS->shaped_text_get_objects(rid); for (int i = 0; i < objects.size(); i++) { @@ -1468,7 +1509,11 @@ float RichTextLabel::_find_click_in_line(ItemFrame *p_frame, int p_line, const V } if (p_click.y >= rect.position.y && p_click.y <= rect.position.y + rect.size.y) { - char_pos = TS->shaped_text_hit_test_position(rid, p_click.x - rect.position.x); + if ((!rtl && p_click.x >= rect.position.x) || (rtl && p_click.x <= rect.position.x + rect.size.x)) { + char_pos = TS->shaped_text_hit_test_position(rid, p_click.x - rect.position.x); + } + line_clicked = true; + text_rect_begin = rtl ? rect.position.x + rect.size.x : rect.position.x; } // If table hit was detected, and line hit is in the table bounds use table hit. @@ -1491,27 +1536,49 @@ float RichTextLabel::_find_click_in_line(ItemFrame *p_frame, int p_line, const V return table_offy; } - off.y += TS->shaped_text_get_descent(rid) + l.text_buf->get_spacing_bottom() + get_theme_constant(SNAME("line_separation")); + off.y += TS->shaped_text_get_descent(rid) + get_theme_constant(SNAME("line_separation")); } // Text line hit. - if (char_pos >= 0) { + if (line_clicked) { // Find item. if (r_click_item != nullptr) { Item *it = p_frame->lines[p_line].from; Item *it_to = (p_line + 1 < (int)p_frame->lines.size()) ? p_frame->lines[p_line + 1].from : nullptr; - if (char_pos == p_frame->lines[p_line].char_count) { - // Selection after the end of line, select last item. - if (it_to != nullptr) { - *r_click_item = _get_prev_item(it_to); - } else { - for (Item *i = it; i; i = _get_next_item(i)) { - *r_click_item = i; + if (char_pos >= 0) { + *r_click_item = _get_item_at_pos(it, it_to, char_pos); + } else { + int stop = text_rect_begin; + *r_click_item = _find_indentable(it); + while (*r_click_item) { + Ref<Font> font = get_theme_font(SNAME("normal_font")); + int font_size = get_theme_font_size(SNAME("normal_font_size")); + ItemFont *font_it = _find_font(*r_click_item); + if (font_it) { + if (font_it->font.is_valid()) { + font = font_it->font; + } + if (font_it->font_size > 0) { + font_size = font_it->font_size; + } + } + ItemFontSize *font_size_it = _find_font_size(*r_click_item); + if (font_size_it && font_size_it->font_size > 0) { + font_size = font_size_it->font_size; + } + if (rtl) { + stop += tab_size * font->get_char_size(' ', font_size).width; + if (stop > p_click.x) { + break; + } + } else { + stop -= tab_size * font->get_char_size(' ', font_size).width; + if (stop < p_click.x) { + break; + } } + *r_click_item = _find_indentable((*r_click_item)->parent); } - } else { - // Selection in the line. - *r_click_item = _get_item_at_pos(it, it_to, char_pos); } } @@ -2068,34 +2135,47 @@ void RichTextLabel::_find_frame(Item *p_item, ItemFrame **r_frame, int *r_line) } } -Ref<Font> RichTextLabel::_find_font(Item *p_item) { +RichTextLabel::Item *RichTextLabel::_find_indentable(Item *p_item) { + Item *indentable = p_item; + + while (indentable) { + if (indentable->type == ITEM_INDENT || indentable->type == ITEM_LIST) { + return indentable; + } + indentable = indentable->parent; + } + + return indentable; +} + +RichTextLabel::ItemFont *RichTextLabel::_find_font(Item *p_item) { Item *fontitem = p_item; while (fontitem) { if (fontitem->type == ITEM_FONT) { ItemFont *fi = static_cast<ItemFont *>(fontitem); - return fi->font; + return fi; } fontitem = fontitem->parent; } - return Ref<Font>(); + return nullptr; } -int RichTextLabel::_find_font_size(Item *p_item) { +RichTextLabel::ItemFontSize *RichTextLabel::_find_font_size(Item *p_item) { Item *sizeitem = p_item; while (sizeitem) { if (sizeitem->type == ITEM_FONT_SIZE) { ItemFontSize *fi = static_cast<ItemFontSize *>(sizeitem); - return fi->font_size; + return fi; } sizeitem = sizeitem->parent; } - return -1; + return nullptr; } int RichTextLabel::_find_outline_size(Item *p_item, int p_default) { @@ -2113,21 +2193,6 @@ int RichTextLabel::_find_outline_size(Item *p_item, int p_default) { return p_default; } -Dictionary RichTextLabel::_find_font_features(Item *p_item) { - Item *ffitem = p_item; - - while (ffitem) { - if (ffitem->type == ITEM_FONT_FEATURES) { - ItemFontFeatures *fi = static_cast<ItemFontFeatures *>(ffitem); - return fi->opentype_features; - } - - ffitem = ffitem->parent; - } - - return Dictionary(); -} - RichTextLabel::ItemDropcap *RichTextLabel::_find_dc_item(Item *p_item) { Item *item = p_item; @@ -2196,26 +2261,42 @@ int RichTextLabel::_find_margin(Item *p_item, const Ref<Font> &p_base_font, int while (item) { if (item->type == ITEM_INDENT) { - Ref<Font> font = _find_font(item); - if (font.is_null()) { - font = p_base_font; + Ref<Font> font = p_base_font; + int font_size = p_base_font_size; + + ItemFont *font_it = _find_font(item); + if (font_it) { + if (font_it->font.is_valid()) { + font = font_it->font; + } + if (font_it->font_size > 0) { + font_size = font_it->font_size; + } } - int font_size = _find_font_size(item); - if (font_size == -1) { - font_size = p_base_font_size; + ItemFontSize *font_size_it = _find_font_size(item); + if (font_size_it && font_size_it->font_size > 0) { + font_size = font_size_it->font_size; } - margin += tab_size * font->get_char_size(' ', 0, font_size).width; + margin += tab_size * font->get_char_size(' ', font_size).width; } else if (item->type == ITEM_LIST) { - Ref<Font> font = _find_font(item); - if (font.is_null()) { - font = p_base_font; + Ref<Font> font = p_base_font; + int font_size = p_base_font_size; + + ItemFont *font_it = _find_font(item); + if (font_it) { + if (font_it->font.is_valid()) { + font = font_it->font; + } + if (font_it->font_size > 0) { + font_size = font_it->font_size; + } } - int font_size = _find_font_size(item); - if (font_size == -1) { - font_size = p_base_font_size; + ItemFontSize *font_size_it = _find_font_size(item); + if (font_size_it && font_size_it->font_size > 0) { + font_size = font_size_it->font_size; } - margin += tab_size * font->get_char_size(' ', 0, font_size).width; + margin += tab_size * font->get_char_size(' ', font_size).width; } item = item->parent; @@ -2584,8 +2665,8 @@ void RichTextLabel::_process_line_caches() { MutexLock data_lock(data_mutex); Rect2 text_rect = _get_text_rect(); - Ref<Font> base_font = get_theme_font(SNAME("normal_font")); int base_font_size = get_theme_font_size(SNAME("normal_font_size")); + Ref<Font> base_font = get_theme_font(SNAME("normal_font")); int ctrl_height = get_size().height; int fi = main->first_invalid_line.load(); int total_chars = (fi == 0) ? 0 : (main->lines[fi].char_offset + main->lines[fi].char_count); @@ -2892,7 +2973,7 @@ void RichTextLabel::push_dropcap(const String &p_string, const Ref<Font> &p_font _add_item(item, false); } -void RichTextLabel::push_font(const Ref<Font> &p_font) { +void RichTextLabel::push_font(const Ref<Font> &p_font, int p_size) { _stop_thread(); MutexLock data_lock(data_mutex); @@ -2901,6 +2982,7 @@ void RichTextLabel::push_font(const Ref<Font> &p_font) { ItemFont *item = memnew(ItemFont); item->font = p_font; + item->font_size = p_size; _add_item(item, true); } @@ -2908,35 +2990,35 @@ void RichTextLabel::push_normal() { Ref<Font> normal_font = get_theme_font(SNAME("normal_font")); ERR_FAIL_COND(normal_font.is_null()); - push_font(normal_font); + push_font(normal_font, get_theme_font_size(SNAME("normal_font_size"))); } void RichTextLabel::push_bold() { Ref<Font> bold_font = get_theme_font(SNAME("bold_font")); ERR_FAIL_COND(bold_font.is_null()); - push_font(bold_font); + push_font(bold_font, get_theme_font_size(SNAME("bold_font_size"))); } void RichTextLabel::push_bold_italics() { Ref<Font> bold_italics_font = get_theme_font(SNAME("bold_italics_font")); ERR_FAIL_COND(bold_italics_font.is_null()); - push_font(bold_italics_font); + push_font(bold_italics_font, get_theme_font_size(SNAME("bold_italics_font_size"))); } void RichTextLabel::push_italics() { Ref<Font> italics_font = get_theme_font(SNAME("italics_font")); ERR_FAIL_COND(italics_font.is_null()); - push_font(italics_font); + push_font(italics_font, get_theme_font_size(SNAME("italics_font_size"))); } void RichTextLabel::push_mono() { Ref<Font> mono_font = get_theme_font(SNAME("mono_font")); ERR_FAIL_COND(mono_font.is_null()); - push_font(mono_font); + push_font(mono_font, get_theme_font_size(SNAME("mono_font_size"))); } void RichTextLabel::push_font_size(int p_font_size) { @@ -2950,25 +3032,14 @@ void RichTextLabel::push_font_size(int p_font_size) { _add_item(item, true); } -void RichTextLabel::push_font_features(const Dictionary &p_features) { - _stop_thread(); - MutexLock data_lock(data_mutex); - - ERR_FAIL_COND(current->type == ITEM_TABLE); - ItemFontFeatures *item = memnew(ItemFontFeatures); - - item->opentype_features = p_features; - _add_item(item, true); -} - -void RichTextLabel::push_outline_size(int p_font_size) { +void RichTextLabel::push_outline_size(int p_ol_size) { _stop_thread(); MutexLock data_lock(data_mutex); ERR_FAIL_COND(current->type == ITEM_TABLE); ItemOutlineSize *item = memnew(ItemOutlineSize); - item->outline_size = p_font_size; + item->outline_size = p_ol_size; _add_item(item, true); } @@ -3537,9 +3608,9 @@ void RichTextLabel::append_text(const String &p_bbcode) { //use bold font in_bold = true; if (in_italics) { - push_font(bold_italics_font); + push_font(bold_italics_font, get_theme_font_size(SNAME("bold_italics_font_size"))); } else { - push_font(bold_font); + push_font(bold_font, get_theme_font_size(SNAME("bold_font_size"))); } pos = brk_end + 1; tag_stack.push_front(tag); @@ -3547,15 +3618,15 @@ void RichTextLabel::append_text(const String &p_bbcode) { //use italics font in_italics = true; if (in_bold) { - push_font(bold_italics_font); + push_font(bold_italics_font, get_theme_font_size(SNAME("bold_italics_font_size"))); } else { - push_font(italics_font); + push_font(italics_font, get_theme_font_size(SNAME("italics_font_size"))); } pos = brk_end + 1; tag_stack.push_front(tag); } else if (tag == "code") { //use monospace font - push_font(mono_font); + push_font(mono_font, get_theme_font_size(SNAME("mono_font_size"))); pos = brk_end + 1; tag_stack.push_front(tag); } else if (tag.begins_with("table=")) { @@ -3840,8 +3911,8 @@ void RichTextLabel::append_text(const String &p_bbcode) { tag_stack.push_front("hint"); } else if (tag.begins_with("dropcap")) { Vector<String> subtag = tag.substr(5, tag.length()).split(" "); - Ref<Font> f = get_theme_font(SNAME("normal_font")); int fs = get_theme_font_size(SNAME("normal_font_size")) * 3; + Ref<Font> f = get_theme_font(SNAME("normal_font")); Color color = get_theme_color(SNAME("default_color")); Color outline_color = get_theme_color(SNAME("outline_color")); int outline_size = get_theme_constant(SNAME("outline_size")); @@ -3974,64 +4045,132 @@ void RichTextLabel::append_text(const String &p_bbcode) { pos = brk_end + 1; tag_stack.push_front("outline_color"); - } else if (tag.begins_with("font=")) { - String fnt = tag.substr(5, tag.length()); - - Ref<Font> font = ResourceLoader::load(fnt, "Font"); - if (font.is_valid()) { - push_font(font); - } else { - push_font(normal_font); - } - - pos = brk_end + 1; - tag_stack.push_front("font"); } else if (tag.begins_with("font_size=")) { int fnt_size = tag.substr(10, tag.length()).to_int(); push_font_size(fnt_size); pos = brk_end + 1; tag_stack.push_front("font_size"); + } else if (tag.begins_with("opentype_features=")) { String fnt_ftr = tag.substr(18, tag.length()); Vector<String> subtag = fnt_ftr.split(","); - Dictionary ftrs; - for (int i = 0; i < subtag.size(); i++) { - Vector<String> subtag_a = subtag[i].split("="); - if (subtag_a.size() == 2) { - ftrs[TS->name_to_tag(subtag_a[0])] = subtag_a[1].to_int(); - } else if (subtag_a.size() == 1) { - ftrs[TS->name_to_tag(subtag_a[0])] = 1; + if (subtag.size() > 0) { + Ref<Font> font = normal_font; + int font_size = 0; + ItemFont *font_it = _find_font(current); + if (font_it) { + if (font_it->font.is_valid()) { + font = font_it->font; + } + if (font_it->font_size > 0) { + font_size = font_it->font_size; + } + } + Ref<FontVariation> fc; + fc.instantiate(); + fc->set_base_font(font); + Dictionary features; + for (int i = 0; i < subtag.size(); i++) { + Vector<String> subtag_a = subtag[i].split("="); + if (subtag_a.size() == 2) { + features[TS->name_to_tag(subtag_a[0])] = subtag_a[1].to_int(); + } else if (subtag_a.size() == 1) { + features[TS->name_to_tag(subtag_a[0])] = 1; + } } + fc->set_opentype_features(features); + push_font(fc, font_size); } - push_font_features(ftrs); pos = brk_end + 1; tag_stack.push_front("opentype_features"); + + } else if (tag.begins_with("font=")) { + String fnt = tag.substr(5, tag.length()); + + Ref<Font> fc = ResourceLoader::load(fnt, "Font"); + if (fc.is_valid()) { + push_font(fc); + } + + pos = brk_end + 1; + tag_stack.push_front("font"); + } else if (tag.begins_with("font ")) { Vector<String> subtag = tag.substr(2, tag.length()).split(" "); + Ref<FontVariation> fc; + fc.instantiate(); + int fnt_size = 0; for (int i = 1; i < subtag.size(); i++) { Vector<String> subtag_a = subtag[i].split("=", true, 2); if (subtag_a.size() == 2) { if (subtag_a[0] == "name" || subtag_a[0] == "n") { String fnt = subtag_a[1]; - Ref<Font> font = ResourceLoader::load(fnt, "Font"); - if (font.is_valid()) { - push_font(font); - } else { - push_font(normal_font); + Ref<Font> font_data = ResourceLoader::load(fnt, "Font"); + if (font_data.is_valid()) { + fc->set_base_font(font_data); } } else if (subtag_a[0] == "size" || subtag_a[0] == "s") { - int fnt_size = subtag_a[1].to_int(); - push_font_size(fnt_size); + fnt_size = subtag_a[1].to_int(); + } else if (subtag_a[0] == "glyph_spacing" || subtag_a[0] == "gl") { + int spacing = subtag_a[1].to_int(); + fc->set_spacing(TextServer::SPACING_GLYPH, spacing); + } else if (subtag_a[0] == "space_spacing" || subtag_a[0] == "sp") { + int spacing = subtag_a[1].to_int(); + fc->set_spacing(TextServer::SPACING_SPACE, spacing); + } else if (subtag_a[0] == "top_spacing" || subtag_a[0] == "top") { + int spacing = subtag_a[1].to_int(); + fc->set_spacing(TextServer::SPACING_TOP, spacing); + } else if (subtag_a[0] == "bottom_spacing" || subtag_a[0] == "bt") { + int spacing = subtag_a[1].to_int(); + fc->set_spacing(TextServer::SPACING_BOTTOM, spacing); + } else if (subtag_a[0] == "embolden" || subtag_a[0] == "emb") { + float emb = subtag_a[1].to_float(); + fc->set_variation_embolden(emb); + } else if (subtag_a[0] == "face_index" || subtag_a[0] == "fi") { + int fi = subtag_a[1].to_int(); + fc->set_variation_face_index(fi); + } else if (subtag_a[0] == "slant" || subtag_a[0] == "sln") { + float slant = subtag_a[1].to_float(); + fc->set_variation_transform(Transform2D(1.0, slant, 0.0, 1.0, 0.0, 0.0)); + } else if (subtag_a[0] == "opentype_variation" || subtag_a[0] == "otv") { + Dictionary variations; + if (!subtag_a[1].is_empty()) { + Vector<String> variation_tags = subtag_a[1].split(","); + for (int j = 0; j < variation_tags.size(); j++) { + Vector<String> subtag_b = variation_tags[j].split("="); + if (subtag_b.size() == 2) { + variations[TS->name_to_tag(subtag_b[0])] = subtag_b[1].to_float(); + } + } + fc->set_variation_opentype(variations); + } + } else if (subtag_a[0] == "opentype_features" || subtag_a[0] == "otf") { + Dictionary features; + if (!subtag_a[1].is_empty()) { + Vector<String> feature_tags = subtag_a[1].split(","); + for (int j = 0; j < feature_tags.size(); j++) { + Vector<String> subtag_b = feature_tags[j].split("="); + if (subtag_b.size() == 2) { + features[TS->name_to_tag(subtag_b[0])] = subtag_b[1].to_float(); + } else if (subtag_b.size() == 1) { + features[TS->name_to_tag(subtag_b[0])] = 1; + } + } + fc->set_opentype_features(features); + } } } } - + push_font(fc, fnt_size); pos = brk_end + 1; tag_stack.push_front("font"); + } else if (tag.begins_with("outline_size=")) { int fnt_size = tag.substr(13, tag.length()).to_int(); - push_outline_size(fnt_size); + if (fnt_size > 0) { + push_outline_size(fnt_size); + } pos = brk_end + 1; tag_stack.push_front("outline_size"); @@ -4805,11 +4944,10 @@ void RichTextLabel::install_effect(const Variant effect) { Ref<RichTextEffect> rteffect; rteffect = effect; - if (rteffect.is_valid()) { - custom_effects.push_back(effect); - if ((!text.is_empty()) && use_bbcode) { - parse_bbcode(text); - } + ERR_FAIL_COND_MSG(rteffect.is_null(), "Invalid RichTextEffect resource."); + custom_effects.push_back(effect); + if ((!text.is_empty()) && use_bbcode) { + parse_bbcode(text); } } @@ -4852,9 +4990,8 @@ void RichTextLabel::_bind_methods() { 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("newline"), &RichTextLabel::add_newline); ClassDB::bind_method(D_METHOD("remove_line", "line"), &RichTextLabel::remove_line); - ClassDB::bind_method(D_METHOD("push_font", "font"), &RichTextLabel::push_font); + ClassDB::bind_method(D_METHOD("push_font", "font", "font_size"), &RichTextLabel::push_font); ClassDB::bind_method(D_METHOD("push_font_size", "font_size"), &RichTextLabel::push_font_size); - ClassDB::bind_method(D_METHOD("push_font_features", "opentype_features"), &RichTextLabel::push_font_features); ClassDB::bind_method(D_METHOD("push_normal"), &RichTextLabel::push_normal); ClassDB::bind_method(D_METHOD("push_bold"), &RichTextLabel::push_bold); ClassDB::bind_method(D_METHOD("push_bold_italics"), &RichTextLabel::push_bold_italics); @@ -5018,11 +5155,9 @@ void RichTextLabel::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::BOOL, "context_menu_enabled"), "set_context_menu_enabled", "is_context_menu_enabled"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "shortcut_keys_enabled"), "set_shortcut_keys_enabled", "is_shortcut_keys_enabled"); - ADD_GROUP("Locale", ""); + ADD_GROUP("BiDi", ""); ADD_PROPERTY(PropertyInfo(Variant::INT, "text_direction", PROPERTY_HINT_ENUM, "Auto,Left-to-Right,Right-to-Left,Inherited"), "set_text_direction", "get_text_direction"); ADD_PROPERTY(PropertyInfo(Variant::STRING, "language", PROPERTY_HINT_LOCALE_ID, ""), "set_language", "get_language"); - - ADD_GROUP("Structured Text", "structured_text_"); ADD_PROPERTY(PropertyInfo(Variant::INT, "structured_text_bidi_override", PROPERTY_HINT_ENUM, "Default,URI,File,Email,List,None,Custom"), "set_structured_text_bidi_override", "get_structured_text_bidi_override"); ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "structured_text_bidi_override_options"), "set_structured_text_bidi_override_options", "get_structured_text_bidi_override_options"); diff --git a/scene/gui/rich_text_label.h b/scene/gui/rich_text_label.h index c697320976..e5f0469c01 100644 --- a/scene/gui/rich_text_label.h +++ b/scene/gui/rich_text_label.h @@ -178,6 +178,7 @@ private: struct ItemFont : public Item { Ref<Font> font; + int font_size = 0; ItemFont() { type = ITEM_FONT; } }; @@ -186,11 +187,6 @@ private: ItemFontSize() { type = ITEM_FONT_SIZE; } }; - struct ItemFontFeatures : public Item { - Dictionary opentype_features; - ItemFontFeatures() { type = ITEM_FONT_FEATURES; } - }; - struct ItemColor : public Item { Color color; ItemColor() { type = ITEM_COLOR; } @@ -464,11 +460,11 @@ private: String _roman(int p_num, bool p_capitalize) const; String _letters(int p_num, bool p_capitalize) const; + Item *_find_indentable(Item *p_item); Item *_get_item_at_pos(Item *p_item_from, Item *p_item_to, int p_position); void _find_frame(Item *p_item, ItemFrame **r_frame, int *r_line); - Ref<Font> _find_font(Item *p_item); - int _find_font_size(Item *p_item); - Dictionary _find_font_features(Item *p_item); + ItemFontSize *_find_font_size(Item *p_item); + ItemFont *_find_font(Item *p_item); int _find_outline_size(Item *p_item, int p_default); ItemList *_find_list_item(Item *p_item); ItemDropcap *_find_dc_item(Item *p_item); @@ -523,9 +519,8 @@ public: 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)); - void push_font(const Ref<Font> &p_font); + void push_font(const Ref<Font> &p_font, int p_size = 0); void push_font_size(int p_font_size); - void push_font_features(const Dictionary &p_features); void push_outline_size(int p_font_size); void push_normal(); void push_bold(); diff --git a/scene/gui/scroll_bar.cpp b/scene/gui/scroll_bar.cpp index f387f91a8b..48c57d9b1b 100644 --- a/scene/gui/scroll_bar.cpp +++ b/scene/gui/scroll_bar.cpp @@ -303,7 +303,7 @@ void ScrollBar::_notification(int p_what) { if (drag_node) { drag_node->connect("gui_input", callable_mp(this, &ScrollBar::_drag_node_input)); - drag_node->connect("tree_exiting", callable_mp(this, &ScrollBar::_drag_node_exit), varray(), CONNECT_ONESHOT); + drag_node->connect("tree_exiting", callable_mp(this, &ScrollBar::_drag_node_exit), CONNECT_ONESHOT); } } break; @@ -595,7 +595,7 @@ void ScrollBar::set_drag_node(const NodePath &p_path) { if (drag_node) { drag_node->connect("gui_input", callable_mp(this, &ScrollBar::_drag_node_input)); - drag_node->connect("tree_exiting", callable_mp(this, &ScrollBar::_drag_node_exit), varray(), CONNECT_ONESHOT); + drag_node->connect("tree_exiting", callable_mp(this, &ScrollBar::_drag_node_exit), CONNECT_ONESHOT); } } } diff --git a/scene/gui/scroll_bar.h b/scene/gui/scroll_bar.h index 651edd1a74..1823f86a67 100644 --- a/scene/gui/scroll_bar.h +++ b/scene/gui/scroll_bar.h @@ -128,4 +128,4 @@ public: ScrollBar(VERTICAL) { set_h_size_flags(0); } }; -#endif +#endif // SCROLL_BAR_H diff --git a/scene/gui/scroll_container.cpp b/scene/gui/scroll_container.cpp index 871cc520e9..9efab27e3a 100644 --- a/scene/gui/scroll_container.cpp +++ b/scene/gui/scroll_container.cpp @@ -37,10 +37,11 @@ Size2 ScrollContainer::get_minimum_size() const { Ref<StyleBox> sb = get_theme_stylebox(SNAME("bg")); Size2 min_size; + Size2 content_min_size; for (int i = 0; i < get_child_count(); i++) { Control *c = Object::cast_to<Control>(get_child(i)); - if (!c) { + if (!c || !c->is_visible()) { continue; } if (c->is_set_as_top_level()) { @@ -51,20 +52,26 @@ Size2 ScrollContainer::get_minimum_size() const { } Size2 minsize = c->get_combined_minimum_size(); - if (horizontal_scroll_mode == SCROLL_MODE_DISABLED) { - min_size.x = MAX(min_size.x, minsize.x); - } - if (vertical_scroll_mode == SCROLL_MODE_DISABLED) { - min_size.y = MAX(min_size.y, minsize.y); - } + content_min_size.x = MAX(content_min_size.x, minsize.x); + content_min_size.y = MAX(content_min_size.y, minsize.y); + } + + if (horizontal_scroll_mode == SCROLL_MODE_DISABLED) { + min_size.x = MAX(min_size.x, content_min_size.x); + } + if (vertical_scroll_mode == SCROLL_MODE_DISABLED) { + min_size.y = MAX(min_size.y, content_min_size.y); } - if (h_scroll->is_visible_in_tree()) { + bool h_scroll_show = horizontal_scroll_mode == SCROLL_MODE_SHOW_ALWAYS || (horizontal_scroll_mode == SCROLL_MODE_AUTO && content_min_size.x > min_size.x); + bool v_scroll_show = vertical_scroll_mode == SCROLL_MODE_SHOW_ALWAYS || (vertical_scroll_mode == SCROLL_MODE_AUTO && content_min_size.y > min_size.y); + if (h_scroll_show) { min_size.y += h_scroll->get_minimum_size().y; } - if (v_scroll->is_visible_in_tree()) { + if (v_scroll_show) { min_size.x += v_scroll->get_minimum_size().x; } + min_size += sb->get_minimum_size(); return min_size; } @@ -274,7 +281,7 @@ void ScrollContainer::_update_dimensions() { for (int i = 0; i < get_child_count(); i++) { Control *c = Object::cast_to<Control>(get_child(i)); - if (!c) { + if (!c || !c->is_visible()) { continue; } if (c->is_set_as_top_level()) { @@ -312,6 +319,7 @@ void ScrollContainer::_update_dimensions() { fit_child_in_rect(c, r); } + update_scrollbars(); update(); } diff --git a/scene/gui/scroll_container.h b/scene/gui/scroll_container.h index b9fcf64db6..7c8690538d 100644 --- a/scene/gui/scroll_container.h +++ b/scene/gui/scroll_container.h @@ -116,4 +116,4 @@ public: VARIANT_ENUM_CAST(ScrollContainer::ScrollMode); -#endif +#endif // SCROLL_CONTAINER_H diff --git a/scene/gui/separator.h b/scene/gui/separator.h index 1621bb3351..e6578a4d04 100644 --- a/scene/gui/separator.h +++ b/scene/gui/separator.h @@ -60,4 +60,4 @@ public: HSeparator(); }; -#endif +#endif // SEPARATOR_H diff --git a/scene/gui/slider.cpp b/scene/gui/slider.cpp index 4b680f72cf..64c07007dc 100644 --- a/scene/gui/slider.cpp +++ b/scene/gui/slider.cpp @@ -96,15 +96,15 @@ void Slider::gui_input(const Ref<InputEvent> &p_event) { if (grab.active) { Size2i size = get_size(); Ref<Texture2D> grabber = get_theme_icon(SNAME("grabber")); - float motion = (orientation == VERTICAL ? mm->get_position().y : mm->get_position().x) - grab.pos; + double motion = (orientation == VERTICAL ? mm->get_position().y : mm->get_position().x) - grab.pos; if (orientation == VERTICAL) { motion = -motion; } - float areasize = orientation == VERTICAL ? size.height - grabber->get_size().height : size.width - grabber->get_size().width; + double areasize = orientation == VERTICAL ? size.height - grabber->get_size().height : size.width - grabber->get_size().width; if (areasize <= 0) { return; } - float umotion = motion / float(areasize); + double umotion = motion / double(areasize); set_as_ratio(grab.uvalue + umotion); } } @@ -180,7 +180,7 @@ void Slider::_notification(int p_what) { if (orientation == VERTICAL) { int widget_width = style->get_minimum_size().width + style->get_center_size().width; - float areasize = size.height - grabber->get_size().height; + double areasize = size.height - grabber->get_size().height; style->draw(ci, Rect2i(Point2i(size.width / 2 - widget_width / 2, 0), Size2i(widget_width, size.height))); grabber_area->draw(ci, Rect2i(Point2i((size.width - widget_width) / 2, size.height - areasize * ratio - grabber->get_size().height / 2), Size2i(widget_width, areasize * ratio + grabber->get_size().height / 2))); @@ -197,7 +197,7 @@ void Slider::_notification(int p_what) { grabber->draw(ci, Point2i(size.width / 2 - grabber->get_size().width / 2, size.height - ratio * areasize - grabber->get_size().height)); } else { int widget_height = style->get_minimum_size().height + style->get_center_size().height; - float areasize = size.width - grabber->get_size().width; + double areasize = size.width - grabber->get_size().width; style->draw(ci, Rect2i(Point2i(0, (size.height - widget_height) / 2), Size2i(size.width, widget_height))); grabber_area->draw(ci, Rect2i(Point2i(0, (size.height - widget_height) / 2), Size2i(areasize * ratio + grabber->get_size().width / 2, widget_height))); @@ -218,11 +218,11 @@ void Slider::_notification(int p_what) { } } -void Slider::set_custom_step(float p_custom_step) { +void Slider::set_custom_step(double p_custom_step) { custom_step = p_custom_step; } -float Slider::get_custom_step() const { +double Slider::get_custom_step() const { return custom_step; } diff --git a/scene/gui/slider.h b/scene/gui/slider.h index 5fbfee2aec..5abaee27aa 100644 --- a/scene/gui/slider.h +++ b/scene/gui/slider.h @@ -38,14 +38,14 @@ class Slider : public Range { struct Grab { int pos = 0; - float uvalue = 0.0; + double uvalue = 0.0; bool active = false; } grab; int ticks = 0; bool mouse_inside = false; Orientation orientation; - float custom_step = -1.0; + double custom_step = -1.0; bool editable = true; bool scrollable = true; @@ -58,8 +58,8 @@ protected: public: virtual Size2 get_minimum_size() const override; - void set_custom_step(float p_custom_step); - float get_custom_step() const; + void set_custom_step(double p_custom_step); + double get_custom_step() const; void set_ticks(int p_count); int get_ticks() const; diff --git a/scene/gui/spin_box.cpp b/scene/gui/spin_box.cpp index 890e349afb..b7bef37e17 100644 --- a/scene/gui/spin_box.cpp +++ b/scene/gui/spin_box.cpp @@ -167,7 +167,7 @@ void SpinBox::gui_input(const Ref<InputEvent> &p_event) { if (mm.is_valid() && (mm->get_button_mask() & MouseButton::MASK_LEFT) != MouseButton::NONE) { if (drag.enabled) { drag.diff_y += mm->get_relative().y; - float diff_y = -0.01 * Math::pow(ABS(drag.diff_y), 1.8f) * SIGN(drag.diff_y); + double diff_y = -0.01 * Math::pow(ABS(drag.diff_y), 1.8) * SIGN(drag.diff_y); set_value(CLAMP(drag.base_val + get_step() * diff_y, get_min(), get_max())); } else if (drag.allowed && drag.capture_pos.distance_to(mm->get_position()) > 2) { Input::get_singleton()->set_mouse_mode(Input::MOUSE_MODE_CAPTURED); @@ -272,7 +272,7 @@ void SpinBox::set_update_on_text_changed(bool p_enabled) { update_on_text_changed = p_enabled; if (p_enabled) { - line_edit->connect("text_changed", callable_mp(this, &SpinBox::_text_changed), Vector<Variant>(), CONNECT_DEFERRED); + line_edit->connect("text_changed", callable_mp(this, &SpinBox::_text_changed), CONNECT_DEFERRED); } else { line_edit->disconnect("text_changed", callable_mp(this, &SpinBox::_text_changed)); } @@ -319,12 +319,12 @@ SpinBox::SpinBox() { line_edit = memnew(LineEdit); add_child(line_edit, false, INTERNAL_MODE_FRONT); - line_edit->set_anchors_and_offsets_preset(Control::PRESET_WIDE); + line_edit->set_anchors_and_offsets_preset(Control::PRESET_FULL_RECT); line_edit->set_mouse_filter(MOUSE_FILTER_PASS); line_edit->set_horizontal_alignment(HORIZONTAL_ALIGNMENT_LEFT); - line_edit->connect("text_submitted", callable_mp(this, &SpinBox::_text_submitted), Vector<Variant>(), CONNECT_DEFERRED); - line_edit->connect("focus_exited", callable_mp(this, &SpinBox::_line_edit_focus_exit), Vector<Variant>(), CONNECT_DEFERRED); + line_edit->connect("text_submitted", callable_mp(this, &SpinBox::_text_submitted), CONNECT_DEFERRED); + line_edit->connect("focus_exited", callable_mp(this, &SpinBox::_line_edit_focus_exit), CONNECT_DEFERRED); line_edit->connect("gui_input", callable_mp(this, &SpinBox::_line_edit_input)); range_click_timer = memnew(Timer); diff --git a/scene/gui/spin_box.h b/scene/gui/spin_box.h index d118b28334..1b1abbcf8e 100644 --- a/scene/gui/spin_box.h +++ b/scene/gui/spin_box.h @@ -56,11 +56,11 @@ class SpinBox : public Range { void _line_edit_input(const Ref<InputEvent> &p_event); struct Drag { - float base_val = 0.0; + double base_val = 0.0; bool allowed = false; bool enabled = false; Vector2 capture_pos; - float diff_y = 0.0; + double diff_y = 0.0; } drag; void _line_edit_focus_exit(); diff --git a/scene/gui/subviewport_container.h b/scene/gui/subviewport_container.h index 55b7802aa4..5b488fb79e 100644 --- a/scene/gui/subviewport_container.h +++ b/scene/gui/subviewport_container.h @@ -28,8 +28,8 @@ /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /*************************************************************************/ -#ifndef VIEWPORTCONTAINER_H -#define VIEWPORTCONTAINER_H +#ifndef SUBVIEWPORT_CONTAINER_H +#define SUBVIEWPORT_CONTAINER_H #include "scene/gui/container.h" @@ -63,4 +63,4 @@ public: SubViewportContainer(); }; -#endif // VIEWPORTCONTAINER_H +#endif // SUBVIEWPORT_CONTAINER_H diff --git a/scene/gui/tab_bar.cpp b/scene/gui/tab_bar.cpp index b4c90596f9..d36a364677 100644 --- a/scene/gui/tab_bar.cpp +++ b/scene/gui/tab_bar.cpp @@ -311,7 +311,7 @@ void TabBar::_shape(int p_tab) { tabs.write[p_tab].text_buf->set_direction((TextServer::Direction)tabs[p_tab].text_direction); } - tabs.write[p_tab].text_buf->add_string(tabs[p_tab].xl_text, font, font_size, tabs[p_tab].opentype_features, !tabs[p_tab].language.is_empty() ? tabs[p_tab].language : TranslationServer::get_singleton()->get_tool_locale()); + tabs.write[p_tab].text_buf->add_string(tabs[p_tab].xl_text, font, font_size, tabs[p_tab].language); } void TabBar::_notification(int p_what) { @@ -667,48 +667,6 @@ Control::TextDirection TabBar::get_tab_text_direction(int p_tab) const { return tabs[p_tab].text_direction; } -void TabBar::clear_tab_opentype_features(int p_tab) { - ERR_FAIL_INDEX(p_tab, tabs.size()); - tabs.write[p_tab].opentype_features.clear(); - - _shape(p_tab); - _update_cache(); - _ensure_no_over_offset(); - if (scroll_to_selected) { - ensure_tab_visible(current); - } - update(); - update_minimum_size(); -} - -void TabBar::set_tab_opentype_feature(int p_tab, const String &p_name, int p_value) { - ERR_FAIL_INDEX(p_tab, tabs.size()); - - int32_t tag = TS->name_to_tag(p_name); - if (!tabs[p_tab].opentype_features.has(tag) || (int)tabs[p_tab].opentype_features[tag] != p_value) { - tabs.write[p_tab].opentype_features[tag] = p_value; - - _shape(p_tab); - _update_cache(); - _ensure_no_over_offset(); - if (scroll_to_selected) { - ensure_tab_visible(current); - } - update(); - update_minimum_size(); - } -} - -int TabBar::get_tab_opentype_feature(int p_tab, const String &p_name) const { - ERR_FAIL_INDEX_V(p_tab, tabs.size(), -1); - - int32_t tag = TS->name_to_tag(p_name); - if (!tabs[p_tab].opentype_features.has(tag)) { - return -1; - } - return tabs[p_tab].opentype_features[tag]; -} - void TabBar::set_tab_language(int p_tab, const String &p_language) { ERR_FAIL_INDEX(p_tab, tabs.size()); @@ -1553,9 +1511,6 @@ void TabBar::_bind_methods() { ClassDB::bind_method(D_METHOD("get_tab_title", "tab_idx"), &TabBar::get_tab_title); ClassDB::bind_method(D_METHOD("set_tab_text_direction", "tab_idx", "direction"), &TabBar::set_tab_text_direction); ClassDB::bind_method(D_METHOD("get_tab_text_direction", "tab_idx"), &TabBar::get_tab_text_direction); - ClassDB::bind_method(D_METHOD("set_tab_opentype_feature", "tab_idx", "tag", "values"), &TabBar::set_tab_opentype_feature); - ClassDB::bind_method(D_METHOD("get_tab_opentype_feature", "tab_idx", "tag"), &TabBar::get_tab_opentype_feature); - ClassDB::bind_method(D_METHOD("clear_tab_opentype_features", "tab_idx"), &TabBar::clear_tab_opentype_features); ClassDB::bind_method(D_METHOD("set_tab_language", "tab_idx", "language"), &TabBar::set_tab_language); ClassDB::bind_method(D_METHOD("get_tab_language", "tab_idx"), &TabBar::get_tab_language); ClassDB::bind_method(D_METHOD("set_tab_icon", "tab_idx", "icon"), &TabBar::set_tab_icon); diff --git a/scene/gui/tab_bar.h b/scene/gui/tab_bar.h index 548a2e62af..d123385e47 100644 --- a/scene/gui/tab_bar.h +++ b/scene/gui/tab_bar.h @@ -57,7 +57,6 @@ private: String text; String xl_text; - Dictionary opentype_features; String language; Control::TextDirection text_direction = Control::TEXT_DIRECTION_INHERITED; @@ -137,10 +136,6 @@ public: void set_tab_text_direction(int p_tab, TextDirection p_text_direction); TextDirection get_tab_text_direction(int p_tab) const; - void set_tab_opentype_feature(int p_tab, const String &p_name, int p_value); - int get_tab_opentype_feature(int p_tab, const String &p_name) const; - void clear_tab_opentype_features(int p_tab); - void set_tab_language(int p_tab, const String &p_language); String get_tab_language(int p_tab) const; diff --git a/scene/gui/tab_container.cpp b/scene/gui/tab_container.cpp index fa929344d4..12f91a9873 100644 --- a/scene/gui/tab_container.cpp +++ b/scene/gui/tab_container.cpp @@ -233,7 +233,7 @@ void TabContainer::_repaint() { if (i == current) { c->show(); - c->set_anchors_and_offsets_preset(Control::PRESET_WIDE); + c->set_anchors_and_offsets_preset(Control::PRESET_FULL_RECT); if (tabs_visible) { c->set_offset(SIDE_TOP, _get_top_margin()); @@ -312,7 +312,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) { + if (!control || control->is_set_as_top_level() || control == tab_bar || control == child_removing) { continue; } @@ -549,7 +549,12 @@ void TabContainer::remove_child_notify(Node *p_child) { return; } - tab_bar->remove_tab(get_tab_idx_from_control(c)); + 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; + tab_bar->remove_tab(idx); + child_removing = nullptr; _update_margins(); if (get_tab_count() == 0) { diff --git a/scene/gui/tab_container.h b/scene/gui/tab_container.h index 9adaa0d844..60c8130939 100644 --- a/scene/gui/tab_container.h +++ b/scene/gui/tab_container.h @@ -46,6 +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; int _get_top_margin() const; Vector<Control *> _get_tab_controls() const; diff --git a/scene/gui/text_edit.cpp b/scene/gui/text_edit.cpp index 5506616b7a..4bc8b8e197 100644 --- a/scene/gui/text_edit.cpp +++ b/scene/gui/text_edit.cpp @@ -75,14 +75,6 @@ int TextEdit::Text::get_tab_size() const { return tab_size; } -void TextEdit::Text::set_font_features(const Dictionary &p_features) { - if (opentype_features.hash() == p_features.hash()) { - return; - } - opentype_features = p_features; - is_dirty = true; -} - void TextEdit::Text::set_direction_and_language(TextServer::Direction p_direction, const String &p_language) { if (direction == p_direction && language == p_language) { return; @@ -178,7 +170,7 @@ void TextEdit::Text::_calculate_max_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) { ERR_FAIL_INDEX(p_line, text.size()); - if (font.is_null() || font_size <= 0) { + if (font.is_null()) { return; // Not in tree? } @@ -191,14 +183,14 @@ void TextEdit::Text::invalidate_cache(int p_line, int p_column, bool p_text_chan text.write[p_line].data_buf->set_preserve_control(draw_control_chars); if (p_ime_text.length() > 0) { if (p_text_changed) { - text.write[p_line].data_buf->add_string(p_ime_text, font, font_size, opentype_features, language); + text.write[p_line].data_buf->add_string(p_ime_text, font, font_size, language); } if (!p_bidi_override.is_empty()) { TS->shaped_text_set_bidi_override(text.write[p_line].data_buf->get_rid(), p_bidi_override); } } else { if (p_text_changed) { - text.write[p_line].data_buf->add_string(text[p_line].data, font, font_size, opentype_features, language); + text.write[p_line].data_buf->add_string(text[p_line].data, font, font_size, language); } if (!text[p_line].bidi_override.is_empty()) { TS->shaped_text_set_bidi_override(text.write[p_line].data_buf->get_rid(), text[p_line].bidi_override); @@ -209,14 +201,17 @@ void TextEdit::Text::invalidate_cache(int p_line, int p_column, bool p_text_chan RID r = text.write[p_line].data_buf->get_rid(); int spans = TS->shaped_get_span_count(r); for (int i = 0; i < spans; i++) { - TS->shaped_set_span_update_font(r, i, font->get_rids(), font_size, opentype_features); + TS->shaped_set_span_update_font(r, i, font->get_rids(), font_size, font->get_opentype_features()); + } + for (int i = 0; i < TextServer::SPACING_MAX; i++) { + TS->shaped_text_set_spacing(r, TextServer::SpacingType(i), font->get_spacing(TextServer::SpacingType(i))); } } // Apply tab align. if (tab_size > 0) { Vector<float> tabs; - tabs.push_back(font->get_char_size(' ', 0, font_size).width * tab_size); + tabs.push_back(font->get_char_size(' ', font_size).width * tab_size); text.write[p_line].data_buf->tab_align(tabs); } @@ -255,7 +250,7 @@ void TextEdit::Text::invalidate_all_lines() { if (tab_size_dirty) { if (tab_size > 0) { Vector<float> tabs; - tabs.push_back(font->get_char_size(' ', 0, font_size).width * tab_size); + tabs.push_back(font->get_char_size(' ', font_size).width * tab_size); text.write[i].data_buf->tab_align(tabs); } // Tabs have changes, force width update. @@ -277,7 +272,7 @@ void TextEdit::Text::invalidate_font() { max_width = -1; line_height = -1; - if (!font.is_null() && font_size > 0) { + if (font.is_valid() && font_size > 0) { font_height = font->get_height(font_size); } @@ -295,7 +290,7 @@ void TextEdit::Text::invalidate_all() { max_width = -1; line_height = -1; - if (!font.is_null() && font_size > 0) { + if (font.is_valid() && font_size > 0) { font_height = font->get_height(font_size); } @@ -448,8 +443,10 @@ void TextEdit::_notification(int p_what) { case NOTIFICATION_LAYOUT_DIRECTION_CHANGED: case NOTIFICATION_TRANSLATION_CHANGED: case NOTIFICATION_THEME_CHANGED: { - _update_caches(); - _update_wrap_at_column(true); + if (is_inside_tree()) { + _update_caches(); + _update_wrap_at_column(true); + } } break; case NOTIFICATION_WM_WINDOW_FOCUS_IN: { @@ -973,7 +970,7 @@ void TextEdit::_notification(int p_what) { // Give visual indication of empty selected line. if (selection.active && line >= selection.from_line && line <= selection.to_line && char_margin >= xmargin_beg) { - float char_w = font->get_char_size(' ', 0, font_size).width; + float char_w = font->get_char_size(' ', font_size).width; if (rtl) { RenderingServer::get_singleton()->canvas_item_add_rect(ci, Rect2(size.width - xmargin_beg - ofs_x - char_w, ofs_y, char_w, row_height), selection_color); } else { @@ -1060,7 +1057,7 @@ void TextEdit::_notification(int p_what) { const Variant *argp[] = { &args[0], &args[1], &args[2] }; Callable::CallError ce; Variant ret; - gutter.custom_draw_callback.call(argp, 3, ret, ce); + gutter.custom_draw_callback.callp(argp, 3, ret, ce); } } break; } @@ -1071,7 +1068,7 @@ void TextEdit::_notification(int p_what) { // Draw line. RID rid = ldata->get_line_rid(line_wrap_index); - float text_height = TS->shaped_text_get_size(rid).y + font->get_spacing(TextServer::SPACING_TOP) + font->get_spacing(TextServer::SPACING_BOTTOM); + float text_height = TS->shaped_text_get_size(rid).y; if (rtl) { char_margin = size.width - char_margin - TS->shaped_text_get_size(rid).x; @@ -1222,7 +1219,7 @@ void TextEdit::_notification(int p_what) { if (brace_open_mismatch) { current_color = brace_mismatch_color; } - Rect2 rect = Rect2(char_pos, ofs_y + font->get_underline_position(font_size), glyphs[j].advance * glyphs[j].repeat, font->get_underline_thickness(font_size)); + Rect2 rect = Rect2(char_pos, ofs_y + font->get_underline_position(font_size), glyphs[j].advance * glyphs[j].repeat, MAX(font->get_underline_thickness(font_size) * get_theme_default_base_scale(), 1)); draw_rect(rect, current_color); } @@ -1231,7 +1228,7 @@ void TextEdit::_notification(int p_what) { if (brace_close_mismatch) { current_color = brace_mismatch_color; } - Rect2 rect = Rect2(char_pos, ofs_y + font->get_underline_position(font_size), glyphs[j].advance * glyphs[j].repeat, font->get_underline_thickness(font_size)); + Rect2 rect = Rect2(char_pos, ofs_y + font->get_underline_position(font_size), glyphs[j].advance * glyphs[j].repeat, MAX(font->get_underline_thickness(font_size) * get_theme_default_base_scale(), 1)); draw_rect(rect, current_color); } } @@ -1355,7 +1352,7 @@ void TextEdit::_notification(int p_what) { ts_caret.l_caret.size.y = caret_width; } if (ts_caret.l_caret.position.x >= TS->shaped_text_get_size(rid).x) { - ts_caret.l_caret.size.x = font->get_char_size('m', 0, font_size).x; + ts_caret.l_caret.size.x = font->get_char_size('m', font_size).x; } else { ts_caret.l_caret.size.x = 3 * caret_width; } @@ -2573,7 +2570,7 @@ void TextEdit::_update_placeholder() { placeholder_data_buf->set_width(text.get_width()); placeholder_data_buf->set_direction((TextServer::Direction)text_direction); placeholder_data_buf->set_preserve_control(draw_control_chars); - placeholder_data_buf->add_string(placeholder_text, font, font_size, opentype_features, language); + placeholder_data_buf->add_string(placeholder_text, font, font_size, language); placeholder_bidi_override = structured_text_parser(st_parser, st_args, placeholder_text); if (placeholder_bidi_override.is_empty()) { @@ -2582,7 +2579,7 @@ void TextEdit::_update_placeholder() { if (get_tab_size() > 0) { Vector<float> tabs; - tabs.push_back(font->get_char_size(' ', 0, font_size).width * get_tab_size()); + tabs.push_back(font->get_char_size(' ', font_size).width * get_tab_size()); placeholder_data_buf->tab_align(tabs); } @@ -2653,7 +2650,6 @@ void TextEdit::_update_caches() { dir = (TextServer::Direction)text_direction; } text.set_direction_and_language(dir, (!language.is_empty()) ? language : TranslationServer::get_singleton()->get_tool_locale()); - text.set_font_features(opentype_features); text.set_draw_control_chars(draw_control_chars); text.set_font(font); text.set_font_size(font_size); @@ -2668,7 +2664,11 @@ void TextEdit::_update_caches() { /* General overrides. */ Size2 TextEdit::get_minimum_size() const { - return style_normal->get_minimum_size(); + Size2 size = style_normal->get_minimum_size(); + if (fit_content_height) { + size.y += content_height_cache; + } + return size; } bool TextEdit::is_text_field() const { @@ -2791,7 +2791,7 @@ String TextEdit::get_tooltip(const Point2 &p_pos) const { const Variant *argp[] = { &args[0] }; Callable::CallError ce; Variant ret; - tooltip_callback.call(argp, 1, ret, ce); + tooltip_callback.callp(argp, 1, ret, ce); ERR_FAIL_COND_V_MSG(ce.error != Callable::CallError::CALL_OK, "", "Failed to call custom tooltip."); return ret; } @@ -2854,33 +2854,6 @@ Control::TextDirection TextEdit::get_text_direction() const { return text_direction; } -void TextEdit::set_opentype_feature(const String &p_name, int p_value) { - int32_t tag = TS->name_to_tag(p_name); - if (!opentype_features.has(tag) || (int)opentype_features[tag] != p_value) { - opentype_features[tag] = p_value; - text.set_font_features(opentype_features); - text.invalidate_font(); - _update_placeholder(); - update(); - } -} - -int TextEdit::get_opentype_feature(const String &p_name) const { - int32_t tag = TS->name_to_tag(p_name); - if (!opentype_features.has(tag)) { - return -1; - } - return opentype_features[tag]; -} - -void TextEdit::clear_opentype_features() { - opentype_features.clear(); - text.set_font_features(opentype_features); - text.invalidate_font(); - _update_placeholder(); - update(); -} - void TextEdit::set_language(const String &p_language) { if (language != p_language) { language = p_language; @@ -4499,6 +4472,18 @@ float TextEdit::get_v_scroll_speed() const { return v_scroll_speed; } +void TextEdit::set_fit_content_height_enabled(const bool p_enabled) { + if (fit_content_height == p_enabled) { + return; + } + fit_content_height = p_enabled; + update_minimum_size(); +} + +bool TextEdit::is_fit_content_height_enabled() const { + return fit_content_height; +} + double TextEdit::get_scroll_pos_for_line(int p_line, int p_wrap_index) const { ERR_FAIL_INDEX_V(p_line, text.size(), 0); ERR_FAIL_COND_V(p_wrap_index < 0, 0); @@ -5055,10 +5040,6 @@ void TextEdit::_bind_methods() { ClassDB::bind_method(D_METHOD("set_text_direction", "direction"), &TextEdit::set_text_direction); ClassDB::bind_method(D_METHOD("get_text_direction"), &TextEdit::get_text_direction); - ClassDB::bind_method(D_METHOD("set_opentype_feature", "tag", "value"), &TextEdit::set_opentype_feature); - ClassDB::bind_method(D_METHOD("get_opentype_feature", "tag"), &TextEdit::get_opentype_feature); - ClassDB::bind_method(D_METHOD("clear_opentype_features"), &TextEdit::clear_opentype_features); - ClassDB::bind_method(D_METHOD("set_language", "language"), &TextEdit::set_language); ClassDB::bind_method(D_METHOD("get_language"), &TextEdit::get_language); @@ -5091,6 +5072,7 @@ void TextEdit::_bind_methods() { ClassDB::bind_method(D_METHOD("set_text", "text"), &TextEdit::set_text); ClassDB::bind_method(D_METHOD("get_text"), &TextEdit::get_text); + ClassDB::bind_method(D_METHOD("get_line_count"), &TextEdit::get_line_count); ClassDB::bind_method(D_METHOD("set_placeholder", "text"), &TextEdit::set_placeholder); @@ -5297,7 +5279,7 @@ void TextEdit::_bind_methods() { /* Viewport. */ // Scrolling. - ClassDB::bind_method(D_METHOD("set_smooth_scroll_enable", "enable"), &TextEdit::set_smooth_scroll_enabled); + 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("set_v_scroll", "value"), &TextEdit::set_v_scroll); @@ -5312,6 +5294,9 @@ void TextEdit::_bind_methods() { ClassDB::bind_method(D_METHOD("set_v_scroll_speed", "speed"), &TextEdit::set_v_scroll_speed); ClassDB::bind_method(D_METHOD("get_v_scroll_speed"), &TextEdit::get_v_scroll_speed); + ClassDB::bind_method(D_METHOD("set_fit_content_height_enabled"), &TextEdit::set_fit_content_height_enabled); + ClassDB::bind_method(D_METHOD("is_fit_content_height_enabled"), &TextEdit::is_fit_content_height_enabled); + ClassDB::bind_method(D_METHOD("get_scroll_pos_for_line", "line", "wrap_index"), &TextEdit::get_scroll_pos_for_line, DEFVAL(0)); // Visible lines. @@ -5408,8 +5393,6 @@ void TextEdit::_bind_methods() { /* Inspector */ ADD_PROPERTY(PropertyInfo(Variant::STRING, "text", PROPERTY_HINT_MULTILINE_TEXT), "set_text", "get_text"); ADD_PROPERTY(PropertyInfo(Variant::STRING, "placeholder_text", PROPERTY_HINT_MULTILINE_TEXT), "set_placeholder", "get_placeholder"); - ADD_PROPERTY(PropertyInfo(Variant::INT, "text_direction", PROPERTY_HINT_ENUM, "Auto,Left-to-Right,Right-to-Left,Inherited"), "set_text_direction", "get_text_direction"); - ADD_PROPERTY(PropertyInfo(Variant::STRING, "language", PROPERTY_HINT_LOCALE_ID, ""), "set_language", "get_language"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "editable"), "set_editable", "is_editable"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "context_menu_enabled"), "set_context_menu_enabled", "is_context_menu_enabled"); @@ -5431,11 +5414,12 @@ void TextEdit::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "syntax_highlighter", PROPERTY_HINT_RESOURCE_TYPE, "SyntaxHighlighter", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_DO_NOT_SHARE_ON_DUPLICATE), "set_syntax_highlighter", "get_syntax_highlighter"); ADD_GROUP("Scroll", "scroll_"); - ADD_PROPERTY(PropertyInfo(Variant::BOOL, "scroll_smooth"), "set_smooth_scroll_enable", "is_smooth_scroll_enabled"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "scroll_smooth"), "set_smooth_scroll_enabled", "is_smooth_scroll_enabled"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "scroll_v_scroll_speed", PROPERTY_HINT_NONE, "suffix:px/s"), "set_v_scroll_speed", "get_v_scroll_speed"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "scroll_past_end_of_file"), "set_scroll_past_end_of_file_enabled", "is_scroll_past_end_of_file_enabled"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "scroll_vertical", PROPERTY_HINT_NONE, "suffix:px"), "set_v_scroll", "get_v_scroll"); ADD_PROPERTY(PropertyInfo(Variant::INT, "scroll_horizontal", PROPERTY_HINT_NONE, "suffix:px"), "set_h_scroll", "get_h_scroll"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "scroll_fit_content_height"), "set_fit_content_height_enabled", "is_fit_content_height_enabled"); ADD_GROUP("Minimap", "minimap_"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "minimap_draw"), "set_draw_minimap", "is_drawing_minimap"); @@ -5448,7 +5432,9 @@ void TextEdit::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::BOOL, "caret_move_on_right_click"), "set_move_caret_on_right_click_enabled", "is_move_caret_on_right_click_enabled"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "caret_mid_grapheme"), "set_caret_mid_grapheme_enabled", "is_caret_mid_grapheme_enabled"); - ADD_GROUP("Structured Text", "structured_text_"); + ADD_GROUP("BiDi", ""); + ADD_PROPERTY(PropertyInfo(Variant::INT, "text_direction", PROPERTY_HINT_ENUM, "Auto,Left-to-Right,Right-to-Left,Inherited"), "set_text_direction", "get_text_direction"); + ADD_PROPERTY(PropertyInfo(Variant::STRING, "language", PROPERTY_HINT_LOCALE_ID, ""), "set_language", "get_language"); ADD_PROPERTY(PropertyInfo(Variant::INT, "structured_text_bidi_override", PROPERTY_HINT_ENUM, "Default,URI,File,Email,List,None,Custom"), "set_structured_text_bidi_override", "get_structured_text_bidi_override"); ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "structured_text_bidi_override_options"), "set_structured_text_bidi_override_options", "get_structured_text_bidi_override_options"); @@ -5473,60 +5459,6 @@ void TextEdit::_bind_methods() { ProjectSettings::get_singleton()->set_custom_property_info("gui/common/text_edit_undo_stack_max_size", PropertyInfo(Variant::INT, "gui/common/text_edit_undo_stack_max_size", PROPERTY_HINT_RANGE, "0,10000,1,or_greater")); // No negative numbers. } -bool TextEdit::_set(const StringName &p_name, const Variant &p_value) { - String str = p_name; - if (str.begins_with("opentype_features/")) { - String name = str.get_slicec('/', 1); - int32_t tag = TS->name_to_tag(name); - int value = p_value; - if (value == -1) { - if (opentype_features.has(tag)) { - opentype_features.erase(tag); - text.set_font_features(opentype_features); - text.invalidate_font(); - _update_placeholder(); - update(); - } - } else { - if (!opentype_features.has(tag) || (int)opentype_features[tag] != value) { - opentype_features[tag] = value; - text.set_font_features(opentype_features); - text.invalidate_font(); - _update_placeholder(); - update(); - } - } - notify_property_list_changed(); - return true; - } - - return false; -} - -bool TextEdit::_get(const StringName &p_name, Variant &r_ret) const { - String str = p_name; - if (str.begins_with("opentype_features/")) { - String name = str.get_slicec('/', 1); - int32_t tag = TS->name_to_tag(name); - if (opentype_features.has(tag)) { - r_ret = opentype_features[tag]; - return true; - } else { - r_ret = -1; - return true; - } - } - return false; -} - -void TextEdit::_get_property_list(List<PropertyInfo> *p_list) const { - for (const Variant *ftr = opentype_features.next(nullptr); ftr != nullptr; ftr = opentype_features.next(ftr)) { - String name = TS->tag_to_name(*ftr); - p_list->push_back(PropertyInfo(Variant::INT, "opentype_features/" + name)); - } - p_list->push_back(PropertyInfo(Variant::NIL, "opentype_features/_new", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_EDITOR)); -} - /* Internal API for CodeEdit. */ // Line hiding. void TextEdit::_set_hiding_enabled(bool p_enabled) { @@ -6197,6 +6129,11 @@ void TextEdit::_update_scrollbars() { total_width += minimap_width; } + content_height_cache = MAX(total_rows, 1) * get_line_height(); + if (fit_content_height) { + update_minimum_size(); + } + updating_scrolls = true; if (total_rows > visible_rows) { diff --git a/scene/gui/text_edit.h b/scene/gui/text_edit.h index 9de2982d0a..6711cf8c7f 100644 --- a/scene/gui/text_edit.h +++ b/scene/gui/text_edit.h @@ -160,7 +160,6 @@ private: int font_size = -1; int font_height = 0; - Dictionary opentype_features; String language; TextServer::Direction direction = TextServer::DIRECTION_AUTO; bool draw_control_chars = false; @@ -180,7 +179,6 @@ private: int get_tab_size() const; void set_font(const Ref<Font> &p_font); void set_font_size(int p_font_size); - void set_font_features(const Dictionary &p_features); void set_direction_and_language(TextServer::Direction p_direction, const String &p_language); void set_draw_control_chars(bool p_enabled); @@ -271,7 +269,6 @@ private: TextDirection text_direction = TEXT_DIRECTION_AUTO; TextDirection input_direction = TEXT_DIRECTION_LTR; - Dictionary opentype_features; String language = ""; TextServer::StructuredTextParser st_parser = TextServer::STRUCTURED_TEXT_DEFAULT; @@ -456,6 +453,8 @@ private: HScrollBar *h_scroll = nullptr; VScrollBar *v_scroll = nullptr; + float content_height_cache = 0.0; + bool fit_content_height = false; bool scroll_past_end_of_file_enabled = false; // Smooth scrolling. @@ -579,10 +578,6 @@ protected: static void _bind_methods(); - bool _set(const StringName &p_name, const Variant &p_value); - bool _get(const StringName &p_name, Variant &r_ret) const; - void _get_property_list(List<PropertyInfo> *p_list) const; - /* Internal API for CodeEdit, pending public API. */ // brace matching bool highlight_matching_braces_enabled = false; @@ -648,10 +643,6 @@ public: void set_text_direction(TextDirection p_text_direction); TextDirection get_text_direction() const; - void set_opentype_feature(const String &p_name, int p_value); - int get_opentype_feature(const String &p_name) const; - void clear_opentype_features(); - void set_language(const String &p_language); String get_language() const; @@ -684,6 +675,7 @@ public: void set_text(const String &p_text); String get_text() const; + int get_line_count() const; void set_placeholder(const String &p_text); @@ -851,6 +843,9 @@ public: void set_v_scroll_speed(float p_speed); float get_v_scroll_speed() const; + void set_fit_content_height_enabled(const bool p_enabled); + bool is_fit_content_height_enabled() const; + double get_scroll_pos_for_line(int p_line, int p_wrap_index = 0) const; // Visible lines. diff --git a/scene/gui/texture_button.h b/scene/gui/texture_button.h index 5762949acd..9f6f7c1515 100644 --- a/scene/gui/texture_button.h +++ b/scene/gui/texture_button.h @@ -101,4 +101,5 @@ public: }; VARIANT_ENUM_CAST(TextureButton::StretchMode); + #endif // TEXTURE_BUTTON_H diff --git a/scene/gui/tree.cpp b/scene/gui/tree.cpp index d3e7540790..1eb6c5a554 100644 --- a/scene/gui/tree.cpp +++ b/scene/gui/tree.cpp @@ -303,37 +303,6 @@ Control::TextDirection TreeItem::get_text_direction(int p_column) const { return cells[p_column].text_direction; } -void TreeItem::clear_opentype_features(int p_column) { - ERR_FAIL_INDEX(p_column, cells.size()); - - cells.write[p_column].opentype_features.clear(); - cells.write[p_column].dirty = true; - cells.write[p_column].cached_minimum_size_dirty = true; - - _changed_notify(p_column); -} - -void TreeItem::set_opentype_feature(int p_column, const String &p_name, int p_value) { - ERR_FAIL_INDEX(p_column, cells.size()); - int32_t tag = TS->name_to_tag(p_name); - if (!cells[p_column].opentype_features.has(tag) || (int)cells[p_column].opentype_features[tag] != p_value) { - cells.write[p_column].opentype_features[tag] = p_value; - cells.write[p_column].dirty = true; - cells.write[p_column].cached_minimum_size_dirty = true; - - _changed_notify(p_column); - } -} - -int TreeItem::get_opentype_feature(int p_column, const String &p_name) const { - ERR_FAIL_INDEX_V(p_column, cells.size(), -1); - int32_t tag = TS->name_to_tag(p_name); - if (!cells[p_column].opentype_features.has(tag)) { - return -1; - } - return cells[p_column].opentype_features[tag]; -} - void TreeItem::set_structured_text_bidi_override(int p_column, TextServer::StructuredTextParser p_parser) { ERR_FAIL_INDEX(p_column, cells.size()); @@ -754,7 +723,12 @@ TreeItem *TreeItem::get_next_visible(bool p_wrap) { TreeItem *TreeItem::get_child(int p_idx) { _create_children_cache(); + + if (p_idx < 0) { + p_idx += children_cache.size(); + } ERR_FAIL_INDEX_V(p_idx, children_cache.size(), nullptr); + return children_cache.get(p_idx); } @@ -1269,10 +1243,6 @@ void TreeItem::_bind_methods() { ClassDB::bind_method(D_METHOD("set_text_direction", "column", "direction"), &TreeItem::set_text_direction); ClassDB::bind_method(D_METHOD("get_text_direction", "column"), &TreeItem::get_text_direction); - ClassDB::bind_method(D_METHOD("set_opentype_feature", "column", "tag", "value"), &TreeItem::set_opentype_feature); - ClassDB::bind_method(D_METHOD("get_opentype_feature", "column", "tag"), &TreeItem::get_opentype_feature); - ClassDB::bind_method(D_METHOD("clear_opentype_features", "column"), &TreeItem::clear_opentype_features); - ClassDB::bind_method(D_METHOD("set_structured_text_bidi_override", "column", "parser"), &TreeItem::set_structured_text_bidi_override); ClassDB::bind_method(D_METHOD("get_structured_text_bidi_override", "column"), &TreeItem::get_structured_text_bidi_override); @@ -1667,7 +1637,7 @@ void Tree::update_column(int p_col) { columns.write[p_col].text_buf->set_direction((TextServer::Direction)columns[p_col].text_direction); } - columns.write[p_col].text_buf->add_string(columns[p_col].title, cache.font, cache.font_size, columns[p_col].opentype_features, !columns[p_col].language.is_empty() ? columns[p_col].language : TranslationServer::get_singleton()->get_tool_locale()); + columns.write[p_col].text_buf->add_string(columns[p_col].title, cache.font, cache.font_size, columns[p_col].language); } void Tree::update_item_cell(TreeItem *p_item, int p_col) { @@ -1725,7 +1695,7 @@ void Tree::update_item_cell(TreeItem *p_item, int p_col) { } else { font_size = cache.font_size; } - p_item->cells.write[p_col].text_buf->add_string(valtext, font, font_size, p_item->cells[p_col].opentype_features, !p_item->cells[p_col].language.is_empty() ? p_item->cells[p_col].language : TranslationServer::get_singleton()->get_tool_locale()); + p_item->cells.write[p_col].text_buf->add_string(valtext, font, font_size, p_item->cells[p_col].language); TS->shaped_text_set_bidi_override(p_item->cells[p_col].text_buf->get_rid(), structured_text_parser(p_item->cells[p_col].st_parser, p_item->cells[p_col].st_args, valtext)); p_item->cells.write[p_col].dirty = false; } @@ -1879,15 +1849,16 @@ int Tree::draw_item(const Point2i &p_pos, const Point2 &p_draw_ofs, const Size2 p_item->set_meta("__focus_rect", Rect2(r.position, r.size)); - if (rtl) { - r.position.x = get_size().width - r.position.x - r.size.x; - } - - if (p_item->cells[i].selected) { - if (has_focus()) { - cache.selected_focus->draw(ci, r); - } else { - cache.selected->draw(ci, r); + if (select_mode != SELECT_ROW) { + if (rtl) { + r.position.x = get_size().width - r.position.x - r.size.x; + } + if (p_item->cells[i].selected) { + if (has_focus()) { + cache.selected_focus->draw(ci, r); + } else { + cache.selected->draw(ci, r); + } } } } @@ -2850,6 +2821,9 @@ void Tree::value_editor_changed(double p_value) { TreeItem::Cell &c = popup_edited_item->cells.write[popup_edited_item_col]; c.val = p_value; + + text_editor->set_text(String::num(c.val, Math::range_step_decimals(c.step))); + item_edited(popup_edited_item_col, popup_edited_item); update(); } @@ -3002,6 +2976,15 @@ void Tree::_go_down() { accept_event(); } +bool Tree::_scroll(bool p_horizontal, float p_pages) { + ScrollBar *scroll = p_horizontal ? (ScrollBar *)h_scroll : (ScrollBar *)v_scroll; + + double prev_value = scroll->get_value(); + scroll->set_value(scroll->get_value() + scroll->get_page() * p_pages); + + return scroll->get_value() != prev_value; +} + void Tree::gui_input(const Ref<InputEvent> &p_event) { ERR_FAIL_COND(p_event.is_null()); @@ -3516,17 +3499,25 @@ void Tree::gui_input(const Ref<InputEvent> &p_event) { } break; case MouseButton::WHEEL_UP: { - double prev_value = v_scroll->get_value(); - v_scroll->set_value(v_scroll->get_value() - v_scroll->get_page() * mb->get_factor() / 8); - if (v_scroll->get_value() != prev_value) { + if (_scroll(false, -mb->get_factor() / 8)) { accept_event(); } } break; case MouseButton::WHEEL_DOWN: { - double prev_value = v_scroll->get_value(); - v_scroll->set_value(v_scroll->get_value() + v_scroll->get_page() * mb->get_factor() / 8); - if (v_scroll->get_value() != prev_value) { + if (_scroll(false, mb->get_factor() / 8)) { + accept_event(); + } + + } break; + case MouseButton::WHEEL_LEFT: { + if (_scroll(true, -mb->get_factor() / 8)) { + accept_event(); + } + + } break; + case MouseButton::WHEEL_RIGHT: { + if (_scroll(true, mb->get_factor() / 8)) { accept_event(); } @@ -4192,7 +4183,7 @@ int Tree::get_column_minimum_width(int p_column) const { // Check if the visible title of the column is wider. if (show_column_titles) { - min_width = MAX(cache.font->get_string_size(columns[p_column].title, cache.font_size).width + cache.bg->get_margin(SIDE_LEFT) + cache.bg->get_margin(SIDE_RIGHT), min_width); + min_width = MAX(cache.font->get_string_size(columns[p_column].title, HORIZONTAL_ALIGNMENT_LEFT, -1, cache.font_size).width + cache.bg->get_margin(SIDE_LEFT) + cache.bg->get_margin(SIDE_RIGHT), min_width); } if (!columns[p_column].clip_content) { @@ -4471,32 +4462,6 @@ Control::TextDirection Tree::get_column_title_direction(int p_column) const { return columns[p_column].text_direction; } -void Tree::clear_column_title_opentype_features(int p_column) { - ERR_FAIL_INDEX(p_column, columns.size()); - columns.write[p_column].opentype_features.clear(); - update_column(p_column); - update(); -} - -void Tree::set_column_title_opentype_feature(int p_column, const String &p_name, int p_value) { - ERR_FAIL_INDEX(p_column, columns.size()); - int32_t tag = TS->name_to_tag(p_name); - if (!columns[p_column].opentype_features.has(tag) || (int)columns[p_column].opentype_features[tag] != p_value) { - columns.write[p_column].opentype_features[tag] = p_value; - update_column(p_column); - update(); - } -} - -int Tree::get_column_title_opentype_feature(int p_column, const String &p_name) const { - ERR_FAIL_INDEX_V(p_column, columns.size(), -1); - int32_t tag = TS->name_to_tag(p_name); - if (!columns[p_column].opentype_features.has(tag)) { - return -1; - } - return columns[p_column].opentype_features[tag]; -} - void Tree::set_column_title_language(int p_column, const String &p_language) { ERR_FAIL_INDEX(p_column, columns.size()); if (columns[p_column].language != p_language) { @@ -4983,10 +4948,6 @@ void Tree::_bind_methods() { ClassDB::bind_method(D_METHOD("set_column_title_direction", "column", "direction"), &Tree::set_column_title_direction); ClassDB::bind_method(D_METHOD("get_column_title_direction", "column"), &Tree::get_column_title_direction); - ClassDB::bind_method(D_METHOD("set_column_title_opentype_feature", "column", "tag", "value"), &Tree::set_column_title_opentype_feature); - ClassDB::bind_method(D_METHOD("get_column_title_opentype_feature", "column", "tag"), &Tree::get_column_title_opentype_feature); - ClassDB::bind_method(D_METHOD("clear_column_title_opentype_features", "column"), &Tree::clear_column_title_opentype_features); - ClassDB::bind_method(D_METHOD("set_column_title_language", "column", "language"), &Tree::set_column_title_language); ClassDB::bind_method(D_METHOD("get_column_title_language", "column"), &Tree::get_column_title_language); @@ -5063,7 +5024,7 @@ Tree::Tree() { popup_editor_vb = memnew(VBoxContainer); popup_editor->add_child(popup_editor_vb); popup_editor_vb->add_theme_constant_override("separation", 0); - popup_editor_vb->set_anchors_and_offsets_preset(PRESET_WIDE); + popup_editor_vb->set_anchors_and_offsets_preset(PRESET_FULL_RECT); text_editor = memnew(LineEdit); popup_editor_vb->add_child(text_editor); text_editor->set_v_size_flags(SIZE_EXPAND_FILL); diff --git a/scene/gui/tree.h b/scene/gui/tree.h index 0a8dd3204a..f0819e2980 100644 --- a/scene/gui/tree.h +++ b/scene/gui/tree.h @@ -63,7 +63,6 @@ private: String text; String suffix; Ref<TextLine> text_buf; - Dictionary opentype_features; String language; TextServer::StructuredTextParser st_parser = TextServer::STRUCTURED_TEXT_DEFAULT; Array st_args; @@ -220,10 +219,6 @@ public: void set_text_direction(int p_column, Control::TextDirection p_text_direction); Control::TextDirection get_text_direction(int p_column) const; - void set_opentype_feature(int p_column, const String &p_name, int p_value); - int get_opentype_feature(int p_column, const String &p_name) const; - void clear_opentype_features(int p_column); - void set_structured_text_bidi_override(int p_column, TextServer::StructuredTextParser p_parser); TextServer::StructuredTextParser get_structured_text_bidi_override(int p_column) const; @@ -429,7 +424,6 @@ private: bool clip_content = false; String title; Ref<TextLine> text_buf; - Dictionary opentype_features; String language; Control::TextDirection text_direction = Control::TEXT_DIRECTION_INHERITED; ColumnInfo() { @@ -616,6 +610,8 @@ private: void _go_down(); void _go_up(); + bool _scroll(bool p_horizontal, float p_pages); + protected: static void _bind_methods(); @@ -666,10 +662,6 @@ public: void set_column_title_direction(int p_column, Control::TextDirection p_text_direction); Control::TextDirection get_column_title_direction(int p_column) const; - void set_column_title_opentype_feature(int p_column, const String &p_name, int p_value); - int get_column_title_opentype_feature(int p_column, const String &p_name) const; - void clear_column_title_opentype_features(int p_column); - void set_column_title_language(int p_column, const String &p_language); String get_column_title_language(int p_column) const; @@ -727,4 +719,5 @@ public: VARIANT_ENUM_CAST(Tree::SelectMode); VARIANT_ENUM_CAST(Tree::DropModeFlags); -#endif + +#endif // TREE_H diff --git a/scene/gui/video_stream_player.cpp b/scene/gui/video_stream_player.cpp index 86334882fa..f20a2ad67b 100644 --- a/scene/gui/video_stream_player.cpp +++ b/scene/gui/video_stream_player.cpp @@ -225,7 +225,7 @@ void VideoStreamPlayer::set_stream(const Ref<VideoStream> &p_stream) { stream = p_stream; if (stream.is_valid()) { stream->set_audio_track(audio_track); - playback = stream->instance_playback(); + playback = stream->instantiate_playback(); } else { playback = Ref<VideoStreamPlayback>(); } diff --git a/scene/gui/video_stream_player.h b/scene/gui/video_stream_player.h index d2822a989b..913e7905b6 100644 --- a/scene/gui/video_stream_player.h +++ b/scene/gui/video_stream_player.h @@ -64,7 +64,7 @@ class VideoStreamPlayer : public Control { bool autoplay = false; float volume = 1.0; double last_audio_time = 0.0; - bool expand = true; + bool expand = false; bool loops = false; int buffering_ms = 500; int audio_track = 0; diff --git a/scene/gui/view_panner.cpp b/scene/gui/view_panner.cpp index 892d0aba29..3b7f499a07 100644 --- a/scene/gui/view_panner.cpp +++ b/scene/gui/view_panner.cpp @@ -135,7 +135,7 @@ void ViewPanner::callback_helper(Callable p_callback, Vector<Variant> p_args) { Variant result; Callable::CallError ce; - p_callback.call(argptr, p_args.size(), result, ce); + p_callback.callp(argptr, p_args.size(), result, ce); } void ViewPanner::set_callbacks(Callable p_scroll_callback, Callable p_pan_callback, Callable p_zoom_callback) { |