summaryrefslogtreecommitdiff
path: root/scene/gui
diff options
context:
space:
mode:
Diffstat (limited to 'scene/gui')
-rw-r--r--scene/gui/base_button.cpp38
-rw-r--r--scene/gui/base_button.h4
-rw-r--r--scene/gui/code_edit.cpp32
-rw-r--r--scene/gui/color_picker.cpp6
-rw-r--r--scene/gui/control.cpp51
-rw-r--r--scene/gui/control.h14
-rw-r--r--scene/gui/graph_edit.cpp8
-rw-r--r--scene/gui/item_list.cpp18
-rw-r--r--scene/gui/label.cpp32
-rw-r--r--scene/gui/label.h1
-rw-r--r--scene/gui/line_edit.cpp9
-rw-r--r--scene/gui/menu_bar.cpp40
-rw-r--r--scene/gui/menu_bar.h5
-rw-r--r--scene/gui/menu_button.cpp4
-rw-r--r--scene/gui/popup.cpp38
-rw-r--r--scene/gui/popup.h1
-rw-r--r--scene/gui/popup_menu.cpp10
-rw-r--r--scene/gui/rich_text_label.cpp65
-rw-r--r--scene/gui/rich_text_label.h2
-rw-r--r--scene/gui/scroll_bar.cpp14
-rw-r--r--scene/gui/slider.cpp4
-rw-r--r--scene/gui/text_edit.cpp88
-rw-r--r--scene/gui/text_edit.h4
-rw-r--r--scene/gui/tree.cpp17
-rw-r--r--scene/gui/view_panner.cpp4
25 files changed, 310 insertions, 199 deletions
diff --git a/scene/gui/base_button.cpp b/scene/gui/base_button.cpp
index af6a99ca62..552345e4fe 100644
--- a/scene/gui/base_button.cpp
+++ b/scene/gui/base_button.cpp
@@ -60,7 +60,7 @@ void BaseButton::gui_input(const Ref<InputEvent> &p_event) {
}
Ref<InputEventMouseButton> mouse_button = p_event;
- bool ui_accept = p_event->is_action("ui_accept") && !p_event->is_echo();
+ bool ui_accept = p_event->is_action("ui_accept", true) && !p_event->is_echo();
bool button_masked = mouse_button.is_valid() && (mouse_button_to_mask(mouse_button->get_button_index()) & button_mask) != MouseButton::NONE;
if (button_masked || ui_accept) {
@@ -349,10 +349,6 @@ Ref<Shortcut> BaseButton::get_shortcut() const {
void BaseButton::shortcut_input(const Ref<InputEvent> &p_event) {
ERR_FAIL_COND(p_event.is_null());
- if (!_is_focus_owner_in_shortcut_context()) {
- return;
- }
-
if (!is_disabled() && is_visible_in_tree() && !p_event->is_echo() && shortcut.is_valid() && shortcut->matches_event(p_event)) {
on_action_event(p_event);
accept_event();
@@ -389,34 +385,6 @@ Ref<ButtonGroup> BaseButton::get_button_group() const {
return button_group;
}
-void BaseButton::set_shortcut_context(Node *p_node) {
- if (p_node != nullptr) {
- shortcut_context = p_node->get_instance_id();
- } else {
- shortcut_context = ObjectID();
- }
-}
-
-Node *BaseButton::get_shortcut_context() const {
- Object *ctx_obj = ObjectDB::get_instance(shortcut_context);
- Node *ctx_node = Object::cast_to<Node>(ctx_obj);
-
- return ctx_node;
-}
-
-bool BaseButton::_is_focus_owner_in_shortcut_context() const {
- if (shortcut_context == ObjectID()) {
- // No context, therefore global - always "in" context.
- return true;
- }
-
- Node *ctx_node = get_shortcut_context();
- Control *vp_focus = get_viewport() ? get_viewport()->gui_get_focus_owner() : nullptr;
-
- // If the context is valid and the viewport focus is valid, check if the context is the focus or is a parent of it.
- return ctx_node && vp_focus && (ctx_node == vp_focus || ctx_node->is_ancestor_of(vp_focus));
-}
-
bool BaseButton::_was_pressed_by_mouse() const {
return was_mouse_pressed;
}
@@ -446,9 +414,6 @@ void BaseButton::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_button_group", "button_group"), &BaseButton::set_button_group);
ClassDB::bind_method(D_METHOD("get_button_group"), &BaseButton::get_button_group);
- ClassDB::bind_method(D_METHOD("set_shortcut_context", "node"), &BaseButton::set_shortcut_context);
- ClassDB::bind_method(D_METHOD("get_shortcut_context"), &BaseButton::get_shortcut_context);
-
GDVIRTUAL_BIND(_pressed);
GDVIRTUAL_BIND(_toggled, "button_pressed");
@@ -466,7 +431,6 @@ void BaseButton::_bind_methods() {
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "keep_pressed_outside"), "set_keep_pressed_outside", "is_keep_pressed_outside");
ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "shortcut", PROPERTY_HINT_RESOURCE_TYPE, "Shortcut"), "set_shortcut", "get_shortcut");
ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "button_group", PROPERTY_HINT_RESOURCE_TYPE, "ButtonGroup"), "set_button_group", "get_button_group");
- ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "shortcut_context", PROPERTY_HINT_NODE_TYPE, "Node"), "set_shortcut_context", "get_shortcut_context");
BIND_ENUM_CONSTANT(DRAW_NORMAL);
BIND_ENUM_CONSTANT(DRAW_PRESSED);
diff --git a/scene/gui/base_button.h b/scene/gui/base_button.h
index c83b08aadf..7839239800 100644
--- a/scene/gui/base_button.h
+++ b/scene/gui/base_button.h
@@ -81,7 +81,6 @@ protected:
virtual void shortcut_input(const Ref<InputEvent> &p_event) override;
void _notification(int p_what);
- bool _is_focus_owner_in_shortcut_context() const;
bool _was_pressed_by_mouse() const;
GDVIRTUAL0(_pressed)
@@ -132,9 +131,6 @@ public:
void set_button_group(const Ref<ButtonGroup> &p_group);
Ref<ButtonGroup> get_button_group() const;
- void set_shortcut_context(Node *p_node);
- Node *get_shortcut_context() const;
-
BaseButton();
~BaseButton();
};
diff --git a/scene/gui/code_edit.cpp b/scene/gui/code_edit.cpp
index 70707dba11..8a5d04f49c 100644
--- a/scene/gui/code_edit.cpp
+++ b/scene/gui/code_edit.cpp
@@ -1248,37 +1248,41 @@ bool CodeEdit::is_drawing_executing_lines_gutter() const {
}
void CodeEdit::_main_gutter_draw_callback(int p_line, int p_gutter, const Rect2 &p_region) {
- bool shift_pressed = Input::get_singleton()->is_key_pressed(Key::SHIFT);
-
if (draw_breakpoints && breakpoint_icon.is_valid()) {
- bool hovering = p_region.has_point(get_local_mouse_pos());
bool breakpointed = is_line_breakpointed(p_line);
+ bool hovering = p_region.has_point(get_local_mouse_pos());
+ bool shift_pressed = Input::get_singleton()->is_key_pressed(Key::SHIFT);
if (breakpointed || (hovering && !is_dragging_cursor() && !shift_pressed)) {
int padding = p_region.size.x / 6;
+
+ Color use_color = breakpoint_color;
+ if (hovering && !shift_pressed) {
+ use_color = breakpointed ? use_color.lightened(0.3) : use_color.darkened(0.5);
+ }
Rect2 icon_region = p_region;
icon_region.position += Point2(padding, padding);
icon_region.size -= Point2(padding, padding) * 2;
-
- // Darken icon when hovering, shift not pressed & not yet breakpointed.
- Color use_color = hovering && !breakpointed && !shift_pressed ? breakpoint_color.darkened(0.4) : breakpoint_color;
breakpoint_icon->draw_rect(get_canvas_item(), icon_region, false, use_color);
}
}
if (draw_bookmarks && bookmark_icon.is_valid()) {
- bool hovering = p_region.has_point(get_local_mouse_pos());
bool bookmarked = is_line_bookmarked(p_line);
+ bool hovering = p_region.has_point(get_local_mouse_pos());
+ bool shift_pressed = Input::get_singleton()->is_key_pressed(Key::SHIFT);
if (bookmarked || (hovering && !is_dragging_cursor() && shift_pressed)) {
int horizontal_padding = p_region.size.x / 2;
int vertical_padding = p_region.size.y / 4;
+
+ Color use_color = bookmark_color;
+ if (hovering && shift_pressed) {
+ use_color = bookmarked ? use_color.lightened(0.3) : use_color.darkened(0.5);
+ }
Rect2 icon_region = p_region;
icon_region.position += Point2(horizontal_padding, 0);
icon_region.size -= Point2(horizontal_padding * 1.1, vertical_padding);
-
- // Darken icon when hovering, shift pressed & not yet bookmarked.
- Color use_color = hovering && !bookmarked && shift_pressed ? bookmark_color.darkened(0.4) : bookmark_color;
bookmark_icon->draw_rect(get_canvas_item(), icon_region, false, use_color);
}
}
@@ -1287,10 +1291,10 @@ void CodeEdit::_main_gutter_draw_callback(int p_line, int p_gutter, const Rect2
int horizontal_padding = p_region.size.x / 10;
int vertical_padding = p_region.size.y / 4;
- Rect2 executing_line_region = p_region;
- executing_line_region.position += Point2(horizontal_padding, vertical_padding);
- executing_line_region.size -= Point2(horizontal_padding, vertical_padding) * 2;
- executing_line_icon->draw_rect(get_canvas_item(), executing_line_region, false, executing_line_color);
+ Rect2 icon_region = p_region;
+ icon_region.position += Point2(horizontal_padding, vertical_padding);
+ icon_region.size -= Point2(horizontal_padding, vertical_padding) * 2;
+ executing_line_icon->draw_rect(get_canvas_item(), icon_region, false, executing_line_color);
}
}
diff --git a/scene/gui/color_picker.cpp b/scene/gui/color_picker.cpp
index f1e18640e6..929bf27be6 100644
--- a/scene/gui/color_picker.cpp
+++ b/scene/gui/color_picker.cpp
@@ -1367,7 +1367,7 @@ void ColorPicker::_screen_input(const Ref<InputEvent> &p_event) {
Ref<Image> img = r->get_texture()->get_image();
if (img.is_valid() && !img->is_empty()) {
- Vector2 ofs = mev->get_global_position() - r->get_visible_rect().get_position();
+ Vector2 ofs = mev->get_global_position();
Color c = img->get_pixel(ofs.x, ofs.y);
set_pick_color(c);
@@ -1403,9 +1403,7 @@ void ColorPicker::_screen_pick_pressed() {
screen->show();
}
screen->move_to_front();
-#ifndef _MSC_VER
-#warning show modal no longer works, needs to be converted to a popup
-#endif
+ // TODO: show modal no longer works, needs to be converted to a popup.
//screen->show_modal();
}
diff --git a/scene/gui/control.cpp b/scene/gui/control.cpp
index 2dcae2553c..2ec0a48278 100644
--- a/scene/gui/control.cpp
+++ b/scene/gui/control.cpp
@@ -1436,13 +1436,6 @@ Rect2 Control::get_screen_rect() const {
return r;
}
-Rect2 Control::get_window_rect() const {
- ERR_FAIL_COND_V(!is_inside_tree(), Rect2());
- Rect2 gr = get_global_rect();
- gr.position += get_viewport()->get_visible_rect().position;
- return gr;
-}
-
Rect2 Control::get_anchorable_rect() const {
return Rect2(Point2(), get_size());
}
@@ -1564,7 +1557,7 @@ Size2 Control::get_minimum_size() const {
return Vector2();
}
-void Control::set_custom_minimum_size(const Size2i &p_custom) {
+void Control::set_custom_minimum_size(const Size2 &p_custom) {
if (p_custom == data.custom_minimum_size) {
return;
}
@@ -1572,7 +1565,7 @@ void Control::set_custom_minimum_size(const Size2i &p_custom) {
update_minimum_size();
}
-Size2i Control::get_custom_minimum_size() const {
+Size2 Control::get_custom_minimum_size() const {
return data.custom_minimum_size;
}
@@ -1759,6 +1752,34 @@ void Control::warp_mouse(const Point2 &p_position) {
get_viewport()->warp_mouse(get_global_transform_with_canvas().xform(p_position));
}
+void Control::set_shortcut_context(const Node *p_node) {
+ if (p_node != nullptr) {
+ data.shortcut_context = p_node->get_instance_id();
+ } else {
+ data.shortcut_context = ObjectID();
+ }
+}
+
+Node *Control::get_shortcut_context() const {
+ Object *ctx_obj = ObjectDB::get_instance(data.shortcut_context);
+ Node *ctx_node = Object::cast_to<Node>(ctx_obj);
+
+ return ctx_node;
+}
+
+bool Control::is_focus_owner_in_shortcut_context() const {
+ if (data.shortcut_context == ObjectID()) {
+ // No context, therefore global - always "in" context.
+ return true;
+ }
+
+ const Node *ctx_node = get_shortcut_context();
+ const Control *vp_focus = get_viewport() ? get_viewport()->gui_get_focus_owner() : nullptr;
+
+ // If the context is valid and the viewport focus is valid, check if the context is the focus or is a parent of it.
+ return ctx_node && vp_focus && (ctx_node == vp_focus || ctx_node->is_ancestor_of(vp_focus));
+}
+
// Drag and drop handling.
void Control::set_drag_forwarding(Object *p_target) {
@@ -2885,8 +2906,8 @@ void Control::_notification(int p_notification) {
if (data.parent_canvas_item) {
data.parent_canvas_item->disconnect("item_rect_changed", callable_mp(this, &Control::_size_changed));
data.parent_canvas_item = nullptr;
- } else if (!is_set_as_top_level()) {
- //disconnect viewport
+ } else {
+ // Disconnect viewport.
Viewport *viewport = get_viewport();
ERR_FAIL_COND(!viewport);
viewport->disconnect("size_changed", callable_mp(this, &Control::_size_changed));
@@ -3133,6 +3154,9 @@ void Control::_bind_methods() {
ClassDB::bind_method(D_METHOD("warp_mouse", "position"), &Control::warp_mouse);
+ ClassDB::bind_method(D_METHOD("set_shortcut_context", "node"), &Control::set_shortcut_context);
+ ClassDB::bind_method(D_METHOD("get_shortcut_context"), &Control::get_shortcut_context);
+
ClassDB::bind_method(D_METHOD("update_minimum_size"), &Control::update_minimum_size);
ClassDB::bind_method(D_METHOD("set_layout_direction", "direction"), &Control::set_layout_direction);
@@ -3144,7 +3168,7 @@ void Control::_bind_methods() {
ADD_GROUP("Layout", "");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "clip_contents"), "set_clip_contents", "is_clipping_contents");
- ADD_PROPERTY(PropertyInfo(Variant::VECTOR2I, "custom_minimum_size", PROPERTY_HINT_NONE, "suffix:px"), "set_custom_minimum_size", "get_custom_minimum_size");
+ ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "custom_minimum_size", PROPERTY_HINT_NONE, "suffix:px"), "set_custom_minimum_size", "get_custom_minimum_size");
ADD_PROPERTY(PropertyInfo(Variant::INT, "layout_direction", PROPERTY_HINT_ENUM, "Inherited,Locale,Left-to-Right,Right-to-Left"), "set_layout_direction", "get_layout_direction");
ADD_PROPERTY(PropertyInfo(Variant::INT, "layout_mode", PROPERTY_HINT_ENUM, "Position,Anchors,Container,Uncontrolled", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_INTERNAL), "_set_layout_mode", "_get_layout_mode");
ADD_PROPERTY_DEFAULT("layout_mode", LayoutMode::LAYOUT_MODE_POSITION);
@@ -3206,6 +3230,9 @@ void Control::_bind_methods() {
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "mouse_force_pass_scroll_events"), "set_force_pass_scroll_events", "is_force_pass_scroll_events");
ADD_PROPERTY(PropertyInfo(Variant::INT, "mouse_default_cursor_shape", PROPERTY_HINT_ENUM, "Arrow,I-Beam,Pointing Hand,Cross,Wait,Busy,Drag,Can Drop,Forbidden,Vertical Resize,Horizontal Resize,Secondary Diagonal Resize,Main Diagonal Resize,Move,Vertical Split,Horizontal Split,Help"), "set_default_cursor_shape", "get_default_cursor_shape");
+ ADD_GROUP("Input", "");
+ ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "shortcut_context", PROPERTY_HINT_NODE_TYPE, "Node"), "set_shortcut_context", "get_shortcut_context");
+
ADD_GROUP("Theme", "theme_");
ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "theme", PROPERTY_HINT_RESOURCE_TYPE, "Theme"), "set_theme", "get_theme");
ADD_PROPERTY(PropertyInfo(Variant::STRING, "theme_type_variation", PROPERTY_HINT_ENUM_SUGGESTION), "set_theme_type_variation", "get_theme_type_variation");
diff --git a/scene/gui/control.h b/scene/gui/control.h
index ee6443c81c..72e870930d 100644
--- a/scene/gui/control.h
+++ b/scene/gui/control.h
@@ -159,6 +159,7 @@ private:
}
};
+ // This Data struct is to avoid namespace pollution in derived classes.
struct Data {
// Global relations.
@@ -200,7 +201,7 @@ private:
int h_size_flags = SIZE_FILL;
int v_size_flags = SIZE_FILL;
real_t expand = 1.0;
- Point2i custom_minimum_size;
+ Point2 custom_minimum_size;
// Input events and rendering.
@@ -218,6 +219,8 @@ private:
NodePath focus_next;
NodePath focus_prev;
+ ObjectID shortcut_context;
+
// Theming.
ThemeOwner *theme_owner = nullptr;
@@ -442,7 +445,6 @@ public:
Rect2 get_rect() const;
Rect2 get_global_rect() const;
Rect2 get_screen_rect() const;
- Rect2 get_window_rect() const; ///< use with care, as it blocks waiting for the rendering server
Rect2 get_anchorable_rect() const override;
void set_scale(const Vector2 &p_scale);
@@ -460,8 +462,8 @@ public:
virtual Size2 get_minimum_size() const;
virtual Size2 get_combined_minimum_size() const;
- void set_custom_minimum_size(const Size2i &p_custom);
- Size2i get_custom_minimum_size() const;
+ void set_custom_minimum_size(const Size2 &p_custom);
+ Size2 get_custom_minimum_size() const;
// Container sizing.
@@ -487,6 +489,10 @@ public:
void warp_mouse(const Point2 &p_position);
+ bool is_focus_owner_in_shortcut_context() const;
+ void set_shortcut_context(const Node *p_node);
+ Node *get_shortcut_context() const;
+
// Drag and drop handling.
virtual void set_drag_forwarding(Object *p_target);
diff --git a/scene/gui/graph_edit.cpp b/scene/gui/graph_edit.cpp
index 90552c32c2..caf8db4515 100644
--- a/scene/gui/graph_edit.cpp
+++ b/scene/gui/graph_edit.cpp
@@ -1385,16 +1385,16 @@ void GraphEdit::gui_input(const Ref<InputEvent> &p_ev) {
}
if (p_ev->is_pressed()) {
- if (p_ev->is_action("ui_graph_duplicate")) {
+ if (p_ev->is_action("ui_graph_duplicate", true)) {
emit_signal(SNAME("duplicate_nodes_request"));
accept_event();
- } else if (p_ev->is_action("ui_copy")) {
+ } else if (p_ev->is_action("ui_copy", true)) {
emit_signal(SNAME("copy_nodes_request"));
accept_event();
- } else if (p_ev->is_action("ui_paste")) {
+ } else if (p_ev->is_action("ui_paste", true)) {
emit_signal(SNAME("paste_nodes_request"));
accept_event();
- } else if (p_ev->is_action("ui_graph_delete")) {
+ } else if (p_ev->is_action("ui_graph_delete", true)) {
TypedArray<StringName> nodes;
for (int i = 0; i < get_child_count(); i++) {
diff --git a/scene/gui/item_list.cpp b/scene/gui/item_list.cpp
index 0dc791f71d..223428906a 100644
--- a/scene/gui/item_list.cpp
+++ b/scene/gui/item_list.cpp
@@ -728,7 +728,7 @@ void ItemList::gui_input(const Ref<InputEvent> &p_event) {
}
if (p_event->is_pressed() && items.size() > 0) {
- if (p_event->is_action("ui_up")) {
+ if (p_event->is_action("ui_up", true)) {
if (!search_string.is_empty()) {
uint64_t now = OS::get_singleton()->get_ticks_msec();
uint64_t diff = now - search_time_msec;
@@ -766,7 +766,7 @@ void ItemList::gui_input(const Ref<InputEvent> &p_event) {
}
accept_event();
}
- } else if (p_event->is_action("ui_down")) {
+ } else if (p_event->is_action("ui_down", true)) {
if (!search_string.is_empty()) {
uint64_t now = OS::get_singleton()->get_ticks_msec();
uint64_t diff = now - search_time_msec;
@@ -803,7 +803,7 @@ void ItemList::gui_input(const Ref<InputEvent> &p_event) {
}
accept_event();
}
- } else if (p_event->is_action("ui_page_up")) {
+ } else if (p_event->is_action("ui_page_up", true)) {
search_string = ""; //any mousepress cancels
for (int i = 4; i > 0; i--) {
@@ -817,7 +817,7 @@ void ItemList::gui_input(const Ref<InputEvent> &p_event) {
break;
}
}
- } else if (p_event->is_action("ui_page_down")) {
+ } else if (p_event->is_action("ui_page_down", true)) {
search_string = ""; //any mousepress cancels
for (int i = 4; i > 0; i--) {
@@ -832,7 +832,7 @@ void ItemList::gui_input(const Ref<InputEvent> &p_event) {
break;
}
}
- } else if (p_event->is_action("ui_left")) {
+ } else if (p_event->is_action("ui_left", true)) {
search_string = ""; //any mousepress cancels
if (current % current_columns != 0) {
@@ -852,7 +852,7 @@ void ItemList::gui_input(const Ref<InputEvent> &p_event) {
}
accept_event();
}
- } else if (p_event->is_action("ui_right")) {
+ } else if (p_event->is_action("ui_right", true)) {
search_string = ""; //any mousepress cancels
if (current % current_columns != (current_columns - 1) && current + 1 < items.size()) {
@@ -872,9 +872,9 @@ void ItemList::gui_input(const Ref<InputEvent> &p_event) {
}
accept_event();
}
- } else if (p_event->is_action("ui_cancel")) {
+ } else if (p_event->is_action("ui_cancel", true)) {
search_string = "";
- } else if (p_event->is_action("ui_select") && select_mode == SELECT_MULTI) {
+ } else if (p_event->is_action("ui_select", true) && select_mode == SELECT_MULTI) {
if (current >= 0 && current < items.size()) {
if (items[current].selectable && !items[current].disabled && !items[current].selected) {
select(current, false);
@@ -884,7 +884,7 @@ void ItemList::gui_input(const Ref<InputEvent> &p_event) {
emit_signal(SNAME("multi_selected"), current, false);
}
}
- } else if (p_event->is_action("ui_accept")) {
+ } else if (p_event->is_action("ui_accept", true)) {
search_string = ""; //any mousepress cancels
if (current >= 0 && current < items.size()) {
diff --git a/scene/gui/label.cpp b/scene/gui/label.cpp
index 63401ca195..2203573bbc 100644
--- a/scene/gui/label.cpp
+++ b/scene/gui/label.cpp
@@ -288,6 +288,36 @@ void Label::_update_theme_item_cache() {
theme_cache.font_shadow_outline_size = get_theme_constant(SNAME("shadow_outline_size"));
}
+PackedStringArray Label::get_configuration_warnings() const {
+ PackedStringArray warnings = Control::get_configuration_warnings();
+
+ // Ensure that the font can render all of the required glyphs.
+ Ref<Font> font;
+ if (settings.is_valid()) {
+ font = settings->get_font();
+ }
+ if (font.is_null()) {
+ font = theme_cache.font;
+ }
+
+ if (font.is_valid()) {
+ if (dirty || font_dirty || lines_dirty) {
+ const_cast<Label *>(this)->_shape();
+ }
+
+ const Glyph *glyph = TS->shaped_text_get_glyphs(text_rid);
+ int64_t glyph_count = TS->shaped_text_get_glyph_count(text_rid);
+ for (int64_t i = 0; i < glyph_count; i++) {
+ if (glyph[i].font_rid == RID()) {
+ warnings.push_back(RTR("The current font does not support rendering one or more characters used in this Label's text."));
+ break;
+ }
+ }
+ }
+
+ return warnings;
+}
+
void Label::_notification(int p_what) {
switch (p_what) {
case NOTIFICATION_TRANSLATION_CHANGED: {
@@ -302,6 +332,7 @@ void Label::_notification(int p_what) {
dirty = true;
queue_redraw();
+ update_configuration_warnings();
} break;
case NOTIFICATION_LAYOUT_DIRECTION_CHANGED: {
@@ -674,6 +705,7 @@ void Label::set_text(const String &p_string) {
}
queue_redraw();
update_minimum_size();
+ update_configuration_warnings();
}
void Label::_invalidate() {
diff --git a/scene/gui/label.h b/scene/gui/label.h
index b79c94a5ba..41d7049b22 100644
--- a/scene/gui/label.h
+++ b/scene/gui/label.h
@@ -93,6 +93,7 @@ protected:
public:
virtual Size2 get_minimum_size() const override;
+ virtual PackedStringArray get_configuration_warnings() const override;
void set_horizontal_alignment(HorizontalAlignment p_alignment);
HorizontalAlignment get_horizontal_alignment() const;
diff --git a/scene/gui/line_edit.cpp b/scene/gui/line_edit.cpp
index 36e85b5d02..65670b7786 100644
--- a/scene/gui/line_edit.cpp
+++ b/scene/gui/line_edit.cpp
@@ -2175,6 +2175,12 @@ void LineEdit::_emit_text_change() {
}
void LineEdit::_shape() {
+ const Ref<Font> &font = theme_cache.font;
+ int font_size = theme_cache.font_size;
+ if (font.is_null()) {
+ return;
+ }
+
Size2 old_size = TS->shaped_text_get_size(text_rid);
TS->shaped_text_clear(text_rid);
@@ -2197,9 +2203,6 @@ void LineEdit::_shape() {
}
TS->shaped_text_set_preserve_control(text_rid, draw_control_chars);
- const Ref<Font> &font = theme_cache.font;
- int font_size = theme_cache.font_size;
- ERR_FAIL_COND(font.is_null());
TS->shaped_text_add_string(text_rid, t, font->get_rids(), font_size, font->get_opentype_features(), language);
for (int i = 0; i < TextServer::SPACING_MAX; i++) {
TS->shaped_text_set_spacing(text_rid, TextServer::SpacingType(i), font->get_spacing(TextServer::SpacingType(i)));
diff --git a/scene/gui/menu_bar.cpp b/scene/gui/menu_bar.cpp
index 742042f65f..82ef53e317 100644
--- a/scene/gui/menu_bar.cpp
+++ b/scene/gui/menu_bar.cpp
@@ -41,7 +41,7 @@ void MenuBar::gui_input(const Ref<InputEvent> &p_event) {
}
MutexLock lock(mutex);
- if (p_event->is_action("ui_left") && p_event->is_pressed()) {
+ if (p_event->is_action("ui_left", true) && p_event->is_pressed()) {
int new_sel = selected_menu;
int old_sel = (selected_menu < 0) ? 0 : selected_menu;
do {
@@ -63,7 +63,7 @@ void MenuBar::gui_input(const Ref<InputEvent> &p_event) {
_open_popup(selected_menu, true);
}
return;
- } else if (p_event->is_action("ui_right") && p_event->is_pressed()) {
+ } else if (p_event->is_action("ui_right", true) && p_event->is_pressed()) {
int new_sel = selected_menu;
int old_sel = (selected_menu < 0) ? menu_cache.size() - 1 : selected_menu;
do {
@@ -149,10 +149,6 @@ void MenuBar::_open_popup(int p_index, bool p_focus_item) {
void MenuBar::shortcut_input(const Ref<InputEvent> &p_event) {
ERR_FAIL_COND(p_event.is_null());
- if (!_is_focus_owner_in_shortcut_context()) {
- return;
- }
-
if (disable_shortcuts) {
return;
}
@@ -175,34 +171,6 @@ void MenuBar::shortcut_input(const Ref<InputEvent> &p_event) {
}
}
-void MenuBar::set_shortcut_context(Node *p_node) {
- if (p_node != nullptr) {
- shortcut_context = p_node->get_instance_id();
- } else {
- shortcut_context = ObjectID();
- }
-}
-
-Node *MenuBar::get_shortcut_context() const {
- Object *ctx_obj = ObjectDB::get_instance(shortcut_context);
- Node *ctx_node = Object::cast_to<Node>(ctx_obj);
-
- return ctx_node;
-}
-
-bool MenuBar::_is_focus_owner_in_shortcut_context() const {
- if (shortcut_context == ObjectID()) {
- // No context, therefore global - always "in" context.
- return true;
- }
-
- Node *ctx_node = get_shortcut_context();
- Control *vp_focus = get_viewport() ? get_viewport()->gui_get_focus_owner() : nullptr;
-
- // If the context is valid and the viewport focus is valid, check if the context is the focus or is a parent of it.
- return ctx_node && vp_focus && (ctx_node == vp_focus || ctx_node->is_ancestor_of(vp_focus));
-}
-
void MenuBar::_popup_visibility_changed(bool p_visible) {
if (!p_visible) {
active_menu = -1;
@@ -694,16 +662,12 @@ void MenuBar::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_menu_hidden", "menu", "hidden"), &MenuBar::set_menu_hidden);
ClassDB::bind_method(D_METHOD("is_menu_hidden", "menu"), &MenuBar::is_menu_hidden);
- ClassDB::bind_method(D_METHOD("set_shortcut_context", "node"), &MenuBar::set_shortcut_context);
- ClassDB::bind_method(D_METHOD("get_shortcut_context"), &MenuBar::get_shortcut_context);
-
ClassDB::bind_method(D_METHOD("get_menu_popup", "menu"), &MenuBar::get_menu_popup);
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "flat"), "set_flat", "is_flat");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "start_index"), "set_start_index", "get_start_index");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "switch_on_hover"), "set_switch_on_hover", "is_switch_on_hover");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "prefer_global_menu"), "set_prefer_global_menu", "is_prefer_global_menu");
- ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "shortcut_context", PROPERTY_HINT_NODE_TYPE, "Node"), "set_shortcut_context", "get_shortcut_context");
ADD_GROUP("BiDi", "");
ADD_PROPERTY(PropertyInfo(Variant::INT, "text_direction", PROPERTY_HINT_ENUM, "Auto,Left-to-Right,Right-to-Left,Inherited"), "set_text_direction", "get_text_direction");
diff --git a/scene/gui/menu_bar.h b/scene/gui/menu_bar.h
index f7ef19e98b..c057a7c96f 100644
--- a/scene/gui/menu_bar.h
+++ b/scene/gui/menu_bar.h
@@ -118,8 +118,6 @@ class MenuBar : public Control {
void _clear_menu();
void _update_menu();
- bool _is_focus_owner_in_shortcut_context() const;
-
protected:
virtual void shortcut_input(const Ref<InputEvent> &p_event) override;
@@ -170,9 +168,6 @@ public:
void set_menu_hidden(int p_menu, bool p_hidden);
bool is_menu_hidden(int p_menu) const;
- void set_shortcut_context(Node *p_node);
- Node *get_shortcut_context() const;
-
PopupMenu *get_menu_popup(int p_menu) const;
virtual void get_translatable_strings(List<String> *p_strings) const override;
diff --git a/scene/gui/menu_button.cpp b/scene/gui/menu_button.cpp
index 78aeab9457..786f23873e 100644
--- a/scene/gui/menu_button.cpp
+++ b/scene/gui/menu_button.cpp
@@ -36,10 +36,6 @@
void MenuButton::shortcut_input(const Ref<InputEvent> &p_event) {
ERR_FAIL_COND(p_event.is_null());
- if (!_is_focus_owner_in_shortcut_context()) {
- return;
- }
-
if (disable_shortcuts) {
return;
}
diff --git a/scene/gui/popup.cpp b/scene/gui/popup.cpp
index a6915f9e61..434eb87e7a 100644
--- a/scene/gui/popup.cpp
+++ b/scene/gui/popup.cpp
@@ -77,28 +77,36 @@ void Popup::_update_theme_item_cache() {
void Popup::_notification(int p_what) {
switch (p_what) {
case NOTIFICATION_VISIBILITY_CHANGED: {
- if (is_visible()) {
- _initialize_visible_parents();
- } else {
- _deinitialize_visible_parents();
- emit_signal(SNAME("popup_hide"));
- popped_up = false;
+ if (!is_in_edited_scene_root()) {
+ if (is_visible()) {
+ _initialize_visible_parents();
+ } else {
+ _deinitialize_visible_parents();
+ emit_signal(SNAME("popup_hide"));
+ popped_up = false;
+ }
}
} break;
case NOTIFICATION_WM_WINDOW_FOCUS_IN: {
- if (has_focus()) {
- popped_up = true;
+ if (!is_in_edited_scene_root()) {
+ if (has_focus()) {
+ popped_up = true;
+ }
}
} break;
case NOTIFICATION_EXIT_TREE: {
- _deinitialize_visible_parents();
+ if (!is_in_edited_scene_root()) {
+ _deinitialize_visible_parents();
+ }
} break;
case NOTIFICATION_WM_CLOSE_REQUEST:
case NOTIFICATION_APPLICATION_FOCUS_OUT: {
- _close_pressed();
+ if (!is_in_edited_scene_root()) {
+ _close_pressed();
+ }
} break;
}
}
@@ -126,6 +134,16 @@ void Popup::_bind_methods() {
ADD_SIGNAL(MethodInfo("popup_hide"));
}
+void Popup::_validate_property(PropertyInfo &p_property) const {
+ if (
+ p_property.name == "transient" ||
+ p_property.name == "exclusive" ||
+ p_property.name == "popup_window" ||
+ p_property.name == "unfocusable") {
+ p_property.usage = PROPERTY_USAGE_NO_EDITOR;
+ }
+}
+
Rect2i Popup::_popup_adjust_rect() const {
ERR_FAIL_COND_V(!is_inside_tree(), Rect2());
Rect2i parent_rect = get_usable_parent_rect();
diff --git a/scene/gui/popup.h b/scene/gui/popup.h
index 0d6ca25c18..57b811cadb 100644
--- a/scene/gui/popup.h
+++ b/scene/gui/popup.h
@@ -59,6 +59,7 @@ protected:
virtual void _update_theme_item_cache() override;
void _notification(int p_what);
static void _bind_methods();
+ void _validate_property(PropertyInfo &p_property) const;
virtual void _parent_focused();
diff --git a/scene/gui/popup_menu.cpp b/scene/gui/popup_menu.cpp
index 4cdde5902e..9a411ef7ed 100644
--- a/scene/gui/popup_menu.cpp
+++ b/scene/gui/popup_menu.cpp
@@ -273,7 +273,7 @@ void PopupMenu::gui_input(const Ref<InputEvent> &p_event) {
ERR_FAIL_COND(p_event.is_null());
if (!items.is_empty()) {
- if (p_event->is_action("ui_down") && p_event->is_pressed()) {
+ if (p_event->is_action("ui_down", true) && p_event->is_pressed()) {
int search_from = mouse_over + 1;
if (search_from >= items.size()) {
search_from = 0;
@@ -305,7 +305,7 @@ void PopupMenu::gui_input(const Ref<InputEvent> &p_event) {
}
}
}
- } else if (p_event->is_action("ui_up") && p_event->is_pressed()) {
+ } else if (p_event->is_action("ui_up", true) && p_event->is_pressed()) {
int search_from = mouse_over - 1;
if (search_from < 0) {
search_from = items.size() - 1;
@@ -337,7 +337,7 @@ void PopupMenu::gui_input(const Ref<InputEvent> &p_event) {
}
}
}
- } else if (p_event->is_action("ui_left") && p_event->is_pressed()) {
+ } else if (p_event->is_action("ui_left", true) && p_event->is_pressed()) {
Node *n = get_parent();
if (n) {
if (Object::cast_to<PopupMenu>(n)) {
@@ -349,7 +349,7 @@ void PopupMenu::gui_input(const Ref<InputEvent> &p_event) {
return;
}
}
- } else if (p_event->is_action("ui_right") && p_event->is_pressed()) {
+ } else if (p_event->is_action("ui_right", true) && p_event->is_pressed()) {
if (mouse_over >= 0 && mouse_over < items.size() && !items[mouse_over].separator && !items[mouse_over].submenu.is_empty() && submenu_over != mouse_over) {
_activate_submenu(mouse_over, true);
set_input_as_handled();
@@ -361,7 +361,7 @@ void PopupMenu::gui_input(const Ref<InputEvent> &p_event) {
return;
}
}
- } else if (p_event->is_action("ui_accept") && p_event->is_pressed()) {
+ } else if (p_event->is_action("ui_accept", true) && p_event->is_pressed()) {
if (mouse_over >= 0 && mouse_over < items.size() && !items[mouse_over].separator) {
if (!items[mouse_over].submenu.is_empty() && submenu_over != mouse_over) {
_activate_submenu(mouse_over, true);
diff --git a/scene/gui/rich_text_label.cpp b/scene/gui/rich_text_label.cpp
index 5060bacba5..c96d3c763d 100644
--- a/scene/gui/rich_text_label.cpp
+++ b/scene/gui/rich_text_label.cpp
@@ -2019,36 +2019,36 @@ void RichTextLabel::gui_input(const Ref<InputEvent> &p_event) {
if (k->is_pressed()) {
bool handled = false;
- if (k->is_action("ui_page_up") && vscroll->is_visible_in_tree()) {
+ if (k->is_action("ui_page_up", true) && vscroll->is_visible_in_tree()) {
vscroll->set_value(vscroll->get_value() - vscroll->get_page());
handled = true;
}
- if (k->is_action("ui_page_down") && vscroll->is_visible_in_tree()) {
+ if (k->is_action("ui_page_down", true) && vscroll->is_visible_in_tree()) {
vscroll->set_value(vscroll->get_value() + vscroll->get_page());
handled = true;
}
- if (k->is_action("ui_up") && vscroll->is_visible_in_tree()) {
+ if (k->is_action("ui_up", true) && vscroll->is_visible_in_tree()) {
vscroll->set_value(vscroll->get_value() - theme_cache.normal_font->get_height(theme_cache.normal_font_size));
handled = true;
}
- if (k->is_action("ui_down") && vscroll->is_visible_in_tree()) {
+ if (k->is_action("ui_down", true) && vscroll->is_visible_in_tree()) {
vscroll->set_value(vscroll->get_value() + theme_cache.normal_font->get_height(theme_cache.normal_font_size));
handled = true;
}
- if (k->is_action("ui_home") && vscroll->is_visible_in_tree()) {
+ if (k->is_action("ui_home", true) && vscroll->is_visible_in_tree()) {
vscroll->set_value(0);
handled = true;
}
- if (k->is_action("ui_end") && vscroll->is_visible_in_tree()) {
+ if (k->is_action("ui_end", true) && vscroll->is_visible_in_tree()) {
vscroll->set_value(vscroll->get_max());
handled = true;
}
if (is_shortcut_keys_enabled()) {
- if (k->is_action("ui_text_select_all")) {
+ if (k->is_action("ui_text_select_all", true)) {
select_all();
handled = true;
}
- if (k->is_action("ui_copy")) {
+ if (k->is_action("ui_copy", true)) {
selection_copy();
handled = true;
}
@@ -2956,7 +2956,7 @@ void RichTextLabel::_remove_item(Item *p_item, const int p_line, const int p_sub
memdelete(p_item);
}
-void RichTextLabel::add_image(const Ref<Texture2D> &p_image, const int p_width, const int p_height, const Color &p_color, InlineAlignment p_alignment) {
+void RichTextLabel::add_image(const Ref<Texture2D> &p_image, const int p_width, const int p_height, const Color &p_color, InlineAlignment p_alignment, const Rect2 &p_region) {
_stop_thread();
MutexLock data_lock(data_mutex);
@@ -2969,7 +2969,15 @@ void RichTextLabel::add_image(const Ref<Texture2D> &p_image, const int p_width,
ERR_FAIL_COND(p_image->get_height() == 0);
ItemImage *item = memnew(ItemImage);
- item->image = p_image;
+ if (p_region.has_area()) {
+ Ref<AtlasTexture> atlas_tex = memnew(AtlasTexture);
+ atlas_tex->set_atlas(p_image);
+ atlas_tex->set_region(p_region);
+ item->image = atlas_tex;
+ } else {
+ item->image = p_image;
+ }
+
item->color = p_color;
item->inline_align = p_alignment;
@@ -2981,17 +2989,30 @@ void RichTextLabel::add_image(const Ref<Texture2D> &p_image, const int p_width,
item->size.height = p_height;
} else {
// calculate height to keep aspect ratio
- item->size.height = p_image->get_height() * p_width / p_image->get_width();
+ if (p_region.has_area()) {
+ item->size.height = p_region.get_size().height * p_width / p_region.get_size().width;
+ } else {
+ item->size.height = p_image->get_height() * p_width / p_image->get_width();
+ }
}
} else {
if (p_height > 0) {
// custom height
item->size.height = p_height;
// calculate width to keep aspect ratio
- item->size.width = p_image->get_width() * p_height / p_image->get_height();
+ if (p_region.has_area()) {
+ item->size.width = p_region.get_size().width * p_height / p_region.get_size().height;
+ } else {
+ item->size.width = p_image->get_width() * p_height / p_image->get_height();
+ }
} else {
- // keep original width and height
- item->size = p_image->get_size();
+ if (p_region.has_area()) {
+ // if the image has a region, keep the region size
+ item->size = p_region.get_size();
+ } else {
+ // keep original width and height
+ item->size = p_image->get_size();
+ }
}
}
@@ -4126,6 +4147,18 @@ void RichTextLabel::append_text(const String &p_bbcode) {
Ref<Texture2D> texture = ResourceLoader::load(image, "Texture2D");
if (texture.is_valid()) {
+ Rect2 region;
+ OptionMap::Iterator region_option = bbcode_options.find("region");
+ if (region_option) {
+ Vector<String> region_values = region_option->value.split(",", false);
+ if (region_values.size() == 4) {
+ region.position.x = region_values[0].to_float();
+ region.position.y = region_values[1].to_float();
+ region.size.x = region_values[2].to_float();
+ region.size.y = region_values[3].to_float();
+ }
+ }
+
Color color = Color(1.0, 1.0, 1.0);
OptionMap::Iterator color_option = bbcode_options.find("color");
if (color_option) {
@@ -4154,7 +4187,7 @@ void RichTextLabel::append_text(const String &p_bbcode) {
}
}
- add_image(texture, width, height, color, (InlineAlignment)alignment);
+ add_image(texture, width, height, color, (InlineAlignment)alignment, region);
}
pos = end;
@@ -5209,7 +5242,7 @@ void RichTextLabel::_bind_methods() {
ClassDB::bind_method(D_METHOD("get_parsed_text"), &RichTextLabel::get_parsed_text);
ClassDB::bind_method(D_METHOD("add_text", "text"), &RichTextLabel::add_text);
ClassDB::bind_method(D_METHOD("set_text", "text"), &RichTextLabel::set_text);
- ClassDB::bind_method(D_METHOD("add_image", "image", "width", "height", "color", "inline_align"), &RichTextLabel::add_image, DEFVAL(0), DEFVAL(0), DEFVAL(Color(1.0, 1.0, 1.0)), DEFVAL(INLINE_ALIGNMENT_CENTER));
+ ClassDB::bind_method(D_METHOD("add_image", "image", "width", "height", "color", "inline_align", "region"), &RichTextLabel::add_image, DEFVAL(0), DEFVAL(0), DEFVAL(Color(1.0, 1.0, 1.0)), DEFVAL(INLINE_ALIGNMENT_CENTER), DEFVAL(Rect2(0, 0, 0, 0)));
ClassDB::bind_method(D_METHOD("newline"), &RichTextLabel::add_newline);
ClassDB::bind_method(D_METHOD("remove_line", "line"), &RichTextLabel::remove_line);
ClassDB::bind_method(D_METHOD("push_font", "font", "font_size"), &RichTextLabel::push_font);
diff --git a/scene/gui/rich_text_label.h b/scene/gui/rich_text_label.h
index 73b7676c22..04a682349d 100644
--- a/scene/gui/rich_text_label.h
+++ b/scene/gui/rich_text_label.h
@@ -569,7 +569,7 @@ private:
public:
String get_parsed_text() const;
void add_text(const String &p_text);
- void add_image(const Ref<Texture2D> &p_image, const int p_width = 0, const int p_height = 0, const Color &p_color = Color(1.0, 1.0, 1.0), InlineAlignment p_alignment = INLINE_ALIGNMENT_CENTER);
+ void add_image(const Ref<Texture2D> &p_image, const int p_width = 0, const int p_height = 0, const Color &p_color = Color(1.0, 1.0, 1.0), InlineAlignment p_alignment = INLINE_ALIGNMENT_CENTER, const Rect2 &p_region = Rect2(0, 0, 0, 0));
void add_newline();
bool remove_line(const int p_line);
void push_dropcap(const String &p_string, const Ref<Font> &p_font, int p_size, const Rect2 &p_dropcap_margins = Rect2(), const Color &p_color = Color(1, 1, 1), int p_ol_size = 0, const Color &p_ol_color = Color(0, 0, 0, 0));
diff --git a/scene/gui/scroll_bar.cpp b/scene/gui/scroll_bar.cpp
index 6c05b171e3..6899178885 100644
--- a/scene/gui/scroll_bar.cpp
+++ b/scene/gui/scroll_bar.cpp
@@ -183,35 +183,35 @@ void ScrollBar::gui_input(const Ref<InputEvent> &p_event) {
}
if (p_event->is_pressed()) {
- if (p_event->is_action("ui_left")) {
+ if (p_event->is_action("ui_left", true)) {
if (orientation != HORIZONTAL) {
return;
}
set_value(get_value() - (custom_step >= 0 ? custom_step : get_step()));
- } else if (p_event->is_action("ui_right")) {
+ } else if (p_event->is_action("ui_right", true)) {
if (orientation != HORIZONTAL) {
return;
}
set_value(get_value() + (custom_step >= 0 ? custom_step : get_step()));
- } else if (p_event->is_action("ui_up")) {
+ } else if (p_event->is_action("ui_up", true)) {
if (orientation != VERTICAL) {
return;
}
set_value(get_value() - (custom_step >= 0 ? custom_step : get_step()));
- } else if (p_event->is_action("ui_down")) {
+ } else if (p_event->is_action("ui_down", true)) {
if (orientation != VERTICAL) {
return;
}
set_value(get_value() + (custom_step >= 0 ? custom_step : get_step()));
- } else if (p_event->is_action("ui_home")) {
+ } else if (p_event->is_action("ui_home", true)) {
set_value(get_min());
- } else if (p_event->is_action("ui_end")) {
+ } else if (p_event->is_action("ui_end", true)) {
set_value(get_max());
}
}
@@ -338,7 +338,7 @@ void ScrollBar::_notification(int p_what) {
if (scrolling) {
if (get_value() != target_scroll) {
double target = target_scroll - get_value();
- double dist = sqrt(target * target);
+ double dist = abs(target);
double vel = ((target / dist) * 500) * get_physics_process_delta_time();
if (Math::abs(vel) >= dist) {
diff --git a/scene/gui/slider.cpp b/scene/gui/slider.cpp
index a7d44c0f3c..6cbacf0dd3 100644
--- a/scene/gui/slider.cpp
+++ b/scene/gui/slider.cpp
@@ -138,10 +138,10 @@ void Slider::gui_input(const Ref<InputEvent> &p_event) {
}
set_value(get_value() - (custom_step >= 0 ? custom_step : get_step()));
accept_event();
- } else if (p_event->is_action("ui_home") && p_event->is_pressed()) {
+ } else if (p_event->is_action("ui_home", true) && p_event->is_pressed()) {
set_value(get_min());
accept_event();
- } else if (p_event->is_action("ui_end") && p_event->is_pressed()) {
+ } else if (p_event->is_action("ui_end", true) && p_event->is_pressed()) {
set_value(get_max());
accept_event();
}
diff --git a/scene/gui/text_edit.cpp b/scene/gui/text_edit.cpp
index f8501f3570..48853cde9c 100644
--- a/scene/gui/text_edit.cpp
+++ b/scene/gui/text_edit.cpp
@@ -464,7 +464,7 @@ void TextEdit::_notification(int p_what) {
case NOTIFICATION_INTERNAL_PHYSICS_PROCESS: {
if (scrolling && get_v_scroll() != target_v_scroll) {
double target_y = target_v_scroll - get_v_scroll();
- double dist = sqrt(target_y * target_y);
+ double dist = abs(target_y);
// To ensure minimap is responsive override the speed setting.
double vel = ((target_y / dist) * ((minimap_clicked) ? 3000 : v_scroll_speed)) * get_physics_process_delta_time();
@@ -1692,7 +1692,7 @@ void TextEdit::gui_input(const Ref<InputEvent> &p_gui_input) {
continue;
}
- if (mpos.x > left_margin && mpos.x <= (left_margin + gutters[i].width) - 3) {
+ if (mpos.x >= left_margin && mpos.x <= left_margin + gutters[i].width) {
emit_signal(SNAME("gutter_clicked"), row, i);
return;
}
@@ -1933,7 +1933,7 @@ void TextEdit::gui_input(const Ref<InputEvent> &p_gui_input) {
continue;
}
- if (mpos.x > left_margin && mpos.x <= (left_margin + gutters[i].width) - 3) {
+ if (mpos.x >= left_margin && mpos.x < left_margin + gutters[i].width) {
// We are in this gutter i's horizontal area.
current_hovered_gutter = Vector2i(i, hovered_row);
break;
@@ -2051,7 +2051,7 @@ void TextEdit::gui_input(const Ref<InputEvent> &p_gui_input) {
}
if (is_shortcut_keys_enabled()) {
- // SELECT ALL, SELECT WORD UNDER CARET, CUT, COPY, PASTE.
+ // SELECT ALL, SELECT WORD UNDER CARET, ADD SELECTION FOR NEXT OCCURRENCE, CUT, COPY, PASTE.
if (k->is_action("ui_text_select_all", true)) {
select_all();
accept_event();
@@ -2062,6 +2062,11 @@ void TextEdit::gui_input(const Ref<InputEvent> &p_gui_input) {
accept_event();
return;
}
+ if (k->is_action("ui_text_add_selection_for_next_occurrence", true)) {
+ add_selection_for_next_occurrence();
+ accept_event();
+ return;
+ }
if (k->is_action("ui_cut", true)) {
cut();
accept_event();
@@ -2192,8 +2197,17 @@ void TextEdit::gui_input(const Ref<InputEvent> &p_gui_input) {
return;
}
- // Handle Unicode (if no modifiers active). Tab has a value of 0x09.
- if (allow_unicode_handling && editable && (k->get_unicode() >= 32 || k->get_keycode() == Key::TAB)) {
+ // Handle tab as it has no set unicode value.
+ if (k->is_action("ui_text_indent", true)) {
+ if (editable) {
+ insert_text_at_caret("\t");
+ }
+ accept_event();
+ return;
+ }
+
+ // Handle Unicode (if no modifiers active).
+ if (allow_unicode_handling && editable && k->get_unicode() >= 32) {
handle_unicode_input(k->get_unicode());
accept_event();
return;
@@ -2997,7 +3011,7 @@ Control::CursorShape TextEdit::get_cursor_shape(const Point2 &p_pos) const {
continue;
}
- if (p_pos.x > left_margin && p_pos.x <= (left_margin + gutters[i].width) - 3) {
+ if (p_pos.x >= left_margin && p_pos.x < left_margin + gutters[i].width) {
if (gutters[i].clickable || is_line_gutter_clickable(row, i)) {
return CURSOR_POINTING_HAND;
}
@@ -3854,7 +3868,7 @@ void TextEdit::undo() {
}
caret_pos_dirty = true;
}
- queue_redraw();
+ adjust_viewport_to_caret();
}
void TextEdit::redo() {
@@ -3906,7 +3920,7 @@ void TextEdit::redo() {
}
caret_pos_dirty = true;
}
- queue_redraw();
+ adjust_viewport_to_caret();
}
void TextEdit::clear_undo_history() {
@@ -4380,7 +4394,7 @@ int TextEdit::add_caret(int p_line, int p_col) {
}
void TextEdit::remove_caret(int p_caret) {
- ERR_FAIL_COND(carets.size() <= 0);
+ ERR_FAIL_COND_MSG(carets.size() <= 1, "The main caret should not be removed.");
ERR_FAIL_INDEX(p_caret, carets.size());
carets.remove_at(p_caret);
caret_index_edit_dirty = true;
@@ -4816,10 +4830,50 @@ void TextEdit::select_word_under_caret(int p_caret) {
continue;
}
- select(get_caret_line(c), begin, get_caret_column(c), end, c);
+ select(get_caret_line(c), begin, get_caret_line(c), end, c);
// Move the caret to the end of the word for easier editing.
set_caret_column(end, false, c);
}
+ merge_overlapping_carets();
+}
+
+void TextEdit::add_selection_for_next_occurrence() {
+ if (!selecting_enabled || !is_multiple_carets_enabled()) {
+ return;
+ }
+
+ if (text.size() == 1 && text[0].length() == 0) {
+ return;
+ }
+
+ // Always use the last caret, to correctly search for
+ // the next occurrence that comes after this caret.
+ int caret = get_caret_count() - 1;
+
+ if (!has_selection(caret)) {
+ select_word_under_caret(caret);
+ return;
+ }
+
+ const String &highlighted_text = get_selected_text(caret);
+ int column = get_selection_from_column(caret) + 1;
+ int line = get_caret_line(caret);
+
+ const Point2i next_occurrence = search(highlighted_text, SEARCH_MATCH_CASE, line, column);
+
+ if (next_occurrence.x == -1 || next_occurrence.y == -1) {
+ return;
+ }
+
+ int to_column = get_selection_to_column(caret) + 1;
+ int end = next_occurrence.x + (to_column - column);
+ int new_caret = add_caret(next_occurrence.y, end);
+
+ if (new_caret != -1) {
+ select(next_occurrence.y, next_occurrence.x, next_occurrence.y, end, new_caret);
+ adjust_viewport_to_caret(new_caret);
+ merge_overlapping_carets();
+ }
}
void TextEdit::select(int p_from_line, int p_from_column, int p_to_line, int p_to_column, int p_caret) {
@@ -5088,6 +5142,14 @@ bool TextEdit::is_scroll_past_end_of_file_enabled() const {
return scroll_past_end_of_file_enabled;
}
+VScrollBar *TextEdit::get_v_scroll_bar() const {
+ return v_scroll;
+}
+
+HScrollBar *TextEdit::get_h_scroll_bar() const {
+ return h_scroll;
+}
+
void TextEdit::set_v_scroll(double p_scroll) {
v_scroll->set_value(p_scroll);
int max_v_scroll = v_scroll->get_max() - v_scroll->get_page();
@@ -5982,6 +6044,7 @@ void TextEdit::_bind_methods() {
ClassDB::bind_method(D_METHOD("select_all"), &TextEdit::select_all);
ClassDB::bind_method(D_METHOD("select_word_under_caret", "caret_index"), &TextEdit::select_word_under_caret, DEFVAL(-1));
+ ClassDB::bind_method(D_METHOD("add_selection_for_next_occurrence"), &TextEdit::add_selection_for_next_occurrence);
ClassDB::bind_method(D_METHOD("select", "from_line", "from_column", "to_line", "to_column", "caret_index"), &TextEdit::select, DEFVAL(0));
ClassDB::bind_method(D_METHOD("has_selection", "caret_index"), &TextEdit::has_selection, DEFVAL(-1));
@@ -6020,6 +6083,9 @@ void TextEdit::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_smooth_scroll_enabled", "enable"), &TextEdit::set_smooth_scroll_enabled);
ClassDB::bind_method(D_METHOD("is_smooth_scroll_enabled"), &TextEdit::is_smooth_scroll_enabled);
+ ClassDB::bind_method(D_METHOD("get_v_scroll_bar"), &TextEdit::get_v_scroll_bar);
+ ClassDB::bind_method(D_METHOD("get_h_scroll_bar"), &TextEdit::get_h_scroll_bar);
+
ClassDB::bind_method(D_METHOD("set_v_scroll", "value"), &TextEdit::set_v_scroll);
ClassDB::bind_method(D_METHOD("get_v_scroll"), &TextEdit::get_v_scroll);
diff --git a/scene/gui/text_edit.h b/scene/gui/text_edit.h
index a8e087909e..86b838e387 100644
--- a/scene/gui/text_edit.h
+++ b/scene/gui/text_edit.h
@@ -851,6 +851,7 @@ public:
void select_all();
void select_word_under_caret(int p_caret = -1);
+ void add_selection_for_next_occurrence();
void select(int p_from_line, int p_from_column, int p_to_line, int p_to_column, int p_caret = 0);
bool has_selection(int p_caret = -1) const;
@@ -886,6 +887,9 @@ public:
void set_scroll_past_end_of_file_enabled(const bool p_enabled);
bool is_scroll_past_end_of_file_enabled() const;
+ VScrollBar *get_v_scroll_bar() const;
+ HScrollBar *get_h_scroll_bar() const;
+
void set_v_scroll(double p_scroll);
double get_v_scroll() const;
diff --git a/scene/gui/tree.cpp b/scene/gui/tree.cpp
index acf398305c..6f9a9a5141 100644
--- a/scene/gui/tree.cpp
+++ b/scene/gui/tree.cpp
@@ -1504,6 +1504,7 @@ void TreeItem::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_button", "column", "button_idx", "button"), &TreeItem::set_button);
ClassDB::bind_method(D_METHOD("erase_button", "column", "button_idx"), &TreeItem::erase_button);
ClassDB::bind_method(D_METHOD("set_button_disabled", "column", "button_idx", "disabled"), &TreeItem::set_button_disabled);
+ ClassDB::bind_method(D_METHOD("set_button_color", "column", "button_idx", "color"), &TreeItem::set_button_color);
ClassDB::bind_method(D_METHOD("is_button_disabled", "column", "button_idx"), &TreeItem::is_button_disabled);
ClassDB::bind_method(D_METHOD("set_tooltip_text", "column", "tooltip"), &TreeItem::set_tooltip_text);
@@ -3190,7 +3191,7 @@ void Tree::gui_input(const Ref<InputEvent> &p_event) {
Ref<InputEventKey> k = p_event;
bool is_command = k.is_valid() && k->is_command_or_control_pressed();
- if (p_event->is_action("ui_right") && p_event->is_pressed()) {
+ if (p_event->is_action("ui_right", true) && p_event->is_pressed()) {
if (!cursor_can_exit_tree) {
accept_event();
}
@@ -3208,7 +3209,7 @@ void Tree::gui_input(const Ref<InputEvent> &p_event) {
} else {
_go_right();
}
- } else if (p_event->is_action("ui_left") && p_event->is_pressed()) {
+ } else if (p_event->is_action("ui_left", true) && p_event->is_pressed()) {
if (!cursor_can_exit_tree) {
accept_event();
}
@@ -3228,21 +3229,21 @@ void Tree::gui_input(const Ref<InputEvent> &p_event) {
_go_left();
}
- } else if (p_event->is_action("ui_up") && p_event->is_pressed() && !is_command) {
+ } else if (p_event->is_action("ui_up", true) && p_event->is_pressed() && !is_command) {
if (!cursor_can_exit_tree) {
accept_event();
}
_go_up();
- } else if (p_event->is_action("ui_down") && p_event->is_pressed() && !is_command) {
+ } else if (p_event->is_action("ui_down", true) && p_event->is_pressed() && !is_command) {
if (!cursor_can_exit_tree) {
accept_event();
}
_go_down();
- } else if (p_event->is_action("ui_page_down") && p_event->is_pressed()) {
+ } else if (p_event->is_action("ui_page_down", true) && p_event->is_pressed()) {
if (!cursor_can_exit_tree) {
accept_event();
}
@@ -3280,7 +3281,7 @@ void Tree::gui_input(const Ref<InputEvent> &p_event) {
}
ensure_cursor_is_visible();
- } else if (p_event->is_action("ui_page_up") && p_event->is_pressed()) {
+ } else if (p_event->is_action("ui_page_up", true) && p_event->is_pressed()) {
if (!cursor_can_exit_tree) {
accept_event();
}
@@ -3317,7 +3318,7 @@ void Tree::gui_input(const Ref<InputEvent> &p_event) {
prev->select(selected_col);
}
ensure_cursor_is_visible();
- } else if (p_event->is_action("ui_accept") && p_event->is_pressed()) {
+ } else if (p_event->is_action("ui_accept", true) && p_event->is_pressed()) {
if (selected_item) {
//bring up editor if possible
if (!edit_selected()) {
@@ -3326,7 +3327,7 @@ void Tree::gui_input(const Ref<InputEvent> &p_event) {
}
}
accept_event();
- } else if (p_event->is_action("ui_select") && p_event->is_pressed()) {
+ } else if (p_event->is_action("ui_select", true) && p_event->is_pressed()) {
if (select_mode == SELECT_MULTI) {
if (!selected_item) {
return;
diff --git a/scene/gui/view_panner.cpp b/scene/gui/view_panner.cpp
index 3b7f499a07..e8e3e3e556 100644
--- a/scene/gui/view_panner.cpp
+++ b/scene/gui/view_panner.cpp
@@ -38,7 +38,9 @@ bool ViewPanner::gui_input(const Ref<InputEvent> &p_event, Rect2 p_canvas_rect)
Ref<InputEventMouseButton> mb = p_event;
if (mb.is_valid()) {
Vector2 scroll_vec = Vector2((mb->get_button_index() == MouseButton::WHEEL_RIGHT) - (mb->get_button_index() == MouseButton::WHEEL_LEFT), (mb->get_button_index() == MouseButton::WHEEL_DOWN) - (mb->get_button_index() == MouseButton::WHEEL_UP));
- if (scroll_vec != Vector2()) {
+ // Moving the scroll wheel sends two events: one with pressed as true,
+ // and one with pressed as false. Make sure we only process one of them.
+ if (scroll_vec != Vector2() && mb->is_pressed()) {
if (control_scheme == SCROLL_PANS) {
if (mb->is_ctrl_pressed()) {
scroll_vec.y *= mb->get_factor();