Compare commits

...

8 commits

Author SHA1 Message Date
Rémi Verschelde 7722461dc5
Merge pull request #72496 from m4gr3d/implement_file_provider_3x
[3.x] Implement file provider capabilities
2023-02-01 13:06:55 +01:00
Rémi Verschelde 617f5c5580
Merge pull request #64424 from RandomShaper/safe_input_synth_3.x
[3.x] Warn users about unsafe usage of `InputEvent`
2023-02-01 13:06:32 +01:00
Rémi Verschelde 44656e8328
Merge pull request #72386 from smix8/navigation_config_warnings_3.x
Fix navigation related nodes not propagating parent class config warnings
2023-02-01 13:06:06 +01:00
Rémi Verschelde fb846d3522
Merge pull request #71425 from Calinou/spinbox-add-independent-arrow-step-3.x
Add independent spinbox arrow step precision
2023-02-01 13:05:43 +01:00
Fredia Huya-Kouadio b04c9a71f4 Implement file provider capabilities
The previously used file sharing api was restricted after Android N causing the engine to crash whenever used on devices running Android N or higher.
2023-02-01 01:01:20 -08:00
Pedro J. Estébanez 9cd84224bd Warn users about unsafe usage of InputEvent 2023-01-31 14:41:27 +01:00
smix8 b5213cceac Fix navigation related nodes not propagating parent class config warnings
Fixes that navigation related nodes do not propagate config warnings from their parent classes.
2023-01-30 16:26:42 +01:00
Jóhannes Gunnar Þorsteinsson e09d4d255e
Add independent spinbox arrow step precision
Backported from the `master` branch, with a property hint added.
2023-01-14 20:14:17 +01:00
17 changed files with 170 additions and 41 deletions

View file

@ -39,6 +39,9 @@
<member name="align" type="int" setter="set_align" getter="get_align" enum="LineEdit.Align" default="0">
Sets the text alignment of the [SpinBox].
</member>
<member name="custom_arrow_step" type="float" setter="set_custom_arrow_step" getter="get_custom_arrow_step" default="0.0">
If not [code]0[/code], [code]value[/code] will always be rounded to a multiple of [code]custom_arrow_step[/code] when interacting with the arrow buttons of the [SpinBox].
</member>
<member name="editable" type="bool" setter="set_editable" getter="is_editable" default="true">
If [code]true[/code], the [SpinBox] will be editable. Otherwise, it will be read only.
</member>

View file

