summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--core/bind/core_bind.cpp12
-rw-r--r--core/bind/core_bind.h2
-rw-r--r--core/os/main_loop.cpp2
-rw-r--r--core/os/os.h2
-rw-r--r--doc/classes/HTTPClient.xml3
-rw-r--r--doc/classes/HTTPRequest.xml1
-rw-r--r--doc/classes/MainLoop.xml11
-rw-r--r--doc/classes/OS.xml14
-rw-r--r--editor/editor_inspector.cpp14
-rw-r--r--editor/editor_properties.cpp15
-rw-r--r--editor/editor_properties.h3
-rw-r--r--editor/property_editor.cpp8
-rw-r--r--editor/property_editor.h3
-rw-r--r--editor/scene_tree_dock.cpp115
-rw-r--r--editor/scene_tree_dock.h4
-rw-r--r--platform/android/java/lib/src/org/godotengine/godot/Godot.java37
-rw-r--r--platform/android/java/lib/src/org/godotengine/godot/utils/PermissionsUtil.java157
-rw-r--r--platform/android/java_godot_lib_jni.cpp4
-rw-r--r--platform/android/java_godot_wrapper.cpp30
-rw-r--r--platform/android/java_godot_wrapper.h4
-rw-r--r--platform/android/os_android.cpp10
-rw-r--r--platform/android/os_android.h2
-rw-r--r--scene/2d/sprite.cpp4
-rw-r--r--scene/3d/sprite_3d.cpp4
-rw-r--r--scene/gui/rich_text_label.cpp8
-rw-r--r--scene/gui/spin_box.cpp6
26 files changed, 377 insertions, 98 deletions
diff --git a/core/bind/core_bind.cpp b/core/bind/core_bind.cpp
index d07ba44788..1d451b2982 100644
--- a/core/bind/core_bind.cpp
+++ b/core/bind/core_bind.cpp
@@ -1136,6 +1136,16 @@ bool _OS::request_permission(const String &p_name) {
return OS::get_singleton()->request_permission(p_name);
}
+bool _OS::request_permissions() {
+
+ return OS::get_singleton()->request_permissions();
+}
+
+Vector<String> _OS::get_granted_permissions() const {
+
+ return OS::get_singleton()->get_granted_permissions();
+}
+
_OS *_OS::singleton = NULL;
void _OS::_bind_methods() {
@@ -1319,6 +1329,8 @@ void _OS::_bind_methods() {
ClassDB::bind_method(D_METHOD("get_power_percent_left"), &_OS::get_power_percent_left);
ClassDB::bind_method(D_METHOD("request_permission", "name"), &_OS::request_permission);
+ ClassDB::bind_method(D_METHOD("request_permissions"), &_OS::request_permissions);
+ ClassDB::bind_method(D_METHOD("get_granted_permissions"), &_OS::get_granted_permissions);
ADD_PROPERTY(PropertyInfo(Variant::STRING, "clipboard"), "set_clipboard", "get_clipboard");
ADD_PROPERTY(PropertyInfo(Variant::INT, "current_screen"), "set_current_screen", "get_current_screen");
diff --git a/core/bind/core_bind.h b/core/bind/core_bind.h
index 693b85710a..1a4fd1d5cb 100644
--- a/core/bind/core_bind.h
+++ b/core/bind/core_bind.h
@@ -349,6 +349,8 @@ public:
bool has_feature(const String &p_feature) const;
bool request_permission(const String &p_name);
+ bool request_permissions();
+ Vector<String> get_granted_permissions() const;
static _OS *get_singleton() { return singleton; }
diff --git a/core/os/main_loop.cpp b/core/os/main_loop.cpp
index 5587e827ba..146a301995 100644
--- a/core/os/main_loop.cpp
+++ b/core/os/main_loop.cpp
@@ -65,6 +65,8 @@ void MainLoop::_bind_methods() {
BIND_CONSTANT(NOTIFICATION_OS_IME_UPDATE);
BIND_CONSTANT(NOTIFICATION_APP_RESUMED);
BIND_CONSTANT(NOTIFICATION_APP_PAUSED);
+
+ ADD_SIGNAL(MethodInfo("on_request_permissions_result", PropertyInfo(Variant::STRING, "permission"), PropertyInfo(Variant::BOOL, "granted")));
};
void MainLoop::set_init_script(const Ref<Script> &p_init_script) {
diff --git a/core/os/os.h b/core/os/os.h
index 9b46b43081..b5224c4f63 100644
--- a/core/os/os.h
+++ b/core/os/os.h
@@ -530,6 +530,8 @@ public:
List<String> get_restart_on_exit_arguments() const;
virtual bool request_permission(const String &p_name) { return true; }
+ virtual bool request_permissions() { return true; }
+ virtual Vector<String> get_granted_permissions() const { return Vector<String>(); }
virtual void process_and_drop_events() {}
OS();
diff --git a/doc/classes/HTTPClient.xml b/doc/classes/HTTPClient.xml
index 1a2d5cab81..52e4b94051 100644
--- a/doc/classes/HTTPClient.xml
+++ b/doc/classes/HTTPClient.xml
@@ -44,6 +44,7 @@
</return>
<description>
Returns the response's body length.
+ [b]Note:[/b] Some Web servers may not send a body length. In this case, the value returned will be [code]-1[/code]. If using chunked transfer encoding, the body length will also be [code]-1[/code].
</description>
</method>
<method name="get_response_code" qualifiers="const">
@@ -175,7 +176,7 @@
<argument index="0" name="bytes" type="int">
</argument>
<description>
- Sets the size of the buffer used and maximum bytes to read per iteration. see [method read_response_body_chunk]
+ Sets the size of the buffer used and maximum bytes to read per iteration. See [method read_response_body_chunk].
</description>
</method>
</methods>
diff --git a/doc/classes/HTTPRequest.xml b/doc/classes/HTTPRequest.xml
index 53ee0b6132..3a73d44a01 100644
--- a/doc/classes/HTTPRequest.xml
+++ b/doc/classes/HTTPRequest.xml
@@ -23,6 +23,7 @@
</return>
<description>
Returns the response body length.
+ [b]Note:[/b] Some Web servers may not send a body length. In this case, the value returned will be [code]-1[/code]. If using chunked transfer encoding, the body length will also be [code]-1[/code].
</description>
</method>
<method name="get_downloaded_bytes" qualifiers="const">
diff --git a/doc/classes/MainLoop.xml b/doc/classes/MainLoop.xml
index 9e65da8eea..9457800825 100644
--- a/doc/classes/MainLoop.xml
+++ b/doc/classes/MainLoop.xml
@@ -167,6 +167,17 @@
</description>
</method>
</methods>
+ <signals>
+ <signal name="on_request_permissions_result">
+ <argument index="0" name="permission" type="String">
+ </argument>
+ <argument index="1" name="granted" type="bool">
+ </argument>
+ <description>
+ Emitted when an user responds to permission request.
+ </description>
+ </signal>
+ </signals>
<constants>
<constant name="NOTIFICATION_WM_MOUSE_ENTER" value="1002">
Notification received from the OS when the mouse enters the game window.
diff --git a/doc/classes/OS.xml b/doc/classes/OS.xml
index 9e1b25abe1..13a2fc2602 100644
--- a/doc/classes/OS.xml
+++ b/doc/classes/OS.xml
@@ -217,6 +217,13 @@
Returns the path to the current engine executable.
</description>
</method>
+ <method name="get_granted_permissions">
+ <return type="PoolStringArray">
+ </return>
+ <description>
+ With this function you can get the list of dangerous permissions that have been granted to the Android application.
+ </description>
+ </method>
<method name="get_ime_selection" qualifiers="const">
<return type="Vector2">
</return>
@@ -744,6 +751,13 @@
At the moment this function is only used by [code]AudioDriverOpenSL[/code] to request permission for [code]RECORD_AUDIO[/code] on Android.
</description>
</method>
+ <method name="request_permissions">
+ <return type="bool">
+ </return>
+ <description>
+ With this function you can request dangerous permissions since normal permissions are automatically granted at install time in Android application.
+ </description>
+ </method>
<method name="set_icon">
<return type="void">
</return>
diff --git a/editor/editor_inspector.cpp b/editor/editor_inspector.cpp
index 78e058eeaa..96b6a32914 100644
--- a/editor/editor_inspector.cpp
+++ b/editor/editor_inspector.cpp
@@ -644,7 +644,19 @@ void EditorProperty::_gui_input(const Ref<InputEvent> &p_event) {
emit_signal("property_keyed", property, use_keying_next());
if (use_keying_next()) {
- call_deferred("emit_changed", property, object->get(property).operator int64_t() + 1, "", false);
+ if (property == "frame_coords" && (object->is_class("Sprite") || object->is_class("Sprite3D"))) {
+ Vector2 new_coords = object->get(property);
+ new_coords.x++;
+ if (new_coords.x >= object->get("hframes").operator int64_t()) {
+ new_coords.x = 0;
+ new_coords.y++;
+ }
+
+ call_deferred("emit_changed", property, new_coords, "", false);
+ } else {
+ call_deferred("emit_changed", property, object->get(property).operator int64_t() + 1, "", false);
+ }
+
call_deferred("update_property");
}
}
diff --git a/editor/editor_properties.cpp b/editor/editor_properties.cpp
index fbb66744a7..d3d91e6e0d 100644
--- a/editor/editor_properties.cpp
+++ b/editor/editor_properties.cpp
@@ -2205,7 +2205,14 @@ void EditorPropertyResource::_menu_option(int p_which) {
case OBJ_MENU_NEW_SCRIPT: {
if (Object::cast_to<Node>(get_edited_object())) {
- EditorNode::get_singleton()->get_scene_tree_dock()->open_script_dialog(Object::cast_to<Node>(get_edited_object()));
+ EditorNode::get_singleton()->get_scene_tree_dock()->open_script_dialog(Object::cast_to<Node>(get_edited_object()), false);
+ }
+
+ } break;
+ case OBJ_MENU_EXTEND_SCRIPT: {
+
+ if (Object::cast_to<Node>(get_edited_object())) {
+ EditorNode::get_singleton()->get_scene_tree_dock()->open_script_dialog(Object::cast_to<Node>(get_edited_object()), true);
}
} break;
@@ -2338,7 +2345,8 @@ void EditorPropertyResource::_update_menu_items() {
menu->clear();
if (get_edited_property() == "script" && base_type == "Script" && Object::cast_to<Node>(get_edited_object())) {
- menu->add_icon_item(get_icon("Script", "EditorIcons"), TTR("New Script"), OBJ_MENU_NEW_SCRIPT);
+ menu->add_icon_item(get_icon("ScriptCreate", "EditorIcons"), TTR("New Script"), OBJ_MENU_NEW_SCRIPT);
+ menu->add_icon_item(get_icon("ScriptExtend", "EditorIcons"), TTR("Extend Script"), OBJ_MENU_EXTEND_SCRIPT);
menu->add_separator();
} else if (base_type != "") {
int idx = 0;
@@ -2633,7 +2641,6 @@ void EditorPropertyResource::update_property() {
if (res == RES()) {
assign->set_icon(Ref<Texture>());
assign->set_text(TTR("[empty]"));
- assign->set_tooltip("");
} else {
assign->set_icon(EditorNode::get_singleton()->get_object_icon(res.operator->(), "Object"));
@@ -2909,7 +2916,7 @@ void EditorInspectorDefaultPlugin::parse_begin(Object *p_object) {
bool EditorInspectorDefaultPlugin::parse_property(Object *p_object, Variant::Type p_type, const String &p_path, PropertyHint p_hint, const String &p_hint_text, int p_usage) {
- double default_float_step = EDITOR_GET("interface/inspector/default_float_step");
+ float default_float_step = EDITOR_GET("interface/inspector/default_float_step");
switch (p_type) {
diff --git a/editor/editor_properties.h b/editor/editor_properties.h
index b8d6aa00c2..952b0447e2 100644
--- a/editor/editor_properties.h
+++ b/editor/editor_properties.h
@@ -550,7 +550,8 @@ class EditorPropertyResource : public EditorProperty {
OBJ_MENU_COPY = 5,
OBJ_MENU_PASTE = 6,
OBJ_MENU_NEW_SCRIPT = 7,
- OBJ_MENU_SHOW_IN_FILE_SYSTEM = 8,
+ OBJ_MENU_EXTEND_SCRIPT = 8,
+ OBJ_MENU_SHOW_IN_FILE_SYSTEM = 9,
TYPE_BASE_ID = 100,
CONVERT_BASE_ID = 1000
diff --git a/editor/property_editor.cpp b/editor/property_editor.cpp
index ecb272876d..ce82d44164 100644
--- a/editor/property_editor.cpp
+++ b/editor/property_editor.cpp
@@ -246,7 +246,13 @@ void CustomPropertyEditor::_menu_option(int p_which) {
case OBJ_MENU_NEW_SCRIPT: {
if (Object::cast_to<Node>(owner))
- EditorNode::get_singleton()->get_scene_tree_dock()->open_script_dialog(Object::cast_to<Node>(owner));
+ EditorNode::get_singleton()->get_scene_tree_dock()->open_script_dialog(Object::cast_to<Node>(owner), false);
+
+ } break;
+ case OBJ_MENU_EXTEND_SCRIPT: {
+
+ if (Object::cast_to<Node>(owner))
+ EditorNode::get_singleton()->get_scene_tree_dock()->open_script_dialog(Object::cast_to<Node>(owner), true);
} break;
case OBJ_MENU_SHOW_IN_FILE_SYSTEM: {
diff --git a/editor/property_editor.h b/editor/property_editor.h
index 029c2211d5..b1c61c5e25 100644
--- a/editor/property_editor.h
+++ b/editor/property_editor.h
@@ -77,7 +77,8 @@ class CustomPropertyEditor : public Popup {
OBJ_MENU_COPY = 4,
OBJ_MENU_PASTE = 5,
OBJ_MENU_NEW_SCRIPT = 6,
- OBJ_MENU_SHOW_IN_FILE_SYSTEM = 7,
+ OBJ_MENU_EXTEND_SCRIPT = 7,
+ OBJ_MENU_SHOW_IN_FILE_SYSTEM = 8,
TYPE_BASE_ID = 100,
CONVERT_BASE_ID = 1000
};
diff --git a/editor/scene_tree_dock.cpp b/editor/scene_tree_dock.cpp
index 98ab1bfb54..0884620e5d 100644
--- a/editor/scene_tree_dock.cpp
+++ b/editor/scene_tree_dock.cpp
@@ -421,53 +421,11 @@ void SceneTreeDock::_tool_selected(int p_tool, bool p_confirm_override) {
create_dialog->popup_create(false, true, selected->get_class());
} break;
+ case TOOL_EXTEND_SCRIPT: {
+ attach_script_to_selected(true);
+ } break;
case TOOL_ATTACH_SCRIPT: {
-
- if (!profile_allow_script_editing) {
- break;
- }
-
- List<Node *> selection = editor_selection->get_selected_node_list();
- if (selection.empty())
- break;
-
- Node *selected = scene_tree->get_selected();
- if (!selected)
- selected = selection.front()->get();
-
- Ref<Script> existing = selected->get_script();
-
- String path = selected->get_filename();
- if (path == "") {
- String root_path = editor_data->get_edited_scene_root()->get_filename();
- if (root_path == "") {
- path = String("res://").plus_file(selected->get_name());
- } else {
- path = root_path.get_base_dir().plus_file(selected->get_name());
- }
- }
-
- String inherits = selected->get_class();
- if (existing.is_valid()) {
- for (int i = 0; i < ScriptServer::get_language_count(); i++) {
- ScriptLanguage *l = ScriptServer::get_language(i);
- if (l->get_type() == existing->get_class()) {
- String name = l->get_global_class_name(existing->get_path());
- if (ScriptServer::is_global_class(name) && EDITOR_GET("interface/editors/derive_script_globals_by_name").operator bool()) {
- inherits = name;
- } else if (l->can_inherit_from_file()) {
- inherits = "\"" + existing->get_path() + "\"";
- }
- break;
- }
- }
- }
- script_create_dialog->connect("script_created", this, "_script_created");
- script_create_dialog->connect("popup_hide", this, "_script_creation_closed");
- script_create_dialog->set_inheritance_base_type("Node");
- script_create_dialog->config(inherits, path);
- script_create_dialog->popup_centered();
-
+ attach_script_to_selected(false);
} break;
case TOOL_CLEAR_SCRIPT: {
@@ -2482,10 +2440,9 @@ void SceneTreeDock::_tree_rmb(const Vector2 &p_menu_pos) {
if (profile_allow_script_editing) {
if (selection.size() == 1) {
- if (!existing_script.is_valid()) {
- menu->add_icon_shortcut(get_icon("ScriptCreate", "EditorIcons"), ED_GET_SHORTCUT("scene_tree/attach_script"), TOOL_ATTACH_SCRIPT);
- } else {
- menu->add_icon_shortcut(get_icon("ScriptExtend", "EditorIcons"), ED_GET_SHORTCUT("scene_tree/extend_script"), TOOL_ATTACH_SCRIPT);
+ menu->add_icon_shortcut(get_icon("ScriptCreate", "EditorIcons"), ED_GET_SHORTCUT("scene_tree/attach_script"), TOOL_ATTACH_SCRIPT);
+ if (existing_script.is_valid()) {
+ menu->add_icon_shortcut(get_icon("ScriptExtend", "EditorIcons"), ED_GET_SHORTCUT("scene_tree/extend_script"), TOOL_EXTEND_SCRIPT);
}
}
if (selection.size() > 1 || (existing_script.is_valid() && exisiting_script_removable)) {
@@ -2595,10 +2552,64 @@ void SceneTreeDock::_focus_node() {
}
}
-void SceneTreeDock::open_script_dialog(Node *p_for_node) {
+void SceneTreeDock::attach_script_to_selected(bool p_extend) {
+ if (!profile_allow_script_editing) {
+ return;
+ }
+
+ List<Node *> selection = editor_selection->get_selected_node_list();
+ if (selection.empty())
+ return;
+
+ Node *selected = scene_tree->get_selected();
+ if (!selected)
+ selected = selection.front()->get();
+
+ Ref<Script> existing = selected->get_script();
+
+ String path = selected->get_filename();
+ if (path == "") {
+ String root_path = editor_data->get_edited_scene_root()->get_filename();
+ if (root_path == "") {
+ path = String("res://").plus_file(selected->get_name());
+ } else {
+ path = root_path.get_base_dir().plus_file(selected->get_name());
+ }
+ }
+
+ String inherits = selected->get_class();
+
+ if (p_extend && existing.is_valid()) {
+ for (int i = 0; i < ScriptServer::get_language_count(); i++) {
+ ScriptLanguage *l = ScriptServer::get_language(i);
+ if (l->get_type() == existing->get_class()) {
+ String name = l->get_global_class_name(existing->get_path());
+ if (ScriptServer::is_global_class(name) && EDITOR_GET("interface/editors/derive_script_globals_by_name").operator bool()) {
+ inherits = name;
+ } else if (l->can_inherit_from_file()) {
+ inherits = "\"" + existing->get_path() + "\"";
+ }
+ break;
+ }
+ }
+ }
+
+ script_create_dialog->connect("script_created", this, "_script_created");
+ script_create_dialog->connect("popup_hide", this, "_script_creation_closed");
+ script_create_dialog->set_inheritance_base_type("Node");
+ script_create_dialog->config(inherits, path);
+ script_create_dialog->popup_centered();
+}
+
+void SceneTreeDock::open_script_dialog(Node *p_for_node, bool p_extend) {
scene_tree->set_selected(p_for_node, false);
- _tool_selected(TOOL_ATTACH_SCRIPT);
+
+ if (p_extend) {
+ _tool_selected(TOOL_EXTEND_SCRIPT);
+ } else {
+ _tool_selected(TOOL_ATTACH_SCRIPT);
+ }
}
void SceneTreeDock::add_remote_tree_editor(Control *p_remote) {
diff --git a/editor/scene_tree_dock.h b/editor/scene_tree_dock.h
index 014ce58e88..4e78b84c53 100644
--- a/editor/scene_tree_dock.h
+++ b/editor/scene_tree_dock.h
@@ -64,6 +64,7 @@ class SceneTreeDock : public VBoxContainer {
TOOL_RENAME,
TOOL_BATCH_RENAME,
TOOL_REPLACE,
+ TOOL_EXTEND_SCRIPT,
TOOL_ATTACH_SCRIPT,
TOOL_CLEAR_SCRIPT,
TOOL_MOVE_UP,
@@ -259,7 +260,8 @@ public:
void replace_node(Node *p_node, Node *p_by_node, bool p_keep_properties = true, bool p_remove_old = true);
- void open_script_dialog(Node *p_for_node);
+ void attach_script_to_selected(bool p_extend);
+ void open_script_dialog(Node *p_for_node, bool p_extend);
ScriptCreateDialog *get_script_create_dialog() { return script_create_dialog; }
diff --git a/platform/android/java/lib/src/org/godotengine/godot/Godot.java b/platform/android/java/lib/src/org/godotengine/godot/Godot.java
index 247f006a69..4dae2dcc53 100644
--- a/platform/android/java/lib/src/org/godotengine/godot/Godot.java
+++ b/platform/android/java/lib/src/org/godotengine/godot/Godot.java
@@ -30,7 +30,6 @@
package org.godotengine.godot;
-import android.Manifest;
import android.annotation.SuppressLint;
import android.app.Activity;
import android.app.ActivityManager;
@@ -64,7 +63,6 @@ import android.os.Vibrator;
import android.provider.Settings.Secure;
import android.support.annotation.Keep;
import android.support.annotation.Nullable;
-import android.support.v4.content.ContextCompat;
import android.view.Display;
import android.view.KeyEvent;
import android.view.MotionEvent;
@@ -98,14 +96,12 @@ import java.util.Locale;
import javax.microedition.khronos.opengles.GL10;
import org.godotengine.godot.input.GodotEditText;
import org.godotengine.godot.payments.PaymentsManager;
+import org.godotengine.godot.utils.PermissionsUtil;
import org.godotengine.godot.xr.XRMode;
public abstract class Godot extends Activity implements SensorEventListener, IDownloaderClient {
static final int MAX_SINGLETONS = 64;
- static final int REQUEST_RECORD_AUDIO_PERMISSION = 1;
- static final int REQUEST_CAMERA_PERMISSION = 2;
- static final int REQUEST_VIBRATE_PERMISSION = 3;
private IStub mDownloaderClientStub;
private TextView mStatusText;
private TextView mProgressFraction;
@@ -1007,32 +1003,15 @@ public abstract class Godot extends Activity implements SensorEventListener, IDo
}
public boolean requestPermission(String p_name) {
- if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
- // Not necessary, asked on install already
- return true;
- }
-
- if (p_name.equals("RECORD_AUDIO")) {
- if (ContextCompat.checkSelfPermission(this, Manifest.permission.RECORD_AUDIO) != PackageManager.PERMISSION_GRANTED) {
- requestPermissions(new String[] { Manifest.permission.RECORD_AUDIO }, REQUEST_RECORD_AUDIO_PERMISSION);
- return false;
- }
- }
+ return PermissionsUtil.requestPermission(p_name, this);
+ }
- if (p_name.equals("CAMERA")) {
- if (ContextCompat.checkSelfPermission(this, Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED) {
- requestPermissions(new String[] { Manifest.permission.CAMERA }, REQUEST_CAMERA_PERMISSION);
- return false;
- }
- }
+ public boolean requestPermissions() {
+ return PermissionsUtil.requestManifestPermissions(this);
+ }
- if (p_name.equals("VIBRATE")) {
- if (ContextCompat.checkSelfPermission(this, Manifest.permission.VIBRATE) != PackageManager.PERMISSION_GRANTED) {
- requestPermissions(new String[] { Manifest.permission.VIBRATE }, REQUEST_VIBRATE_PERMISSION);
- return false;
- }
- }
- return true;
+ public String[] getGrantedPermissions() {
+ return PermissionsUtil.getGrantedPermissions(this);
}
/**
diff --git a/platform/android/java/lib/src/org/godotengine/godot/utils/PermissionsUtil.java b/platform/android/java/lib/src/org/godotengine/godot/utils/PermissionsUtil.java
new file mode 100644
index 0000000000..2c4a444e5a
--- /dev/null
+++ b/platform/android/java/lib/src/org/godotengine/godot/utils/PermissionsUtil.java
@@ -0,0 +1,157 @@
+package org.godotengine.godot.utils;
+
+import android.Manifest;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.PermissionInfo;
+import android.os.Build;
+import android.support.v4.content.ContextCompat;
+import java.util.ArrayList;
+import java.util.List;
+import org.godotengine.godot.Godot;
+
+/**
+ * This class includes utility functions for Android permissions related operations.
+ * @author Cagdas Caglak <cagdascaglak@gmail.com>
+ */
+public final class PermissionsUtil {
+
+ static final int REQUEST_RECORD_AUDIO_PERMISSION = 1;
+ static final int REQUEST_CAMERA_PERMISSION = 2;
+ static final int REQUEST_VIBRATE_PERMISSION = 3;
+ static final int REQUEST_ALL_PERMISSION_REQ_CODE = 1001;
+
+ private PermissionsUtil() {
+ }
+
+ /**
+ * Request a dangerous permission. name must be specified in <a href="https://github.com/aosp-mirror/platform_frameworks_base/blob/master/core/res/AndroidManifest.xml">this</a>
+ * @param name the name of the requested permission.
+ * @param activity the caller activity for this method.
+ * @return true/false. "true" if permission was granted otherwise returns "false".
+ */
+ public static boolean requestPermission(String name, Godot activity) {
+ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
+ // Not necessary, asked on install already
+ return true;
+ }
+
+ if (name.equals("RECORD_AUDIO") && ContextCompat.checkSelfPermission(activity, Manifest.permission.RECORD_AUDIO) != PackageManager.PERMISSION_GRANTED) {
+ activity.requestPermissions(new String[] { Manifest.permission.RECORD_AUDIO }, REQUEST_RECORD_AUDIO_PERMISSION);
+ return false;
+ }
+
+ if (name.equals("CAMERA") && ContextCompat.checkSelfPermission(activity, Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED) {
+ activity.requestPermissions(new String[] { Manifest.permission.CAMERA }, REQUEST_CAMERA_PERMISSION);
+ return false;
+ }
+
+ if (name.equals("VIBRATE") && ContextCompat.checkSelfPermission(activity, Manifest.permission.VIBRATE) != PackageManager.PERMISSION_GRANTED) {
+ activity.requestPermissions(new String[] { Manifest.permission.VIBRATE }, REQUEST_VIBRATE_PERMISSION);
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * Request dangerous permissions which are defined in the Android manifest file from the user.
+ * @param activity the caller activity for this method.
+ * @return true/false. "true" if all permissions were granted otherwise returns "false".
+ */
+ public static boolean requestManifestPermissions(Godot activity) {
+ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
+ return true;
+ }
+
+ String[] manifestPermissions;
+ try {
+ manifestPermissions = getManifestPermissions(activity);
+ } catch (PackageManager.NameNotFoundException e) {
+ e.printStackTrace();
+ return false;
+ }
+
+ if (manifestPermissions == null || manifestPermissions.length == 0)
+ return true;
+
+ List<String> dangerousPermissions = new ArrayList<>();
+ for (String manifestPermission : manifestPermissions) {
+ try {
+ PermissionInfo permissionInfo = getPermissionInfo(activity, manifestPermission);
+ int protectionLevel = Build.VERSION.SDK_INT >= Build.VERSION_CODES.P ? permissionInfo.getProtection() : permissionInfo.protectionLevel;
+ if (protectionLevel == PermissionInfo.PROTECTION_DANGEROUS && ContextCompat.checkSelfPermission(activity, manifestPermission) != PackageManager.PERMISSION_GRANTED) {
+ dangerousPermissions.add(manifestPermission);
+ }
+ } catch (PackageManager.NameNotFoundException e) {
+ e.printStackTrace();
+ return false;
+ }
+ }
+
+ if (dangerousPermissions.isEmpty()) {
+ // If list is empty, all of dangerous permissions were granted.
+ return true;
+ }
+
+ String[] requestedPermissions = dangerousPermissions.toArray(new String[0]);
+ activity.requestPermissions(requestedPermissions, REQUEST_ALL_PERMISSION_REQ_CODE);
+ return false;
+ }
+
+ /**
+ * With this function you can get the list of dangerous permissions that have been granted to the Android application.
+ * @param activity the caller activity for this method.
+ * @return granted permissions list
+ */
+ public static String[] getGrantedPermissions(Godot activity) {
+ String[] manifestPermissions;
+ try {
+ manifestPermissions = getManifestPermissions(activity);
+ } catch (PackageManager.NameNotFoundException e) {
+ e.printStackTrace();
+ return new String[0];
+ }
+ if (manifestPermissions == null || manifestPermissions.length == 0)
+ return new String[0];
+
+ List<String> dangerousPermissions = new ArrayList<>();
+ for (String manifestPermission : manifestPermissions) {
+ try {
+ PermissionInfo permissionInfo = getPermissionInfo(activity, manifestPermission);
+ int protectionLevel = Build.VERSION.SDK_INT >= Build.VERSION_CODES.P ? permissionInfo.getProtection() : permissionInfo.protectionLevel;
+ if (protectionLevel == PermissionInfo.PROTECTION_DANGEROUS && ContextCompat.checkSelfPermission(activity, manifestPermission) == PackageManager.PERMISSION_GRANTED) {
+ dangerousPermissions.add(manifestPermission);
+ }
+ } catch (PackageManager.NameNotFoundException e) {
+ e.printStackTrace();
+ return new String[0];
+ }
+ }
+
+ return dangerousPermissions.toArray(new String[0]);
+ }
+
+ /**
+ * Returns the permissions defined in the AndroidManifest.xml file.
+ * @param activity the caller activity for this method.
+ * @return manifest permissions list
+ * @throws PackageManager.NameNotFoundException the exception is thrown when a given package, application, or component name cannot be found.
+ */
+ private static String[] getManifestPermissions(Godot activity) throws PackageManager.NameNotFoundException {
+ PackageManager packageManager = activity.getPackageManager();
+ PackageInfo packageInfo = packageManager.getPackageInfo(activity.getPackageName(), PackageManager.GET_PERMISSIONS);
+ return packageInfo.requestedPermissions;
+ }
+
+ /**
+ * Returns the information of the desired permission.
+ * @param activity the caller activity for this method.
+ * @param permission the name of the permission.
+ * @return permission info object
+ * @throws PackageManager.NameNotFoundException the exception is thrown when a given package, application, or component name cannot be found.
+ */
+ private static PermissionInfo getPermissionInfo(Godot activity, String permission) throws PackageManager.NameNotFoundException {
+ PackageManager packageManager = activity.getPackageManager();
+ return packageManager.getPermissionInfo(permission, 0);
+ }
+}
diff --git a/platform/android/java_godot_lib_jni.cpp b/platform/android/java_godot_lib_jni.cpp
index 7daea19961..a14e0a1960 100644
--- a/platform/android/java_godot_lib_jni.cpp
+++ b/platform/android/java_godot_lib_jni.cpp
@@ -1393,6 +1393,10 @@ JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_requestPermissionResu
if (permission == "android.permission.RECORD_AUDIO" && p_result) {
AudioDriver::get_singleton()->capture_start();
}
+
+ if (os_android->get_main_loop()) {
+ os_android->get_main_loop()->emit_signal("on_request_permissions_result", permission, p_result == JNI_TRUE);
+ }
}
JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_onRendererResumed(JNIEnv *env, jclass clazz) {
diff --git a/platform/android/java_godot_wrapper.cpp b/platform/android/java_godot_wrapper.cpp
index 8194ee6ecf..e3e613d30b 100644
--- a/platform/android/java_godot_wrapper.cpp
+++ b/platform/android/java_godot_wrapper.cpp
@@ -59,6 +59,8 @@ GodotJavaWrapper::GodotJavaWrapper(JNIEnv *p_env, jobject p_godot_instance) {
_get_clipboard = p_env->GetMethodID(cls, "getClipboard", "()Ljava/lang/String;");
_set_clipboard = p_env->GetMethodID(cls, "setClipboard", "(Ljava/lang/String;)V");
_request_permission = p_env->GetMethodID(cls, "requestPermission", "(Ljava/lang/String;)Z");
+ _request_permissions = p_env->GetMethodID(cls, "requestPermissions", "()Z");
+ _get_granted_permissions = p_env->GetMethodID(cls, "getGrantedPermissions", "()[Ljava/lang/String;");
_init_input_devices = p_env->GetMethodID(cls, "initInputDevices", "()V");
_get_surface = p_env->GetMethodID(cls, "getSurface", "()Landroid/view/Surface;");
_is_activity_resumed = p_env->GetMethodID(cls, "isActivityResumed", "()Z");
@@ -199,6 +201,34 @@ bool GodotJavaWrapper::request_permission(const String &p_name) {
}
}
+bool GodotJavaWrapper::request_permissions() {
+ if (_request_permissions) {
+ JNIEnv *env = ThreadAndroid::get_env();
+ return env->CallBooleanMethod(godot_instance, _request_permissions);
+ } else {
+ return false;
+ }
+}
+
+Vector<String> GodotJavaWrapper::get_granted_permissions() const {
+ Vector<String> permissions_list;
+ if (_get_granted_permissions) {
+ JNIEnv *env = ThreadAndroid::get_env();
+ jobject permissions_object = env->CallObjectMethod(godot_instance, _get_granted_permissions);
+ jobjectArray *arr = reinterpret_cast<jobjectArray *>(&permissions_object);
+
+ int i = 0;
+ jsize len = env->GetArrayLength(*arr);
+ for (i = 0; i < len; i++) {
+ jstring jstr = (jstring)env->GetObjectArrayElement(*arr, i);
+ String str = jstring_to_string(jstr, env);
+ permissions_list.push_back(str);
+ env->DeleteLocalRef(jstr);
+ }
+ }
+ return permissions_list;
+}
+
void GodotJavaWrapper::init_input_devices() {
if (_init_input_devices) {
JNIEnv *env = ThreadAndroid::get_env();
diff --git a/platform/android/java_godot_wrapper.h b/platform/android/java_godot_wrapper.h
index b1bd9b7f48..d23ff273cb 100644
--- a/platform/android/java_godot_wrapper.h
+++ b/platform/android/java_godot_wrapper.h
@@ -54,6 +54,8 @@ private:
jmethodID _get_clipboard = 0;
jmethodID _set_clipboard = 0;
jmethodID _request_permission = 0;
+ jmethodID _request_permissions = 0;
+ jmethodID _get_granted_permissions = 0;
jmethodID _init_input_devices = 0;
jmethodID _get_surface = 0;
jmethodID _is_activity_resumed = 0;
@@ -81,6 +83,8 @@ public:
bool has_set_clipboard();
void set_clipboard(const String &p_text);
bool request_permission(const String &p_name);
+ bool request_permissions();
+ Vector<String> get_granted_permissions() const;
void init_input_devices();
jobject get_surface();
bool is_activity_resumed();
diff --git a/platform/android/os_android.cpp b/platform/android/os_android.cpp
index 91bd6cbdd2..defee8f1f1 100644
--- a/platform/android/os_android.cpp
+++ b/platform/android/os_android.cpp
@@ -220,6 +220,16 @@ bool OS_Android::request_permission(const String &p_name) {
return godot_java->request_permission(p_name);
}
+bool OS_Android::request_permissions() {
+
+ return godot_java->request_permissions();
+}
+
+Vector<String> OS_Android::get_granted_permissions() const {
+
+ return godot_java->get_granted_permissions();
+}
+
Error OS_Android::open_dynamic_library(const String p_path, void *&p_library_handle, bool p_also_set_library_path) {
p_library_handle = dlopen(p_path.utf8().get_data(), RTLD_NOW);
ERR_FAIL_COND_V_MSG(!p_library_handle, ERR_CANT_OPEN, "Can't open dynamic library: " + p_path + ", error: " + dlerror() + ".");
diff --git a/platform/android/os_android.h b/platform/android/os_android.h
index 9bad9b2e01..a290c0cedd 100644
--- a/platform/android/os_android.h
+++ b/platform/android/os_android.h
@@ -125,6 +125,8 @@ public:
virtual void alert(const String &p_alert, const String &p_title = "ALERT!");
virtual bool request_permission(const String &p_name);
+ virtual bool request_permissions();
+ virtual Vector<String> get_granted_permissions() const;
virtual Error open_dynamic_library(const String p_path, void *&p_library_handle, bool p_also_set_library_path = false);
diff --git a/scene/2d/sprite.cpp b/scene/2d/sprite.cpp
index d2e1e494e3..8cdfceea52 100644
--- a/scene/2d/sprite.cpp
+++ b/scene/2d/sprite.cpp
@@ -387,6 +387,10 @@ void Sprite::_validate_property(PropertyInfo &property) const {
property.hint_string = "0," + itos(vframes * hframes - 1) + ",1";
property.usage |= PROPERTY_USAGE_KEYING_INCREMENTS;
}
+
+ if (property.name == "frame_coords") {
+ property.usage |= PROPERTY_USAGE_KEYING_INCREMENTS;
+ }
}
void Sprite::_texture_changed() {
diff --git a/scene/3d/sprite_3d.cpp b/scene/3d/sprite_3d.cpp
index a8d2f4d415..adcd80b0ab 100644
--- a/scene/3d/sprite_3d.cpp
+++ b/scene/3d/sprite_3d.cpp
@@ -662,6 +662,10 @@ void Sprite3D::_validate_property(PropertyInfo &property) const {
property.hint_string = "0," + itos(vframes * hframes - 1) + ",1";
property.usage |= PROPERTY_USAGE_KEYING_INCREMENTS;
}
+
+ if (property.name == "frame_coords") {
+ property.usage |= PROPERTY_USAGE_KEYING_INCREMENTS;
+ }
}
void Sprite3D::_bind_methods() {
diff --git a/scene/gui/rich_text_label.cpp b/scene/gui/rich_text_label.cpp
index 0ce63a7c6f..8c19255fd0 100644
--- a/scene/gui/rich_text_label.cpp
+++ b/scene/gui/rich_text_label.cpp
@@ -555,12 +555,12 @@ int RichTextLabel::_process_line(ItemFrame *p_frame, const Vector2 &p_ofs, int &
if (p_font_color_shadow.a > 0) {
float x_ofs_shadow = align_ofs + pofs;
float y_ofs_shadow = y + lh - line_descent;
- font->draw_char(ci, Point2(x_ofs_shadow, y_ofs_shadow) + shadow_ofs, fx_char, c[i + 1], p_font_color_shadow);
+ font->draw_char(ci, Point2(x_ofs_shadow, y_ofs_shadow) + shadow_ofs + fx_offset, fx_char, c[i + 1], p_font_color_shadow);
if (p_shadow_as_outline) {
- font->draw_char(ci, Point2(x_ofs_shadow, y_ofs_shadow) + Vector2(-shadow_ofs.x, shadow_ofs.y), fx_char, c[i + 1], p_font_color_shadow);
- font->draw_char(ci, Point2(x_ofs_shadow, y_ofs_shadow) + Vector2(shadow_ofs.x, -shadow_ofs.y), fx_char, c[i + 1], p_font_color_shadow);
- font->draw_char(ci, Point2(x_ofs_shadow, y_ofs_shadow) + Vector2(-shadow_ofs.x, -shadow_ofs.y), fx_char, c[i + 1], p_font_color_shadow);
+ font->draw_char(ci, Point2(x_ofs_shadow, y_ofs_shadow) + Vector2(-shadow_ofs.x, shadow_ofs.y) + fx_offset, fx_char, c[i + 1], p_font_color_shadow);
+ font->draw_char(ci, Point2(x_ofs_shadow, y_ofs_shadow) + Vector2(shadow_ofs.x, -shadow_ofs.y) + fx_offset, fx_char, c[i + 1], p_font_color_shadow);
+ font->draw_char(ci, Point2(x_ofs_shadow, y_ofs_shadow) + Vector2(-shadow_ofs.x, -shadow_ofs.y) + fx_offset, fx_char, c[i + 1], p_font_color_shadow);
}
}
diff --git a/scene/gui/spin_box.cpp b/scene/gui/spin_box.cpp
index 172c366c41..bf067898e6 100644
--- a/scene/gui/spin_box.cpp
+++ b/scene/gui/spin_box.cpp
@@ -108,21 +108,21 @@ void SpinBox::_gui_input(const Ref<InputEvent> &p_event) {
case BUTTON_LEFT: {
+ line_edit->grab_focus();
+
set_value(get_value() + (up ? get_step() : -get_step()));
range_click_timer->set_wait_time(0.6);
range_click_timer->set_one_shot(true);
range_click_timer->start();
- line_edit->grab_focus();
-
drag.allowed = true;
drag.capture_pos = mb->get_position();
} break;
case BUTTON_RIGHT: {
- set_value((up ? get_max() : get_min()));
line_edit->grab_focus();
+ set_value((up ? get_max() : get_min()));
} break;
case BUTTON_WHEEL_UP: {
if (line_edit->has_focus()) {