summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--core/extension/gdnative_interface.h10
-rw-r--r--core/string/ustring.cpp61
-rw-r--r--doc/classes/BaseButton.xml3
-rw-r--r--doc/classes/Control.xml3
-rw-r--r--doc/classes/MenuBar.xml3
-rw-r--r--editor/editor_properties.cpp7
-rw-r--r--editor/editor_spin_slider.cpp2
-rw-r--r--editor/plugins/sprite_frames_editor_plugin.cpp105
-rw-r--r--editor/plugins/sprite_frames_editor_plugin.h10
-rw-r--r--modules/websocket/editor/editor_debugger_server_websocket.cpp8
-rw-r--r--modules/websocket/emws_peer.cpp2
-rw-r--r--modules/websocket/register_types.cpp10
-rw-r--r--modules/websocket/remote_debugger_peer_websocket.cpp6
-rw-r--r--modules/websocket/websocket_multiplayer_peer.cpp1
-rw-r--r--platform/web/os_web.cpp8
-rw-r--r--scene/gui/base_button.cpp38
-rw-r--r--scene/gui/base_button.h4
-rw-r--r--scene/gui/control.cpp34
-rw-r--r--scene/gui/control.h6
-rw-r--r--scene/gui/graph_edit.cpp8
-rw-r--r--scene/gui/item_list.cpp18
-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_menu.cpp10
-rw-r--r--scene/gui/rich_text_label.cpp16
-rw-r--r--scene/gui/scroll_bar.cpp12
-rw-r--r--scene/gui/slider.cpp4
-rw-r--r--scene/gui/tree.cpp16
-rw-r--r--scene/gui/view_panner.cpp4
-rw-r--r--scene/main/scene_tree.cpp22
-rw-r--r--scene/resources/primitive_meshes.cpp3
-rw-r--r--tests/core/string/test_string.h18
33 files changed, 261 insertions, 240 deletions
diff --git a/core/extension/gdnative_interface.h b/core/extension/gdnative_interface.h
index 9ac5cc9398..4513c66ab5 100644
--- a/core/extension/gdnative_interface.h
+++ b/core/extension/gdnative_interface.h
@@ -201,18 +201,18 @@ typedef GDNativeBool (*GDNativeExtensionClassGet)(GDExtensionClassInstancePtr p_
typedef uint64_t (*GDNativeExtensionClassGetRID)(GDExtensionClassInstancePtr p_instance);
typedef struct {
- uint32_t type;
+ GDNativeVariantType type;
const char *name;
const char *class_name;
- uint32_t hint;
+ uint32_t hint; // Bitfield of `PropertyHint` (defined in `extension_api.json`)
const char *hint_string;
- uint32_t usage;
+ uint32_t usage; // Bitfield of `PropertyUsageFlags` (defined in `extension_api.json`)
} GDNativePropertyInfo;
typedef struct {
const char *name;
GDNativePropertyInfo return_value;
- uint32_t flags; // From GDNativeExtensionClassMethodFlags
+ uint32_t flags; // Bitfield of `GDNativeExtensionClassMethodFlags`
int32_t id;
GDNativePropertyInfo *arguments;
uint32_t argument_count;
@@ -292,7 +292,7 @@ typedef struct {
void *method_userdata;
GDNativeExtensionClassMethodCall call_func;
GDNativeExtensionClassMethodPtrCall ptrcall_func;
- uint32_t method_flags; /* GDNativeExtensionClassMethodFlags */
+ uint32_t method_flags; // Bitfield of `GDNativeExtensionClassMethodFlags`
uint32_t argument_count;
GDNativeBool has_return_value;
GDNativeExtensionClassMethodGetArgumentType get_argument_type_func;
diff --git a/core/string/ustring.cpp b/core/string/ustring.cpp
index 872c8357ae..671b06f0ed 100644
--- a/core/string/ustring.cpp
+++ b/core/string/ustring.cpp
@@ -3667,32 +3667,43 @@ bool String::is_network_share_path() const {
String String::simplify_path() const {
String s = *this;
String drive;
- if (s.begins_with("local://")) {
- drive = "local://";
- s = s.substr(8);
- } else if (s.begins_with("res://")) {
- drive = "res://";
- s = s.substr(6);
- } else if (s.begins_with("user://")) {
- drive = "user://";
- s = s.substr(7);
- } else if (s.begins_with("uid://")) {
- drive = "uid://";
- s = s.substr(6);
- } else if (is_network_share_path()) {
- drive = s.substr(0, 2);
- s = s.substr(2, s.length() - 2);
- } else if (s.begins_with("/") || s.begins_with("\\")) {
- drive = s.substr(0, 1);
- s = s.substr(1, s.length() - 1);
- } else {
- int p = s.find(":/");
- if (p == -1) {
- p = s.find(":\\");
+
+ // Check if we have a special path (like res://) or a protocol identifier.
+ int p = s.find("://");
+ bool found = false;
+ if (p > 0) {
+ bool only_chars = true;
+ for (int i = 0; i < p; i++) {
+ if (!is_ascii_char(s[i])) {
+ only_chars = false;
+ break;
+ }
+ }
+ if (only_chars) {
+ found = true;
+ drive = s.substr(0, p + 3);
+ s = s.substr(p + 3);
}
- if (p != -1 && p < s.find("/")) {
- drive = s.substr(0, p + 2);
- s = s.substr(p + 2);
+ }
+ if (!found) {
+ if (is_network_share_path()) {
+ // Network path, beginning with // or \\.
+ drive = s.substr(0, 2);
+ s = s.substr(2);
+ } else if (s.begins_with("/") || s.begins_with("\\")) {
+ // Absolute path.
+ drive = s.substr(0, 1);
+ s = s.substr(1);
+ } else {
+ // Windows-style drive path, like C:/ or C:\.
+ p = s.find(":/");
+ if (p == -1) {
+ p = s.find(":\\");
+ }
+ if (p != -1 && p < s.find("/")) {
+ drive = s.substr(0, p + 2);
+ s = s.substr(p + 2);
+ }
}
}
diff --git a/doc/classes/BaseButton.xml b/doc/classes/BaseButton.xml
index 629675132a..47e1f85c97 100644
--- a/doc/classes/BaseButton.xml
+++ b/doc/classes/BaseButton.xml
@@ -68,9 +68,6 @@
<member name="shortcut" type="Shortcut" setter="set_shortcut" getter="get_shortcut">
[Shortcut] associated to the button.
</member>
- <member name="shortcut_context" type="Node" setter="set_shortcut_context" getter="get_shortcut_context">
- The [Node] which must be a parent of the focused GUI [Control] for the shortcut to be activated. If [code]null[/code], the shortcut can be activated when any control is focused (a global shortcut). This allows shortcuts to be accepted only when the user has a certain area of the GUI focused.
- </member>
<member name="shortcut_in_tooltip" type="bool" setter="set_shortcut_in_tooltip" getter="is_shortcut_in_tooltip_enabled">
If [code]true[/code], the button will add information about its shortcut in the tooltip.
</member>
diff --git a/doc/classes/Control.xml b/doc/classes/Control.xml
index 9c7cb7b089..7968b03c4b 100644
--- a/doc/classes/Control.xml
+++ b/doc/classes/Control.xml
@@ -1051,6 +1051,9 @@
[b]Note:[/b] This property is mainly intended to be used for animation purposes. Text inside the Control will look pixelated or blurry when the Control is scaled. To support multiple resolutions in your project, use an appropriate viewport stretch mode as described in the [url=$DOCS_URL/tutorials/viewports/multiple_resolutions.html]documentation[/url] instead of scaling Controls individually.
[b]Note:[/b] If the Control node is a child of a [Container] node, the scale will be reset to [code]Vector2(1, 1)[/code] when the scene is instantiated. To set the Control's scale when it's instantiated, wait for one frame using [code]await get_tree().process_frame[/code] then set its [member scale] property.
</member>
+ <member name="shortcut_context" type="Node" setter="set_shortcut_context" getter="get_shortcut_context">
+ The [Node] which must be a parent of the focused [Control] for the shortcut to be activated. If [code]null[/code], the shortcut can be activated when any control is focused (a global shortcut). This allows shortcuts to be accepted only when the user has a certain area of the GUI focused.
+ </member>
<member name="size" type="Vector2" setter="_set_size" getter="get_size" default="Vector2(0, 0)">
The size of the node's bounding rectangle, in pixels. [Container] nodes update this property automatically.
</member>
diff --git a/doc/classes/MenuBar.xml b/doc/classes/MenuBar.xml
index 3ef0572e9f..e8505937ff 100644
--- a/doc/classes/MenuBar.xml
+++ b/doc/classes/MenuBar.xml
@@ -106,9 +106,6 @@
<member name="prefer_global_menu" type="bool" setter="set_prefer_global_menu" getter="is_prefer_global_menu" default="true">
If [code]true[/code], [MenuBar] will use system global menu when supported.
</member>
- <member name="shortcut_context" type="Node" setter="set_shortcut_context" getter="get_shortcut_context">
- The [Node] which must be a parent of the focused GUI [Control] for the shortcut to be activated. If [code]null[/code], the shortcut can be activated when any control is focused (a global shortcut). This allows shortcuts to be accepted only when the user has a certain area of the GUI focused.
- </member>
<member name="start_index" type="int" setter="set_start_index" getter="get_start_index" default="-1">
Position in the global menu to insert first [MenuBar] item at.
</member>
diff --git a/editor/editor_properties.cpp b/editor/editor_properties.cpp
index 155802d16c..332e47dc52 100644
--- a/editor/editor_properties.cpp
+++ b/editor/editor_properties.cpp
@@ -760,8 +760,13 @@ void EditorPropertyEnum::_option_selected(int p_which) {
}
void EditorPropertyEnum::update_property() {
- int64_t which = get_edited_object()->get(get_edited_property());
+ Variant current = get_edited_object()->get(get_edited_property());
+ if (current.get_type() == Variant::NIL) {
+ options->select(-1);
+ return;
+ }
+ int64_t which = current;
for (int i = 0; i < options->get_item_count(); i++) {
if (which == (int64_t)options->get_item_metadata(i)) {
options->select(i);
diff --git a/editor/editor_spin_slider.cpp b/editor/editor_spin_slider.cpp
index 5edb6d877c..1cdfceebc8 100644
--- a/editor/editor_spin_slider.cpp
+++ b/editor/editor_spin_slider.cpp
@@ -143,7 +143,7 @@ void EditorSpinSlider::gui_input(const Ref<InputEvent> &p_event) {
}
Ref<InputEventKey> k = p_event;
- if (k.is_valid() && k->is_pressed() && k->is_action("ui_accept")) {
+ if (k.is_valid() && k->is_pressed() && k->is_action("ui_accept", true)) {
_focus_entered();
}
}
diff --git a/editor/plugins/sprite_frames_editor_plugin.cpp b/editor/plugins/sprite_frames_editor_plugin.cpp
index cfece173d6..5dac66d3e1 100644
--- a/editor/plugins/sprite_frames_editor_plugin.cpp
+++ b/editor/plugins/sprite_frames_editor_plugin.cpp
@@ -414,16 +414,16 @@ void SpriteFramesEditor::_notification(int p_what) {
load_sheet->set_icon(get_theme_icon(SNAME("SpriteSheet"), SNAME("EditorIcons")));
copy->set_icon(get_theme_icon(SNAME("ActionCopy"), SNAME("EditorIcons")));
paste->set_icon(get_theme_icon(SNAME("ActionPaste"), SNAME("EditorIcons")));
- empty->set_icon(get_theme_icon(SNAME("InsertBefore"), SNAME("EditorIcons")));
- empty2->set_icon(get_theme_icon(SNAME("InsertAfter"), SNAME("EditorIcons")));
+ empty_before->set_icon(get_theme_icon(SNAME("InsertBefore"), SNAME("EditorIcons")));
+ empty_after->set_icon(get_theme_icon(SNAME("InsertAfter"), SNAME("EditorIcons")));
move_up->set_icon(get_theme_icon(SNAME("MoveLeft"), SNAME("EditorIcons")));
move_down->set_icon(get_theme_icon(SNAME("MoveRight"), SNAME("EditorIcons")));
- _delete->set_icon(get_theme_icon(SNAME("Remove"), SNAME("EditorIcons")));
+ delete_frame->set_icon(get_theme_icon(SNAME("Remove"), SNAME("EditorIcons")));
zoom_out->set_icon(get_theme_icon(SNAME("ZoomLess"), SNAME("EditorIcons")));
zoom_reset->set_icon(get_theme_icon(SNAME("ZoomReset"), SNAME("EditorIcons")));
zoom_in->set_icon(get_theme_icon(SNAME("ZoomMore"), SNAME("EditorIcons")));
- new_anim->set_icon(get_theme_icon(SNAME("New"), SNAME("EditorIcons")));
- remove_anim->set_icon(get_theme_icon(SNAME("Remove"), SNAME("EditorIcons")));
+ add_anim->set_icon(get_theme_icon(SNAME("New"), SNAME("EditorIcons")));
+ delete_anim->set_icon(get_theme_icon(SNAME("Remove"), SNAME("EditorIcons")));
anim_search_box->set_right_icon(get_theme_icon(SNAME("Search"), SNAME("EditorIcons")));
split_sheet_zoom_out->set_icon(get_theme_icon(SNAME("ZoomLess"), SNAME("EditorIcons")));
split_sheet_zoom_reset->set_icon(get_theme_icon(SNAME("ZoomReset"), SNAME("EditorIcons")));
@@ -1016,19 +1016,19 @@ void SpriteFramesEditor::edit(SpriteFrames *p_frames) {
hide();
}
- new_anim->set_disabled(read_only);
- remove_anim->set_disabled(read_only);
+ add_anim->set_disabled(read_only);
+ delete_anim->set_disabled(read_only);
anim_speed->set_editable(!read_only);
anim_loop->set_disabled(read_only);
load->set_disabled(read_only);
load_sheet->set_disabled(read_only);
copy->set_disabled(read_only);
paste->set_disabled(read_only);
- empty->set_disabled(read_only);
- empty2->set_disabled(read_only);
+ empty_before->set_disabled(read_only);
+ empty_after->set_disabled(read_only);
move_up->set_disabled(read_only);
move_down->set_disabled(read_only);
- _delete->set_disabled(read_only);
+ delete_frame->set_disabled(read_only);
}
void SpriteFramesEditor::set_undo_redo(Ref<EditorUndoRedoManager> p_undo_redo) {
@@ -1187,18 +1187,16 @@ SpriteFramesEditor::SpriteFramesEditor() {
HBoxContainer *hbc_animlist = memnew(HBoxContainer);
sub_vb->add_child(hbc_animlist);
- new_anim = memnew(Button);
- new_anim->set_flat(true);
- new_anim->set_tooltip_text(TTR("New Animation"));
- hbc_animlist->add_child(new_anim);
- new_anim->connect("pressed", callable_mp(this, &SpriteFramesEditor::_animation_add));
+ add_anim = memnew(Button);
+ add_anim->set_flat(true);
+ hbc_animlist->add_child(add_anim);
+ add_anim->connect("pressed", callable_mp(this, &SpriteFramesEditor::_animation_add));
- remove_anim = memnew(Button);
- remove_anim->set_flat(true);
- remove_anim->set_tooltip_text(TTR("Remove Animation"));
- hbc_animlist->add_child(remove_anim);
- remove_anim->set_disabled(true);
- remove_anim->connect("pressed", callable_mp(this, &SpriteFramesEditor::_animation_remove));
+ delete_anim = memnew(Button);
+ delete_anim->set_flat(true);
+ hbc_animlist->add_child(delete_anim);
+ delete_anim->set_disabled(true);
+ delete_anim->connect("pressed", callable_mp(this, &SpriteFramesEditor::_animation_remove));
anim_search_box = memnew(LineEdit);
hbc_animlist->add_child(anim_search_box);
@@ -1215,6 +1213,11 @@ SpriteFramesEditor::SpriteFramesEditor() {
animations->connect("item_edited", callable_mp(this, &SpriteFramesEditor::_animation_name_edited));
animations->set_allow_reselect(true);
+ add_anim->set_shortcut_context(animations);
+ add_anim->set_shortcut(ED_SHORTCUT("sprite_frames/new_animation", TTR("Add Animation"), KeyModifierMask::CMD_OR_CTRL | Key::N));
+ delete_anim->set_shortcut_context(animations);
+ delete_anim->set_shortcut(ED_SHORTCUT("sprite_frames/delete_animation", TTR("Delete Animation"), Key::KEY_DELETE));
+
HBoxContainer *hbc_anim_speed = memnew(HBoxContainer);
hbc_anim_speed->add_child(memnew(Label(TTR("Speed:"))));
vbc_animlist->add_child(hbc_anim_speed);
@@ -1244,54 +1247,45 @@ SpriteFramesEditor::SpriteFramesEditor() {
load = memnew(Button);
load->set_flat(true);
- load->set_tooltip_text(TTR("Add a Texture from File"));
hbc->add_child(load);
load_sheet = memnew(Button);
load_sheet->set_flat(true);
- load_sheet->set_tooltip_text(TTR("Add Frames from a Sprite Sheet"));
hbc->add_child(load_sheet);
hbc->add_child(memnew(VSeparator));
copy = memnew(Button);
copy->set_flat(true);
- copy->set_tooltip_text(TTR("Copy"));
hbc->add_child(copy);
paste = memnew(Button);
paste->set_flat(true);
- paste->set_tooltip_text(TTR("Paste"));
hbc->add_child(paste);
hbc->add_child(memnew(VSeparator));
- empty = memnew(Button);
- empty->set_flat(true);
- empty->set_tooltip_text(TTR("Insert Empty (Before)"));
- hbc->add_child(empty);
+ empty_before = memnew(Button);
+ empty_before->set_flat(true);
+ hbc->add_child(empty_before);
- empty2 = memnew(Button);
- empty2->set_flat(true);
- empty2->set_tooltip_text(TTR("Insert Empty (After)"));
- hbc->add_child(empty2);
+ empty_after = memnew(Button);
+ empty_after->set_flat(true);
+ hbc->add_child(empty_after);
hbc->add_child(memnew(VSeparator));
move_up = memnew(Button);
move_up->set_flat(true);
- move_up->set_tooltip_text(TTR("Move (Before)"));
hbc->add_child(move_up);
move_down = memnew(Button);
move_down->set_flat(true);
- move_down->set_tooltip_text(TTR("Move (After)"));
hbc->add_child(move_down);
- _delete = memnew(Button);
- _delete->set_flat(true);
- _delete->set_tooltip_text(TTR("Delete"));
- hbc->add_child(_delete);
+ delete_frame = memnew(Button);
+ delete_frame->set_flat(true);
+ hbc->add_child(delete_frame);
hbc->add_spacer();
@@ -1333,13 +1327,40 @@ SpriteFramesEditor::SpriteFramesEditor() {
load->connect("pressed", callable_mp(this, &SpriteFramesEditor::_load_pressed));
load_sheet->connect("pressed", callable_mp(this, &SpriteFramesEditor::_open_sprite_sheet));
- _delete->connect("pressed", callable_mp(this, &SpriteFramesEditor::_delete_pressed));
+ delete_frame->connect("pressed", callable_mp(this, &SpriteFramesEditor::_delete_pressed));
copy->connect("pressed", callable_mp(this, &SpriteFramesEditor::_copy_pressed));
paste->connect("pressed", callable_mp(this, &SpriteFramesEditor::_paste_pressed));
- empty->connect("pressed", callable_mp(this, &SpriteFramesEditor::_empty_pressed));
- empty2->connect("pressed", callable_mp(this, &SpriteFramesEditor::_empty2_pressed));
+ empty_before->connect("pressed", callable_mp(this, &SpriteFramesEditor::_empty_pressed));
+ empty_after->connect("pressed", callable_mp(this, &SpriteFramesEditor::_empty2_pressed));
move_up->connect("pressed", callable_mp(this, &SpriteFramesEditor::_up_pressed));
move_down->connect("pressed", callable_mp(this, &SpriteFramesEditor::_down_pressed));
+
+ load->set_shortcut_context(tree);
+ load->set_shortcut(ED_SHORTCUT("sprite_frames/load_from_file", TTR("Add frame from file"), KeyModifierMask::CMD_OR_CTRL | Key::O));
+ load_sheet->set_shortcut_context(tree);
+ load_sheet->set_shortcut(ED_SHORTCUT("sprite_frames/load_from_sheet", TTR("Add frames from sprite sheet"), KeyModifierMask::CMD_OR_CTRL | KeyModifierMask::SHIFT | Key::O));
+ delete_frame->set_shortcut_context(tree);
+ delete_frame->set_shortcut(ED_SHORTCUT("sprite_frames/delete", TTR("Delete Frame"), Key::KEY_DELETE));
+ copy->set_shortcut_context(tree);
+ copy->set_shortcut(ED_SHORTCUT("sprite_frames/copy", TTR("Copy Frame"), KeyModifierMask::CMD_OR_CTRL | Key::C));
+ paste->set_shortcut_context(tree);
+ paste->set_shortcut(ED_SHORTCUT("sprite_frames/paste", TTR("Paste Frame"), KeyModifierMask::CMD_OR_CTRL | Key::V));
+ empty_before->set_shortcut_context(tree);
+ empty_before->set_shortcut(ED_SHORTCUT("sprite_frames/empty_before", TTR("Insert Empty (Before Selected)"), KeyModifierMask::ALT | Key::LEFT));
+ empty_after->set_shortcut_context(tree);
+ empty_after->set_shortcut(ED_SHORTCUT("sprite_frames/empty_after", TTR("Insert Empty (After Selected)"), KeyModifierMask::ALT | Key::RIGHT));
+ move_up->set_shortcut_context(tree);
+ move_up->set_shortcut(ED_SHORTCUT("sprite_frames/move_left", TTR("Move Frame Left"), KeyModifierMask::CMD_OR_CTRL | Key::LEFT));
+ move_down->set_shortcut_context(tree);
+ move_down->set_shortcut(ED_SHORTCUT("sprite_frames/move_right", TTR("Move Frame Right"), KeyModifierMask::CMD_OR_CTRL | Key::RIGHT));
+
+ zoom_out->set_shortcut_context(tree);
+ zoom_out->set_shortcut(ED_SHORTCUT_ARRAY("sprite_frames/zoom_out", TTR("Zoom Out"),
+ { int32_t(KeyModifierMask::CMD_OR_CTRL | Key::MINUS), int32_t(KeyModifierMask::CMD_OR_CTRL | Key::KP_SUBTRACT) }));
+ zoom_in->set_shortcut_context(tree);
+ zoom_in->set_shortcut(ED_SHORTCUT_ARRAY("sprite_frames/zoom_in", TTR("Zoom In"),
+ { int32_t(KeyModifierMask::CMD_OR_CTRL | Key::EQUAL), int32_t(KeyModifierMask::CMD_OR_CTRL | Key::KP_ADD) }));
+
file->connect("files_selected", callable_mp(this, &SpriteFramesEditor::_file_load_request).bind(-1));
loading_scene = false;
sel = -1;
diff --git a/editor/plugins/sprite_frames_editor_plugin.h b/editor/plugins/sprite_frames_editor_plugin.h
index 092f556c63..5fc3fe4481 100644
--- a/editor/plugins/sprite_frames_editor_plugin.h
+++ b/editor/plugins/sprite_frames_editor_plugin.h
@@ -61,11 +61,11 @@ class SpriteFramesEditor : public HSplitContainer {
Button *load = nullptr;
Button *load_sheet = nullptr;
- Button *_delete = nullptr;
+ Button *delete_frame = nullptr;
Button *copy = nullptr;
Button *paste = nullptr;
- Button *empty = nullptr;
- Button *empty2 = nullptr;
+ Button *empty_before = nullptr;
+ Button *empty_after = nullptr;
Button *move_up = nullptr;
Button *move_down = nullptr;
Button *zoom_out = nullptr;
@@ -75,8 +75,8 @@ class SpriteFramesEditor : public HSplitContainer {
bool loading_scene;
int sel;
- Button *new_anim = nullptr;
- Button *remove_anim = nullptr;
+ Button *add_anim = nullptr;
+ Button *delete_anim = nullptr;
LineEdit *anim_search_box = nullptr;
Tree *animations = nullptr;
diff --git a/modules/websocket/editor/editor_debugger_server_websocket.cpp b/modules/websocket/editor/editor_debugger_server_websocket.cpp
index 1c4ebd0f55..48bfbaa14e 100644
--- a/modules/websocket/editor/editor_debugger_server_websocket.cpp
+++ b/modules/websocket/editor/editor_debugger_server_websocket.cpp
@@ -40,7 +40,13 @@
void EditorDebuggerServerWebSocket::poll() {
if (pending_peer.is_null() && tcp_server->is_connection_available()) {
- Ref<WebSocketPeer> peer;
+ Ref<WebSocketPeer> peer = Ref<WebSocketPeer>(WebSocketPeer::create());
+ ERR_FAIL_COND(peer.is_null()); // Bug.
+
+ Vector<String> ws_protocols;
+ ws_protocols.push_back("binary"); // Compatibility for emscripten TCP-to-WebSocket.
+ peer->set_supported_protocols(ws_protocols);
+
Error err = peer->accept_stream(tcp_server->take_connection());
if (err == OK) {
pending_timer = OS::get_singleton()->get_ticks_msec();
diff --git a/modules/websocket/emws_peer.cpp b/modules/websocket/emws_peer.cpp
index 5f3cb76852..3bd132bc73 100644
--- a/modules/websocket/emws_peer.cpp
+++ b/modules/websocket/emws_peer.cpp
@@ -120,7 +120,7 @@ Error EMWSPeer::_send(const uint8_t *p_buffer, int p_buffer_size, bool p_binary)
}
Error EMWSPeer::send(const uint8_t *p_buffer, int p_buffer_size, WriteMode p_mode) {
- return _send(p_buffer, p_buffer_size, p_mode == WRITE_MODE_TEXT);
+ return _send(p_buffer, p_buffer_size, p_mode == WRITE_MODE_BINARY);
}
Error EMWSPeer::put_packet(const uint8_t *p_buffer, int p_buffer_size) {
diff --git a/modules/websocket/register_types.cpp b/modules/websocket/register_types.cpp
index 691b031bbd..c55a651ab0 100644
--- a/modules/websocket/register_types.cpp
+++ b/modules/websocket/register_types.cpp
@@ -31,11 +31,14 @@
#include "register_types.h"
#include "core/config/project_settings.h"
+#include "core/debugger/engine_debugger.h"
#include "core/error/error_macros.h"
#include "websocket_multiplayer_peer.h"
#include "websocket_peer.h"
+#include "remote_debugger_peer_websocket.h"
+
#ifdef WEB_ENABLED
#include "emws_peer.h"
#else
@@ -55,7 +58,7 @@ static void _editor_init_callback() {
#endif
void initialize_websocket_module(ModuleInitializationLevel p_level) {
- if (p_level == MODULE_INITIALIZATION_LEVEL_SCENE) {
+ if (p_level == MODULE_INITIALIZATION_LEVEL_CORE) {
#ifdef WEB_ENABLED
EMWSPeer::initialize();
#else
@@ -64,6 +67,9 @@ void initialize_websocket_module(ModuleInitializationLevel p_level) {
GDREGISTER_CLASS(WebSocketMultiplayerPeer);
ClassDB::register_custom_instance_class<WebSocketPeer>();
+
+ EngineDebugger::register_uri_handler("ws://", RemoteDebuggerPeerWebSocket::create);
+ EngineDebugger::register_uri_handler("wss://", RemoteDebuggerPeerWebSocket::create);
}
#ifdef TOOLS_ENABLED
@@ -74,7 +80,7 @@ void initialize_websocket_module(ModuleInitializationLevel p_level) {
}
void uninitialize_websocket_module(ModuleInitializationLevel p_level) {
- if (p_level != MODULE_INITIALIZATION_LEVEL_SCENE) {
+ if (p_level != MODULE_INITIALIZATION_LEVEL_CORE) {
return;
}
#ifndef WEB_ENABLED
diff --git a/modules/websocket/remote_debugger_peer_websocket.cpp b/modules/websocket/remote_debugger_peer_websocket.cpp
index fc4f51b59b..58adb76208 100644
--- a/modules/websocket/remote_debugger_peer_websocket.cpp
+++ b/modules/websocket/remote_debugger_peer_websocket.cpp
@@ -33,12 +33,13 @@
#include "core/config/project_settings.h"
Error RemoteDebuggerPeerWebSocket::connect_to_host(const String &p_uri) {
+ ws_peer = Ref<WebSocketPeer>(WebSocketPeer::create());
+ ERR_FAIL_COND_V(ws_peer.is_null(), ERR_BUG);
+
Vector<String> protocols;
protocols.push_back("binary"); // Compatibility for emscripten TCP-to-WebSocket.
- ws_peer = Ref<WebSocketPeer>(WebSocketPeer::create());
ws_peer->set_supported_protocols(protocols);
-
ws_peer->set_max_queued_packets(max_queued_messages);
ws_peer->set_inbound_buffer_size((1 << 23) - 1);
ws_peer->set_outbound_buffer_size((1 << 23) - 1);
@@ -81,6 +82,7 @@ void RemoteDebuggerPeerWebSocket::poll() {
}
int RemoteDebuggerPeerWebSocket::get_max_message_size() const {
+ ERR_FAIL_COND_V(ws_peer.is_null(), 0);
return ws_peer->get_max_packet_size();
}
diff --git a/modules/websocket/websocket_multiplayer_peer.cpp b/modules/websocket/websocket_multiplayer_peer.cpp
index 00b96ec587..c314ebd049 100644
--- a/modules/websocket/websocket_multiplayer_peer.cpp
+++ b/modules/websocket/websocket_multiplayer_peer.cpp
@@ -61,6 +61,7 @@ void WebSocketMultiplayerPeer::_clear() {
tls_key.unref();
if (current_packet.data != nullptr) {
memfree(current_packet.data);
+ current_packet.data = nullptr;
}
for (Packet &E : incoming_packets) {
diff --git a/platform/web/os_web.cpp b/platform/web/os_web.cpp
index c263ee094b..07c53e51d0 100644
--- a/platform/web/os_web.cpp
+++ b/platform/web/os_web.cpp
@@ -37,9 +37,6 @@
#include "platform/web/display_server_web.h"
#include "modules/modules_enabled.gen.h" // For websocket.
-#ifdef MODULE_WEBSOCKET_ENABLED
-#include "modules/websocket/remote_debugger_peer_websocket.h"
-#endif
#include <dlfcn.h>
#include <emscripten.h>
@@ -56,11 +53,6 @@ void OS_Web::alert(const String &p_alert, const String &p_title) {
void OS_Web::initialize() {
OS_Unix::initialize_core();
DisplayServerWeb::register_web_driver();
-
-#ifdef MODULE_WEBSOCKET_ENABLED
- EngineDebugger::register_uri_handler("ws://", RemoteDebuggerPeerWebSocket::create);
- EngineDebugger::register_uri_handler("wss://", RemoteDebuggerPeerWebSocket::create);
-#endif
}
void OS_Web::resume_audio() {
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/control.cpp b/scene/gui/control.cpp
index 488ae762c5..565e450d60 100644
--- a/scene/gui/control.cpp
+++ b/scene/gui/control.cpp
@@ -1759,6 +1759,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) {
@@ -3133,6 +3161,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);
@@ -3206,6 +3237,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 2cc628ca5e..e526690cbe 100644
--- a/scene/gui/control.h
+++ b/scene/gui/control.h
@@ -219,6 +219,8 @@ private:
NodePath focus_next;
NodePath focus_prev;
+ ObjectID shortcut_context;
+
// Theming.
ThemeOwner *theme_owner = nullptr;
@@ -488,6 +490,10 @@ public:
void warp_mouse(const Point2 &p_position);
+ bool is_focus_owner_in_shortcut_context() const;
+ void set_shortcut_context(const Node *p_node);
+ Node *get_shortcut_context() const;
+
// Drag and drop handling.
virtual void set_drag_forwarding(Object *p_target);
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/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_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 9217f31310..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;
}
diff --git a/scene/gui/scroll_bar.cpp b/scene/gui/scroll_bar.cpp
index 6c05b171e3..193c6f1ce7 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());
}
}
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/tree.cpp b/scene/gui/tree.cpp
index 2e8fa1e606..6f9a9a5141 100644
--- a/scene/gui/tree.cpp
+++ b/scene/gui/tree.cpp
@@ -3191,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();
}
@@ -3209,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();
}
@@ -3229,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();
}
@@ -3281,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();
}
@@ -3318,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()) {
@@ -3327,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();
diff --git a/scene/main/scene_tree.cpp b/scene/main/scene_tree.cpp
index 64eb3b879b..270e5b7025 100644
--- a/scene/main/scene_tree.cpp
+++ b/scene/main/scene_tree.cpp
@@ -44,6 +44,7 @@
#include "node.h"
#include "scene/animation/tween.h"
#include "scene/debugger/scene_debugger.h"
+#include "scene/gui/control.h"
#include "scene/main/multiplayer_api.h"
#include "scene/main/viewport.h"
#include "scene/resources/environment.h"
@@ -895,6 +896,8 @@ void SceneTree::_call_input_pause(const StringName &p_group, CallInputType p_cal
call_lock++;
+ Vector<Node *> no_context_nodes;
+
for (int i = gr_node_count - 1; i >= 0; i--) {
if (p_viewport->is_input_handled()) {
break;
@@ -913,9 +916,22 @@ void SceneTree::_call_input_pause(const StringName &p_group, CallInputType p_cal
case CALL_INPUT_TYPE_INPUT:
n->_call_input(p_input);
break;
- case CALL_INPUT_TYPE_SHORTCUT_INPUT:
+ case CALL_INPUT_TYPE_SHORTCUT_INPUT: {
+ const Control *c = Object::cast_to<Control>(n);
+ if (c) {
+ // If calling shortcut input on a control, ensure it respects the shortcut context.
+ // Shortcut context (based on focus) only makes sense for controls (UI), so don't need to worry about it for nodes
+ if (c->get_shortcut_context() == nullptr) {
+ no_context_nodes.append(n);
+ continue;
+ }
+ if (!c->is_focus_owner_in_shortcut_context()) {
+ continue;
+ }
+ }
n->_call_shortcut_input(p_input);
break;
+ }
case CALL_INPUT_TYPE_UNHANDLED_INPUT:
n->_call_unhandled_input(p_input);
break;
@@ -925,6 +941,10 @@ void SceneTree::_call_input_pause(const StringName &p_group, CallInputType p_cal
}
}
+ for (Node *n : no_context_nodes) {
+ n->_call_shortcut_input(p_input);
+ }
+
call_lock--;
if (call_lock == 0) {
call_skip.clear();
diff --git a/scene/resources/primitive_meshes.cpp b/scene/resources/primitive_meshes.cpp
index 7d0336cff3..eb83a37c7b 100644
--- a/scene/resources/primitive_meshes.cpp
+++ b/scene/resources/primitive_meshes.cpp
@@ -771,6 +771,7 @@ void CylinderMesh::create_mesh_array(Array &p_arr, float top_radius, float botto
thisrow = 0;
prevrow = 0;
+ const real_t side_normal_y = (bottom_radius - top_radius) / height;
for (j = 0; j <= (rings + 1); j++) {
v = j;
v /= (rings + 1);
@@ -789,7 +790,7 @@ void CylinderMesh::create_mesh_array(Array &p_arr, float top_radius, float botto
Vector3 p = Vector3(x * radius, y, z * radius);
points.push_back(p);
- normals.push_back(Vector3(x, 0.0, z));
+ normals.push_back(Vector3(x, side_normal_y, z).normalized());
ADD_TANGENT(z, 0.0, -x, 1.0)
uvs.push_back(Vector2(u, v * 0.5));
point++;
diff --git a/tests/core/string/test_string.h b/tests/core/string/test_string.h
index 969f5fc096..cf34caac28 100644
--- a/tests/core/string/test_string.h
+++ b/tests/core/string/test_string.h
@@ -1424,20 +1424,22 @@ TEST_CASE("[String] dedent") {
}
TEST_CASE("[String] Path functions") {
- static const char *path[7] = { "C:\\Godot\\project\\test.tscn", "/Godot/project/test.xscn", "../Godot/project/test.scn", "Godot\\test.doc", "C:\\test.", "res://test", "/.test" };
- static const char *base_dir[7] = { "C:\\Godot\\project", "/Godot/project", "../Godot/project", "Godot", "C:\\", "res://", "/" };
- static const char *base_name[7] = { "C:\\Godot\\project\\test", "/Godot/project/test", "../Godot/project/test", "Godot\\test", "C:\\test", "res://test", "/" };
- static const char *ext[7] = { "tscn", "xscn", "scn", "doc", "", "", "test" };
- static const char *file[7] = { "test.tscn", "test.xscn", "test.scn", "test.doc", "test.", "test", ".test" };
- static const bool abs[7] = { true, true, false, false, true, true, true };
-
- for (int i = 0; i < 7; i++) {
+ static const char *path[8] = { "C:\\Godot\\project\\test.tscn", "/Godot/project/test.xscn", "../Godot/project/test.scn", "Godot\\test.doc", "C:\\test.", "res://test", "user://test", "/.test" };
+ static const char *base_dir[8] = { "C:\\Godot\\project", "/Godot/project", "../Godot/project", "Godot", "C:\\", "res://", "user://", "/" };
+ static const char *base_name[8] = { "C:\\Godot\\project\\test", "/Godot/project/test", "../Godot/project/test", "Godot\\test", "C:\\test", "res://test", "user://test", "/" };
+ static const char *ext[8] = { "tscn", "xscn", "scn", "doc", "", "", "", "test" };
+ static const char *file[8] = { "test.tscn", "test.xscn", "test.scn", "test.doc", "test.", "test", "test", ".test" };
+ static const char *simplified[8] = { "C:/Godot/project/test.tscn", "/Godot/project/test.xscn", "Godot/project/test.scn", "Godot/test.doc", "C:/test.", "res://test", "user://test", "/.test" };
+ static const bool abs[8] = { true, true, false, false, true, true, true, true };
+
+ for (int i = 0; i < 8; i++) {
CHECK(String(path[i]).get_base_dir() == base_dir[i]);
CHECK(String(path[i]).get_basename() == base_name[i]);
CHECK(String(path[i]).get_extension() == ext[i]);
CHECK(String(path[i]).get_file() == file[i]);
CHECK(String(path[i]).is_absolute_path() == abs[i]);
CHECK(String(path[i]).is_relative_path() != abs[i]);
+ CHECK(String(path[i]).simplify_path() == String(simplified[i]));
CHECK(String(path[i]).simplify_path().get_base_dir().path_join(file[i]) == String(path[i]).simplify_path());
}