@ -708,6 +708,31 @@ void InputDefault::parse_input_event(const Ref<InputEvent> &p_event) {
ERR_FAIL_COND(p_event.is_null());
#ifdef DEBUG_ENABLED
uint64_t curr_frame = Engine::get_singleton()->get_idle_frames();
if (curr_frame != last_parsed_frame) {
frame_parsed_events.clear();
last_parsed_frame = curr_frame;
frame_parsed_events.insert(p_event);
} else if (frame_parsed_events.has(p_event)) {
// It would be technically safe to send the same event in cases such as:
// - After an explicit flush.
// - In platforms using buffering when agile flushing is enabled, after one of the mid-frame flushes.
// - If platform doesn't use buffering and event accumulation is disabled.
// - If platform doesn't use buffering and the event type is not accumulable.
// However, it wouldn't be reasonable to ask users to remember the full ruleset and be aware at all times
// of the possibilites of the target platform, project settings and engine internals, which may change
// without prior notice.
// Therefore, the guideline is, "don't send the same event object more than once per frame".
WARN_PRINT_ONCE(
"An input event object is being parsed more than once in the same frame, which is unsafe.\n"
"If you are generating events in a script, you have to instantiate a new event instead of sending the same one more than once, unless the original one was sent on an earlier frame.\n"
"You can call duplicate() on the event to get a new instance with identical values.");
} else {
frame_parsed_events.insert(p_event);
}
#endif
if (use_accumulated_input) {
if (buffered_events.empty() || !buffered_events.back()->get()->accumulate(p_event)) {
buffered_events.push_back(p_event);

View file

@ -204,6 +204,11 @@ private:
bool use_input_buffering;
bool use_accumulated_input;
#ifdef DEBUG_ENABLED
Set<Ref<InputEvent>> frame_parsed_events;
uint64_t last_parsed_frame = UINT64_MAX;
#endif
protected:
struct VibrationInfo {
float weak_magnitude;

View file

@ -968,6 +968,10 @@ void EditorExportPlatformAndroid::_fix_manifest(const Ref<EditorExportPreset> &p
encode_uint32(is_resizeable, &p_manifest.write[iofs + 16]);
}
if (tname == "provider" && attrname == "authorities") {
string_table.write[attr_value] = get_package_name(package_name) + String(".fileprovider");
}
if (tname == "supports-screens") {
if (attrname == "smallScreens") {
encode_uint32(screen_support_small ? 0xFFFFFFFF : 0, &p_manifest.write[iofs + 16]);

View file

@ -20,6 +20,16 @@
android:exported="false"
/>
<provider
android:name="androidx.core.content.FileProvider"
android:authorities="${applicationId}.fileprovider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/godot_provider_paths" />
</provider>
</application>
</manifest>

View file

@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
<external-path
name="public"
path="." />
<external-files-path
name="app"
path="." />
</paths>

View file

@ -49,6 +49,9 @@ import android.view.Display;
import android.view.DisplayCutout;
import android.view.WindowInsets;
import androidx.core.content.FileProvider;
import java.io.File;
import java.util.List;
import java.util.Locale;
@ -84,29 +87,42 @@ public class GodotIO {
// MISCELLANEOUS OS IO
/////////////////////////
public int openURI(String p_uri) {
public int openURI(String uriString) {
try {
String path = p_uri;
String type = "";
if (path.startsWith("/")) {
//absolute path to filesystem, prepend file://
path = "file://" + path;
if (p_uri.endsWith(".png") || p_uri.endsWith(".jpg") || p_uri.endsWith(".gif") || p_uri.endsWith(".webp")) {
type = "image/*";
Uri dataUri;
String dataType = "";
boolean grantReadUriPermission = false;
if (uriString.startsWith("/") || uriString.startsWith("file://")) {
String filePath = uriString;
// File uris needs to be provided via the FileProvider
grantReadUriPermission = true;
if (filePath.startsWith("file://")) {
filePath = filePath.replace("file://", "");
}
File targetFile = new File(filePath);
dataUri = FileProvider.getUriForFile(activity, activity.getPackageName() + ".fileprovider", targetFile);
dataType = activity.getContentResolver().getType(dataUri);
} else {
dataUri = Uri.parse(uriString);
}
Intent intent = new Intent();
intent.setAction(Intent.ACTION_VIEW);
if (!type.equals("")) {
intent.setDataAndType(Uri.parse(path), type);
if (TextUtils.isEmpty(dataType)) {
intent.setData(dataUri);
} else {
intent.setData(Uri.parse(path));
intent.setDataAndType(dataUri, dataType);
}
if (grantReadUriPermission) {
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
}
activity.startActivity(intent);
return 0;
} catch (ActivityNotFoundException e) {
Log.e(TAG, "Unable to open uri " + uriString, e);
return 1;
}
}

View file

@ -83,7 +83,14 @@ Vector<Vector2> Navigation2D::get_simple_path(const Vector2 &p_start, const Vect
}
String Navigation2D::get_configuration_warning() const {
return TTR("'Navigation2D' node and 'Navigation2D.get_simple_path()' are deprecated and will be removed in a future version. Use 'Navigation2DServer.map_get_path()' instead.");
String warning = Node2D::get_configuration_warning();
if (warning != String()) {
warning += "\n\n";
}
warning += TTR("'Navigation2D' node and 'Navigation2D.get_simple_path()' are deprecated and will be removed in a future version. Use 'Navigation2DServer.map_get_path()' instead.");
return warning;
}
Vector2 Navigation2D::get_closest_point(const Vector2 &p_point) const {

View file

@ -398,11 +398,16 @@ void NavigationAgent2D::_avoidance_done(Vector3 p_new_velocity) {
}
String NavigationAgent2D::get_configuration_warning() const {
String warning = Node::get_configuration_warning();
if (!Object::cast_to<Node2D>(get_parent())) {
return TTR("The NavigationAgent2D can be used only under a Node2D inheriting parent node.");
if (warning != String()) {
warning += "\n\n";
}
warning += TTR("The NavigationAgent2D can be used only under a Node2D inheriting parent node.");
}
return String();
return warning;
}
void NavigationAgent2D::update_navigation() {

View file

@ -158,16 +158,24 @@ Node *NavigationObstacle2D::get_navigation_node() const {
}
String NavigationObstacle2D::get_configuration_warning() const {
String warning = Node::get_configuration_warning();
if (!Object::cast_to<Node2D>(get_parent())) {
return TTR("The NavigationObstacle2D only serves to provide collision avoidance to a Node2D object.");
if (warning != String()) {
warning += "\n\n";
}
warning += TTR("The NavigationObstacle2D only serves to provide collision avoidance to a Node2D object.");
}
if (Object::cast_to<StaticBody2D>(get_parent())) {
return TTR("The NavigationObstacle2D is intended for constantly moving bodies like KinematicBody2D or RigidBody2D as it creates only an RVO avoidance radius and does not follow scene geometry exactly."
"\nNot constantly moving or complete static objects should be captured with a refreshed NavigationPolygon so agents can not only avoid them but also move along those objects outline at high detail");
if (warning != String()) {
warning += "\n\n";
}
warning += TTR("The NavigationObstacle2D is intended for constantly moving bodies like KinematicBody2D or RigidBody2D as it creates only an RVO avoidance radius and does not follow scene geometry exactly."
"\nNot constantly moving or complete static objects should be (re)baked to a NavigationMesh so agents can not only avoid them but also move along those objects outline at high detail");
}
return String();
return warning;
}
void NavigationObstacle2D::initialize_agent() {

View file

@ -582,11 +582,8 @@ void NavigationPolygonInstance::_map_changed(RID p_map) {
}
String NavigationPolygonInstance::get_configuration_warning() const {
if (!is_visible_in_tree() || !is_inside_tree()) {
return String();
}
String warning = Node2D::get_configuration_warning();
if (!navpoly.is_valid()) {
if (warning != String()) {
warning += "\n\n";

View file

@ -38,7 +38,14 @@ Vector<Vector3> Navigation::get_simple_path(const Vector3 &p_start, const Vector
}
String Navigation::get_configuration_warning() const {
return TTR("'Navigation' node and 'Navigation.get_simple_path()' are deprecated and will be removed in a future version. Use 'NavigationServer.map_get_path()' instead.");
String warning = Spatial::get_configuration_warning();
if (warning != String()) {
warning += "\n\n";
}
warning += TTR("'Navigation' node and 'Navigation.get_simple_path()' are deprecated and will be removed in a future version. Use 'NavigationServer.map_get_path()' instead.");
return warning;
}
Vector3 Navigation::get_closest_point_to_segment(const Vector3 &p_from, const Vector3 &p_to, bool p_use_collision) const {

View file

@ -407,11 +407,16 @@ void NavigationAgent::_avoidance_done(Vector3 p_new_velocity) {
}
String NavigationAgent::get_configuration_warning() const {
String warning = Node::get_configuration_warning();
if (!Object::cast_to<Spatial>(get_parent())) {
return TTR("The NavigationAgent can be used only under a Spatial inheriting parent node.");
if (warning != String()) {
warning += "\n\n";
}
warning += TTR("The NavigationAgent can be used only under a Spatial inheriting parent node.");
}
return String();
return warning;
}
void NavigationAgent::update_navigation() {

View file

@ -245,15 +245,16 @@ void NavigationMeshInstance::_bake_finished(Ref<NavigationMesh> p_nav_mesh) {
}
String NavigationMeshInstance::get_configuration_warning() const {
if (!is_visible_in_tree() || !is_inside_tree()) {
return String();
}
String warning = Spatial::get_configuration_warning();
if (!navmesh.is_valid()) {
return TTR("A NavigationMesh resource must be set or created for this node to work.");
if (warning != String()) {
warning += "\n\n";
}
warning += TTR("A NavigationMesh resource must be set or created for this node to work.");
}
return String();
return warning;
}
void NavigationMeshInstance::_bind_methods() {

View file

@ -164,16 +164,24 @@ Node *NavigationObstacle::get_navigation_node() const {
}
String NavigationObstacle::get_configuration_warning() const {
String warning = Node::get_configuration_warning();
if (!Object::cast_to<Spatial>(get_parent())) {
return TTR("The NavigationObstacle only serves to provide collision avoidance to a Spatial inheriting parent object.");
if (warning != String()) {
warning += "\n\n";
}
warning += TTR("The NavigationObstacle only serves to provide collision avoidance to a Spatial inheriting parent object.");
}
if (Object::cast_to<StaticBody>(get_parent())) {
return TTR("The NavigationObstacle is intended for constantly moving bodies like KinematicBody3D or RigidBody3D as it creates only an RVO avoidance radius and does not follow scene geometry exactly."
"\nNot constantly moving or complete static objects should be (re)baked to a NavigationMesh so agents can not only avoid them but also move along those objects outline at high detail");
if (warning != String()) {
warning += "\n\n";
}
warning += TTR("The NavigationObstacle is intended for constantly moving bodies like KinematicBody or RigidBody as it creates only an RVO avoidance radius and does not follow scene geometry exactly."
"\nNot constantly moving or complete static objects should be (re)baked to a NavigationMesh so agents can not only avoid them but also move along those objects outline at high detail");
}
return String();
return warning;
}
void NavigationObstacle::initialize_agent() {

View file

@ -79,7 +79,8 @@ void SpinBox::_line_edit_input(const Ref<InputEvent> &p_event) {
void SpinBox::_range_click_timeout() {
if (!drag.enabled && Input::get_singleton()->is_mouse_button_pressed(BUTTON_LEFT)) {
bool up = get_local_mouse_position().y < (get_size().height / 2);
set_value(get_value() + (up ? get_step() : -get_step()));
double step = get_custom_arrow_step() != 0.0 ? get_custom_arrow_step() : get_step();
set_value(get_value() + (up ? step : -step));
if (range_click_timer->is_one_shot()) {
range_click_timer->set_wait_time(0.075);
@ -109,6 +110,8 @@ void SpinBox::_gui_input(const Ref<InputEvent> &p_event) {
Ref<InputEventMouseButton> mb = p_event;
double step = get_custom_arrow_step() != 0.0 ? get_custom_arrow_step() : get_step();
if (mb.is_valid() && mb->is_pressed()) {
bool up = mb->get_position().y < (get_size().height / 2);
@ -116,7 +119,7 @@ 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()));
set_value(get_value() + (up ? step : -step));
range_click_timer->set_wait_time(0.6);
range_click_timer->set_one_shot(true);
@ -131,13 +134,13 @@ void SpinBox::_gui_input(const Ref<InputEvent> &p_event) {
} break;
case BUTTON_WHEEL_UP: {
if (line_edit->has_focus()) {
set_value(get_value() + get_step() * mb->get_factor());
set_value(get_value() + step * mb->get_factor());
accept_event();
}
} break;
case BUTTON_WHEEL_DOWN: {
if (line_edit->has_focus()) {
set_value(get_value() - get_step() * mb->get_factor());
set_value(get_value() - step * mb->get_factor());
accept_event();
}
} break;
@ -156,8 +159,8 @@ void SpinBox::_gui_input(const Ref<InputEvent> &p_event) {
if (mm.is_valid() && mm->get_button_mask() & BUTTON_MASK_LEFT) {
if (drag.enabled) {
drag.diff_y += mm->get_relative().y;
float diff_y = -0.01 * Math::pow(ABS(drag.diff_y), 1.8f) * SGN(drag.diff_y);
set_value(CLAMP(drag.base_val + get_step() * diff_y, get_min(), get_max()));
double diff_y = -0.01 * Math::pow(ABS(drag.diff_y), 1.8f) * SGN(drag.diff_y);
set_value(CLAMP(drag.base_val + step * diff_y, get_min(), get_max()));
} else if (drag.allowed && drag.capture_pos.distance_to(mm->get_position()) > 2) {
Input::get_singleton()->set_mouse_mode(Input::MOUSE_MODE_CAPTURED);
drag.enabled = true;
@ -252,6 +255,14 @@ void SpinBox::apply() {
_text_entered(line_edit->get_text());
}
void SpinBox::set_custom_arrow_step(double p_custom_arrow_step) {
custom_arrow_step = p_custom_arrow_step;
}
double SpinBox::get_custom_arrow_step() const {
return custom_arrow_step;
}
void SpinBox::_bind_methods() {
//ClassDB::bind_method(D_METHOD("_value_changed"),&SpinBox::_value_changed);
ClassDB::bind_method(D_METHOD("_gui_input"), &SpinBox::_gui_input);
@ -262,8 +273,10 @@ void SpinBox::_bind_methods() {
ClassDB::bind_method(D_METHOD("get_suffix"), &SpinBox::get_suffix);
ClassDB::bind_method(D_METHOD("set_prefix", "prefix"), &SpinBox::set_prefix);
ClassDB::bind_method(D_METHOD("get_prefix"), &SpinBox::get_prefix);
ClassDB::bind_method(D_METHOD("set_editable", "editable"), &SpinBox::set_editable);
ClassDB::bind_method(D_METHOD("set_editable", "enabled"), &SpinBox::set_editable);
ClassDB::bind_method(D_METHOD("is_editable"), &SpinBox::is_editable);
ClassDB::bind_method(D_METHOD("set_custom_arrow_step", "arrow_step"), &SpinBox::set_custom_arrow_step);
ClassDB::bind_method(D_METHOD("get_custom_arrow_step"), &SpinBox::get_custom_arrow_step);
ClassDB::bind_method(D_METHOD("apply"), &SpinBox::apply);
ClassDB::bind_method(D_METHOD("_line_edit_focus_enter"), &SpinBox::_line_edit_focus_enter);
ClassDB::bind_method(D_METHOD("_line_edit_focus_exit"), &SpinBox::_line_edit_focus_exit);
@ -275,6 +288,7 @@ void SpinBox::_bind_methods() {
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "editable"), "set_editable", "is_editable");
ADD_PROPERTY(PropertyInfo(Variant::STRING, "prefix"), "set_prefix", "get_prefix");
ADD_PROPERTY(PropertyInfo(Variant::STRING, "suffix"), "set_suffix", "get_suffix");
ADD_PROPERTY(PropertyInfo(Variant::REAL, "custom_arrow_step", PROPERTY_HINT_RANGE, "0,10000,0.0001,or_greater"), "set_custom_arrow_step", "get_custom_arrow_step");
}
SpinBox::SpinBox() {

View file

@ -49,6 +49,7 @@ class SpinBox : public Range {
virtual void _value_changed(double);
String prefix;
String suffix;
double custom_arrow_step = 0.0;
void _line_edit_input(const Ref<InputEvent> &p_event);
@ -90,6 +91,8 @@ public:
String get_prefix() const;
void apply();
void set_custom_arrow_step(const double p_custom_arrow_step);
double get_custom_arrow_step() const;
SpinBox();
};