diff --git a/.github/workflows/build-publish.yml b/.github/workflows/build-publish.yml index 0628b0745c1..156f08d84c8 100644 --- a/.github/workflows/build-publish.yml +++ b/.github/workflows/build-publish.yml @@ -17,10 +17,12 @@ jobs: with: fetch-depth: 0 submodules: 'recursive' - - name: Set up JDK 1.8 - uses: actions/setup-java@v1 + + - name: Set up JDK 11 + uses: actions/setup-java@v2 with: - java-version: 1.8 + distribution: 'zulu' + java-version: '11' - name: Cache Gradle packages uses: actions/cache@v2 @@ -66,10 +68,12 @@ jobs: with: fetch-depth: 0 submodules: 'recursive' - - name: Set up JDK 1.8 - uses: actions/setup-java@v1 + + - name: Set up JDK 11 + uses: actions/setup-java@v2 with: - java-version: 1.8 + distribution: 'zulu' + java-version: '11' - name: Cache Gradle packages uses: actions/cache@v2 @@ -115,10 +119,11 @@ jobs: fetch-depth: 0 submodules: 'recursive' - - name: Set up JDK 1.8 - uses: actions/setup-java@v1 + - name: Set up JDK 11 + uses: actions/setup-java@v2 with: - java-version: 1.8 + distribution: 'zulu' + java-version: '11' - name: Install cross-compilation toolchains run: | @@ -164,10 +169,11 @@ jobs: fetch-depth: 0 submodules: 'recursive' - - name: Set up JDK 1.8 - uses: actions/setup-java@v1 + - name: Set up JDK 11 + uses: actions/setup-java@v2 with: - java-version: 1.8 + distribution: 'zulu' + java-version: '11' - name: Install cross-compilation toolchains run: | @@ -211,10 +217,11 @@ jobs: fetch-depth: 0 submodules: 'recursive' - - name: Set up JDK 1.8 - uses: actions/setup-java@v1 + - name: Set up JDK 11 + uses: actions/setup-java@v2 with: - java-version: 1.8 + distribution: 'zulu' + java-version: '11' - name: Cache Gradle packages uses: actions/cache@v2 @@ -259,10 +266,11 @@ jobs: fetch-depth: 0 submodules: 'recursive' - - name: Set up JDK 1.8 - uses: actions/setup-java@v1 + - name: Set up JDK 11 + uses: actions/setup-java@v2 with: - java-version: 1.8 + distribution: 'zulu' + java-version: '11' - name: Cache Gradle packages uses: actions/cache@v2 @@ -348,10 +356,11 @@ jobs: fetch-depth: 0 submodules: 'recursive' - - name: Set up JDK 1.8 - uses: actions/setup-java@v1 + - name: Set up JDK 11 + uses: actions/setup-java@v2 with: - java-version: 1.8 + distribution: 'zulu' + java-version: '11' - name: Download natives artifact uses: actions/download-artifact@v2 @@ -378,11 +387,11 @@ jobs: with: gpg-private-key: ${{ secrets.GPG_PRIVATE_KEY }} passphrase: ${{ secrets.GPG_PASSPHRASE }} + - name: Release build deploy if: github.event_name == 'release' && github.repository_owner == 'libgdx' run: ./gradlew build publish -PRELEASE -Psigning.gnupg.keyId=${{ secrets.GPG_KEYID }} -Psigning.gnupg.passphrase=${{ secrets.GPG_PASSPHRASE }} -Psigning.gnupg.keyName=${{ secrets.GPG_KEYID }} - build-and-upload-runnables: runs-on: ubuntu-18.04 env: @@ -394,10 +403,12 @@ jobs: fetch-depth: 0 submodules: 'recursive' - - name: Set up JDK 1.8 - uses: actions/setup-java@v1 + - name: Set up JDK 11 + uses: actions/setup-java@v2 with: - java-version: 1.8 + distribution: 'zulu' + java-version: '11' + - name: Build Runnables run: ./gradlew clean fetchNatives buildRunnables build @@ -407,4 +418,3 @@ jobs: aws s3 cp ./extensions/gdx-tools/build/libs/ s3://libgdx-nightlies/libgdx-runnables/ --recursive aws s3 cp ./extensions/gdx-setup/build/libs/ s3://libgdx-nightlies/libgdx-runnables/ --recursive - diff --git a/.github/workflows/build-pullrequest.yml b/.github/workflows/build-pullrequest.yml index d0d6f0eb39b..362cf1fb3b4 100644 --- a/.github/workflows/build-pullrequest.yml +++ b/.github/workflows/build-pullrequest.yml @@ -16,10 +16,11 @@ jobs: fetch-depth: 0 submodules: 'recursive' - - name: Set up JDK 1.8 - uses: actions/setup-java@v1 + - name: Set up JDK 11 + uses: actions/setup-java@v2 with: - java-version: 1.8 + distribution: 'zulu' + java-version: '11' - name: Cache Gradle packages uses: actions/cache@v2 diff --git a/.github/workflows/fix-formatting.yml b/.github/workflows/fix-formatting.yml index 9fa5b450832..b7eee833e0c 100644 --- a/.github/workflows/fix-formatting.yml +++ b/.github/workflows/fix-formatting.yml @@ -9,10 +9,11 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 - - name: Set up JDK 1.8 - uses: actions/setup-java@v1 + - name: Set up JDK 11 + uses: actions/setup-java@v2 with: - java-version: 1.8 + distribution: 'zulu' + java-version: '11' - name: Cache Gradle packages uses: actions/cache@v2 diff --git a/.gitignore b/.gitignore index 016a0f151ad..131dac10ca6 100644 --- a/.gitignore +++ b/.gitignore @@ -19,7 +19,6 @@ gwt-unitCache/ war/ gen/ gen-external-apklibs/ -armeabi/ armeabi-v7a/ arm64-v8a/ linux32/ @@ -33,7 +32,6 @@ Release/ x64/ x86/ x86_64/ -robovm-build/ ipch/ /dist diff --git a/CHANGES b/CHANGES index cf07d57168a..c4373e790ec 100644 --- a/CHANGES +++ b/CHANGES @@ -1,8 +1,15 @@ [1.11.1] - [BREAKING CHANGE] Added #touchCancelled to InputProcessor interface, see #6871. +- [BREAKING CHANGE] Android: Immersive mode is now true by default. Set `useImmersiveMode` config to `false` to disable it. +- [BREAKING CHANGE] iOS: `hideHomeIndicator` set to `false` by default (was `true`). +- [BREAKING CHANGE] iOS: `screenEdgesDeferringSystemGestures` set to `UIRectEdge.All` by default (was `UIRectEdge.None`). - iOS: Add new MobiVM MetalANGLE backend +- API Addition: Added Haptics API with 4 different Input#vibrate() methods with complete Android and iOS implementations. - Fix: Fixed Android and iOS touch cancelled related issues, see #6871. - Javadoc: Add "-use" flag to javadoc generation +- Android: gdx-setup now uses AGP 7.0.4 and SDK 31, requiring Android Studio Arctic Fox or IntelliJ IDEA 2021.3 and JDK 11. +- libGDX is now built using Java 11 due to new Android requirements. The rest of libGDX can still be built with JDK 8 and runtime compatibility of libGDX projects should be unaffected. +- Fixed glViewport when using HdpiMode.Logical with the LWJGL3 backend. [1.11.0] - [BREAKING CHANGE] iOS: Increased min supported iOS version to 9.0. Update your Info.plist file if necessary. diff --git a/backends/gdx-backend-android/src/com/badlogic/gdx/backends/android/AndroidApplicationConfiguration.java b/backends/gdx-backend-android/src/com/badlogic/gdx/backends/android/AndroidApplicationConfiguration.java index d77be36f527..3907e693876 100644 --- a/backends/gdx-backend-android/src/com/badlogic/gdx/backends/android/AndroidApplicationConfiguration.java +++ b/backends/gdx-backend-android/src/com/badlogic/gdx/backends/android/AndroidApplicationConfiguration.java @@ -88,7 +88,7 @@ public class AndroidApplicationConfiguration { public boolean getTouchEventsForLiveWallpaper = false; /** set this to true to enable Android 4.4 KitKat's 'Immersive mode' **/ - public boolean useImmersiveMode = false; + public boolean useImmersiveMode = true; /** Experimental, whether to enable OpenGL ES 3 if supported. If not supported it will fall-back to OpenGL ES 2.0. When GL ES * 3* is enabled, {@link com.badlogic.gdx.Gdx#gl30} can be used to access its functionality. Requires at least Android 4.3 (API diff --git a/backends/gdx-backend-android/src/com/badlogic/gdx/backends/android/AndroidHaptics.java b/backends/gdx-backend-android/src/com/badlogic/gdx/backends/android/AndroidHaptics.java new file mode 100644 index 00000000000..af162a86e53 --- /dev/null +++ b/backends/gdx-backend-android/src/com/badlogic/gdx/backends/android/AndroidHaptics.java @@ -0,0 +1,112 @@ +/******************************************************************************* + * Copyright 2011 See AUTHORS file. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ******************************************************************************/ + +package com.badlogic.gdx.backends.android; + +import android.content.Context; +import android.media.AudioAttributes; +import android.os.Build; +import android.os.VibrationEffect; +import android.os.Vibrator; +import com.badlogic.gdx.Input; +import com.badlogic.gdx.math.MathUtils; + +public class AndroidHaptics { + + private final Vibrator vibrator; + private AudioAttributes audioAttributes; + private boolean vibratorSupport; + private boolean amplitudeSupport; + + public AndroidHaptics (Context context) { + vibratorSupport = false; + amplitudeSupport = false; + this.vibrator = (Vibrator)context.getSystemService(Context.VIBRATOR_SERVICE); + if (vibrator != null && vibrator.hasVibrator()) { + vibratorSupport = true; + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + if (vibrator.hasAmplitudeControl()) { + amplitudeSupport = true; + } + this.audioAttributes = new AudioAttributes.Builder().setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION) + .setUsage(AudioAttributes.USAGE_GAME).build(); + } + } + } + + public void vibrate (int milliseconds) { + if (vibratorSupport) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) + vibrator.vibrate(VibrationEffect.createOneShot(milliseconds, VibrationEffect.DEFAULT_AMPLITUDE)); + else + vibrator.vibrate(milliseconds); + } + } + + public void vibrate (Input.VibrationType vibrationType) { + if (amplitudeSupport) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { + int vibrationEffect; + switch (vibrationType) { + case LIGHT: + vibrationEffect = VibrationEffect.EFFECT_TICK; + break; + case MEDIUM: + vibrationEffect = VibrationEffect.EFFECT_CLICK; + break; + case HEAVY: + vibrationEffect = VibrationEffect.EFFECT_HEAVY_CLICK; + break; + default: + throw new IllegalArgumentException("Unknown VibrationType " + vibrationType); + } + vibrator.vibrate(VibrationEffect.createPredefined(vibrationEffect), audioAttributes); + } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + int amplitude; + switch (vibrationType) { + case LIGHT: + amplitude = 50; + break; + case MEDIUM: + amplitude = VibrationEffect.DEFAULT_AMPLITUDE; + break; + case HEAVY: + amplitude = 250; + break; + default: + throw new IllegalArgumentException("Unknown VibrationType " + vibrationType); + } + vibrator.vibrate(VibrationEffect.createOneShot(25, amplitude)); + } + } + } + + public void vibrate (int milliseconds, int intensity, boolean fallback) { + if (amplitudeSupport) { + intensity = MathUtils.clamp(intensity, 0, 255); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) + vibrator.vibrate(VibrationEffect.createOneShot(milliseconds, intensity)); + } else if (fallback) vibrate(milliseconds); + } + + public boolean hasVibratorAvailable () { + return vibratorSupport; + } + + public boolean hasAmplitudeSupport () { + return amplitudeSupport; + } +} diff --git a/backends/gdx-backend-android/src/com/badlogic/gdx/backends/android/AndroidSound.java b/backends/gdx-backend-android/src/com/badlogic/gdx/backends/android/AndroidSound.java index 5144b940679..2d43a2fb7b1 100644 --- a/backends/gdx-backend-android/src/com/badlogic/gdx/backends/android/AndroidSound.java +++ b/backends/gdx-backend-android/src/com/badlogic/gdx/backends/android/AndroidSound.java @@ -102,7 +102,7 @@ public long loop () { @Override public long loop (float volume) { if (streamIds.size == 8) streamIds.pop(); - int streamId = soundPool.play(soundId, volume, volume, 1, -1, 1); + int streamId = soundPool.play(soundId, volume, volume, 2, -1, 1); // standardise error code with other backends if (streamId == 0) return -1; streamIds.insert(0, streamId); @@ -112,9 +112,12 @@ public long loop (float volume) { @Override public void setLooping (long soundId, boolean looping) { int streamId = (int)soundId; - soundPool.pause(streamId); soundPool.setLoop(streamId, looping ? -1 : 0); + if (looping) + soundPool.setPriority(streamId, 2); + else + soundPool.setPriority(streamId, 1); soundPool.resume(streamId); } @@ -159,7 +162,7 @@ public long loop (float volume, float pitch, float pan) { } else if (pan > 0) { leftVolume *= (1 - Math.abs(pan)); } - int streamId = soundPool.play(soundId, leftVolume, rightVolume, 1, -1, pitch); + int streamId = soundPool.play(soundId, leftVolume, rightVolume, 2, -1, pitch); // standardise error code with other backends if (streamId == 0) return -1; streamIds.insert(0, streamId); diff --git a/backends/gdx-backend-android/src/com/badlogic/gdx/backends/android/DefaultAndroidInput.java b/backends/gdx-backend-android/src/com/badlogic/gdx/backends/android/DefaultAndroidInput.java index b01d80a7f6c..14f8a78154d 100644 --- a/backends/gdx-backend-android/src/com/badlogic/gdx/backends/android/DefaultAndroidInput.java +++ b/backends/gdx-backend-android/src/com/badlogic/gdx/backends/android/DefaultAndroidInput.java @@ -25,10 +25,7 @@ import android.hardware.SensorEvent; import android.hardware.SensorEventListener; import android.hardware.SensorManager; -import android.os.Build; import android.os.Handler; -import android.os.VibrationEffect; -import android.os.Vibrator; import android.text.InputType; import android.text.method.PasswordTransformationMethod; import android.view.MotionEvent; @@ -124,7 +121,7 @@ protected TouchEvent newObject () { final Context context; protected final AndroidTouchHandler touchHandler; private int sleepTime = 0; - protected final Vibrator vibrator; + protected final AndroidHaptics haptics; private boolean compassAvailable = false; private boolean rotationVectorAvailable = false; boolean keyboardAvailable; @@ -171,7 +168,7 @@ public DefaultAndroidInput (Application activity, Context context, Object view, touchHandler = new AndroidTouchHandler(); hasMultitouch = touchHandler.supportsMultitouch(context); - vibrator = (Vibrator)context.getSystemService(Context.VIBRATOR_SERVICE); + haptics = new AndroidHaptics(context); int rotation = getRotation(); DisplayMode mode = app.getGraphics().getDisplayMode(); @@ -646,23 +643,22 @@ public void run () { @Override public void vibrate (int milliseconds) { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) - vibrator.vibrate(VibrationEffect.createOneShot(milliseconds, VibrationEffect.DEFAULT_AMPLITUDE)); - else - vibrator.vibrate(milliseconds); + haptics.vibrate(milliseconds); } @Override - public void vibrate (long[] pattern, int repeat) { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) - vibrator.vibrate(VibrationEffect.createWaveform(pattern, repeat)); - else - vibrator.vibrate(pattern, repeat); + public void vibrate (int milliseconds, boolean fallback) { + haptics.vibrate(milliseconds); } @Override - public void cancelVibrate () { - vibrator.cancel(); + public void vibrate (int milliseconds, int amplitude, boolean fallback) { + haptics.vibrate(milliseconds, amplitude, fallback); + } + + @Override + public void vibrate (VibrationType vibrationType) { + haptics.vibrate(vibrationType); } @Override @@ -835,7 +831,8 @@ public boolean isPeripheralAvailable (Peripheral peripheral) { if (peripheral == Peripheral.Compass) return compassAvailable; if (peripheral == Peripheral.HardwareKeyboard) return keyboardAvailable; if (peripheral == Peripheral.OnscreenKeyboard) return true; - if (peripheral == Peripheral.Vibrator) return vibrator != null && vibrator.hasVibrator(); + if (peripheral == Peripheral.Vibrator) return haptics.hasVibratorAvailable(); + if (peripheral == Peripheral.HapticFeedback) return haptics.hasAmplitudeSupport(); if (peripheral == Peripheral.MultitouchScreen) return hasMultitouch; if (peripheral == Peripheral.RotationVector) return rotationVectorAvailable; if (peripheral == Peripheral.Pressure) return true; diff --git a/backends/gdx-backend-headless/src/com/badlogic/gdx/backends/headless/mock/input/MockInput.java b/backends/gdx-backend-headless/src/com/badlogic/gdx/backends/headless/mock/input/MockInput.java index 0653bfb9d00..f913da3981e 100644 --- a/backends/gdx-backend-headless/src/com/badlogic/gdx/backends/headless/mock/input/MockInput.java +++ b/backends/gdx-backend-headless/src/com/badlogic/gdx/backends/headless/mock/input/MockInput.java @@ -170,13 +170,15 @@ public void vibrate (int milliseconds) { } @Override - public void vibrate (long[] pattern, int repeat) { - + public void vibrate (int milliseconds, boolean fallback) { } @Override - public void cancelVibrate () { + public void vibrate (int milliseconds, int amplitude, boolean fallback) { + } + @Override + public void vibrate (VibrationType vibrationType) { } @Override diff --git a/backends/gdx-backend-lwjgl/src/com/badlogic/gdx/backends/lwjgl/DefaultLwjglInput.java b/backends/gdx-backend-lwjgl/src/com/badlogic/gdx/backends/lwjgl/DefaultLwjglInput.java index cde3b3e58db..b39fc2360a8 100644 --- a/backends/gdx-backend-lwjgl/src/com/badlogic/gdx/backends/lwjgl/DefaultLwjglInput.java +++ b/backends/gdx-backend-lwjgl/src/com/badlogic/gdx/backends/lwjgl/DefaultLwjglInput.java @@ -626,6 +626,18 @@ public InputProcessor getInputProcessor () { public void vibrate (int milliseconds) { } + @Override + public void vibrate (int milliseconds, boolean fallback) { + } + + @Override + public void vibrate (int milliseconds, int amplitude, boolean fallback) { + } + + @Override + public void vibrate (VibrationType vibrationType) { + } + @Override public boolean justTouched () { return justTouched; @@ -658,14 +670,6 @@ public boolean isButtonJustPressed (int button) { return justPressedButtons[button]; } - @Override - public void vibrate (long[] pattern, int repeat) { - } - - @Override - public void cancelVibrate () { - } - @Override public float getAzimuth () { return 0; diff --git a/backends/gdx-backend-lwjgl/src/com/badlogic/gdx/backends/lwjgl/LwjglAWTInput.java b/backends/gdx-backend-lwjgl/src/com/badlogic/gdx/backends/lwjgl/LwjglAWTInput.java index d6286788677..301b1408b08 100644 --- a/backends/gdx-backend-lwjgl/src/com/badlogic/gdx/backends/lwjgl/LwjglAWTInput.java +++ b/backends/gdx-backend-lwjgl/src/com/badlogic/gdx/backends/lwjgl/LwjglAWTInput.java @@ -847,6 +847,18 @@ public InputProcessor getInputProcessor () { public void vibrate (int milliseconds) { } + @Override + public void vibrate (int milliseconds, boolean fallback) { + } + + @Override + public void vibrate (int milliseconds, int amplitude, boolean fallback) { + } + + @Override + public void vibrate (VibrationType vibrationType) { + } + @Override public boolean justTouched () { return justTouched; @@ -863,14 +875,6 @@ public boolean isButtonJustPressed (int button) { return justPressedButtons[button]; } - @Override - public void vibrate (long[] pattern, int repeat) { - } - - @Override - public void cancelVibrate () { - } - @Override public float getAzimuth () { return 0; diff --git a/backends/gdx-backend-lwjgl3/src/com/badlogic/gdx/backends/lwjgl3/DefaultLwjgl3Input.java b/backends/gdx-backend-lwjgl3/src/com/badlogic/gdx/backends/lwjgl3/DefaultLwjgl3Input.java index 9452e0eb8fa..07598790233 100644 --- a/backends/gdx-backend-lwjgl3/src/com/badlogic/gdx/backends/lwjgl3/DefaultLwjgl3Input.java +++ b/backends/gdx-backend-lwjgl3/src/com/badlogic/gdx/backends/lwjgl3/DefaultLwjgl3Input.java @@ -662,11 +662,15 @@ public void vibrate (int milliseconds) { } @Override - public void vibrate (long[] pattern, int repeat) { + public void vibrate (int milliseconds, boolean fallback) { } @Override - public void cancelVibrate () { + public void vibrate (int milliseconds, int amplitude, boolean fallback) { + } + + @Override + public void vibrate (VibrationType vibrationType) { } @Override diff --git a/backends/gdx-backend-lwjgl3/src/com/badlogic/gdx/backends/lwjgl3/Lwjgl3Graphics.java b/backends/gdx-backend-lwjgl3/src/com/badlogic/gdx/backends/lwjgl3/Lwjgl3Graphics.java index 8dd704ea3e7..0a3b9f9bced 100644 --- a/backends/gdx-backend-lwjgl3/src/com/badlogic/gdx/backends/lwjgl3/Lwjgl3Graphics.java +++ b/backends/gdx-backend-lwjgl3/src/com/badlogic/gdx/backends/lwjgl3/Lwjgl3Graphics.java @@ -84,7 +84,7 @@ public void run () { return; } window.makeCurrent(); - gl20.glViewport(0, 0, getWidth(), getHeight()); + gl20.glViewport(0, 0, backBufferWidth, backBufferHeight); window.getListener().resize(getWidth(), getHeight()); window.getListener().render(); GLFW.glfwSwapBuffers(windowHandle); diff --git a/backends/gdx-backend-robovm-metalangle/build.gradle b/backends/gdx-backend-robovm-metalangle/build.gradle index a89c968544a..81fb4d02b7a 100644 --- a/backends/gdx-backend-robovm-metalangle/build.gradle +++ b/backends/gdx-backend-robovm-metalangle/build.gradle @@ -133,4 +133,7 @@ task jnigen() { if(OperatingSystem.current() == OperatingSystem.MAC_OS) { jnigenBuild.dependsOn jnigenBuildIOS -} \ No newline at end of file +} + +processResources.duplicatesStrategy = DuplicatesStrategy.EXCLUDE +sourcesJar.duplicatesStrategy = DuplicatesStrategy.EXCLUDE diff --git a/backends/gdx-backend-robovm-metalangle/src/com/badlogic/gdx/backends/iosrobovm/DefaultIOSInput.java b/backends/gdx-backend-robovm-metalangle/src/com/badlogic/gdx/backends/iosrobovm/DefaultIOSInput.java index 5d28cbdfdac..589d2a34231 100644 --- a/backends/gdx-backend-robovm-metalangle/src/com/badlogic/gdx/backends/iosrobovm/DefaultIOSInput.java +++ b/backends/gdx-backend-robovm-metalangle/src/com/badlogic/gdx/backends/iosrobovm/DefaultIOSInput.java @@ -13,7 +13,6 @@ import com.badlogic.gdx.utils.Array; import com.badlogic.gdx.utils.GdxRuntimeException; import com.badlogic.gdx.utils.Pool; -import org.robovm.apple.audiotoolbox.AudioServices; import org.robovm.apple.coregraphics.CGPoint; import org.robovm.apple.coregraphics.CGRect; import org.robovm.apple.foundation.Foundation; @@ -25,7 +24,6 @@ import org.robovm.apple.uikit.UIAlertActionStyle; import org.robovm.apple.uikit.UIAlertController; import org.robovm.apple.uikit.UIAlertControllerStyle; -import org.robovm.apple.uikit.UIDevice; import org.robovm.apple.uikit.UIForceTouchCapability; import org.robovm.apple.uikit.UIKey; import org.robovm.apple.uikit.UIKeyboardHIDUsage; @@ -136,7 +134,7 @@ protected KeyEvent newObject () { InputProcessor inputProcessor = null; - boolean hasVibrator; + private IOSHaptics haptics; // CMMotionManager motionManager; protected UIAccelerometerDelegate accelerometerDelegate; @@ -160,12 +158,8 @@ public void setupPeripherals () { // motionManager = new CMMotionManager(); setupAccelerometer(); setupCompass(); - UIDevice device = UIDevice.getCurrentDevice(); - if (device.getModel().equalsIgnoreCase("iphone")) hasVibrator = true; - if (app.getVersion() >= 9) { - UIForceTouchCapability forceTouchCapability = UIScreen.getMainScreen().getTraitCollection().getForceTouchCapability(); - pressureSupported = forceTouchCapability == UIForceTouchCapability.Available; - } + setupHaptics(); + setupPressure(); } protected void setupCompass () { @@ -194,6 +188,17 @@ public void didAccelerate (UIAccelerometer accelerometer, @Pointer long valuesPt } } + protected void setupHaptics () { + haptics = new IOSHaptics(config.useHaptics); + } + + protected void setupPressure () { + if (app.getVersion() >= 9) { + UIForceTouchCapability forceTouchCapability = UIScreen.getMainScreen().getTraitCollection().getForceTouchCapability(); + pressureSupported = forceTouchCapability == UIForceTouchCapability.Available; + } + } + // need to retain a reference so GC doesn't get right of the // object passed to the native thread // VoidBlock2 accelVoid = null; @@ -455,9 +460,9 @@ public void setOnscreenKeyboardVisible (boolean visible, OnscreenKeyboardType ty if (textfield == null) createDefaultTextField(); softkeyboardActive = visible; if (visible) { - UIKeyboardType preferredInputType; if (type == null) type = OnscreenKeyboardType.Default; textfield.setKeyboardType(getIosInputType(type)); + textfield.reloadInputViews(); textfield.becomeFirstResponder(); textfield.setDelegate(textDelegate); } else { @@ -558,17 +563,22 @@ public void invoke (UIAlertAction uiAlertAction) { @Override public void vibrate (int milliseconds) { - AudioServices.playSystemSound(4095); + haptics.vibrate(milliseconds, true); + } + + @Override + public void vibrate (int milliseconds, boolean fallback) { + haptics.vibrate(milliseconds, fallback); } @Override - public void vibrate (long[] pattern, int repeat) { - // FIXME implement this + public void vibrate (int milliseconds, int amplitude, boolean fallback) { + haptics.vibrate(milliseconds, amplitude, fallback); } @Override - public void cancelVibrate () { - // FIXME implement this + public void vibrate (VibrationType vibrationType) { + haptics.vibrate(vibrationType); } @Override @@ -590,7 +600,8 @@ public InputProcessor getInputProcessor () { public boolean isPeripheralAvailable (Peripheral peripheral) { if (peripheral == Peripheral.Accelerometer && config.useAccelerometer) return true; if (peripheral == Peripheral.MultitouchScreen) return true; - if (peripheral == Peripheral.Vibrator) return hasVibrator; + if (peripheral == Peripheral.Vibrator) return haptics.isVibratorSupported(); + if (peripheral == Peripheral.HapticFeedback) return haptics.isHapticsSupported(); if (peripheral == Peripheral.Compass) return compassSupported; if (peripheral == Peripheral.OnscreenKeyboard) return true; if (peripheral == Peripheral.Pressure) return pressureSupported; diff --git a/backends/gdx-backend-robovm-metalangle/src/com/badlogic/gdx/backends/iosrobovm/IOSApplicationConfiguration.java b/backends/gdx-backend-robovm-metalangle/src/com/badlogic/gdx/backends/iosrobovm/IOSApplicationConfiguration.java index 24cfa0dc25c..d74d72a9ee6 100644 --- a/backends/gdx-backend-robovm-metalangle/src/com/badlogic/gdx/backends/iosrobovm/IOSApplicationConfiguration.java +++ b/backends/gdx-backend-robovm-metalangle/src/com/badlogic/gdx/backends/iosrobovm/IOSApplicationConfiguration.java @@ -51,6 +51,9 @@ public class IOSApplicationConfiguration { /** whether to use the compass, default true * */ public boolean useCompass = true; + /** whether to use the haptics engine, default false. * */ + public boolean useHaptics = false; + /** whether or not to allow background music from iPod * */ public boolean allowIpod = true; @@ -65,15 +68,16 @@ public class IOSApplicationConfiguration { /** whether the status bar should be visible or not * */ public boolean statusBarVisible = false; - /** whether the home indicator should be hidden or not * */ - public boolean hideHomeIndicator = true; + /** whether the home indicator should auto-hide or not. Be careful that if enabled, leaving the app only takes one swipe + * gesture instead of two and the indicator is never semitransparent. * */ + public boolean hideHomeIndicator = false; /** Whether to override the ringer/mute switch, see https://github.com/libgdx/libgdx/issues/4430 */ public boolean overrideRingerSwitch = false; /** Edges where app gestures must be fired over system gestures. Prior to iOS 11, UIRectEdge.All was default behaviour if * status bar hidden, see https://github.com/libgdx/libgdx/issues/5110 * */ - public UIRectEdge screenEdgesDeferringSystemGestures = UIRectEdge.None; + public UIRectEdge screenEdgesDeferringSystemGestures = UIRectEdge.All; /** The maximum number of threads to use for network requests. Default is {@link Integer#MAX_VALUE}. */ public int maxNetThreads = Integer.MAX_VALUE; diff --git a/backends/gdx-backend-robovm-metalangle/src/com/badlogic/gdx/backends/iosrobovm/IOSHaptics.java b/backends/gdx-backend-robovm-metalangle/src/com/badlogic/gdx/backends/iosrobovm/IOSHaptics.java new file mode 100644 index 00000000000..3208df2d2f7 --- /dev/null +++ b/backends/gdx-backend-robovm-metalangle/src/com/badlogic/gdx/backends/iosrobovm/IOSHaptics.java @@ -0,0 +1,144 @@ +/*DO NOT EDIT THIS FILE - it is machine generated*/ + +package com.badlogic.gdx.backends.iosrobovm; + +import com.badlogic.gdx.Gdx; +import com.badlogic.gdx.Input; +import com.badlogic.gdx.math.MathUtils; +import org.robovm.apple.audiotoolbox.AudioServices; +import org.robovm.apple.corehaptic.CHHapticEngine; +import org.robovm.apple.corehaptic.CHHapticEventParameterID; +import org.robovm.apple.corehaptic.CHHapticEventType; +import org.robovm.apple.corehaptic.CHHapticPattern; +import org.robovm.apple.corehaptic.CHHapticPatternDict; +import org.robovm.apple.foundation.NSArray; +import org.robovm.apple.foundation.NSError; +import org.robovm.apple.foundation.NSErrorException; +import org.robovm.apple.foundation.NSObject; +import org.robovm.apple.foundation.NSProcessInfo; +import org.robovm.apple.uikit.UIDevice; +import org.robovm.apple.uikit.UIImpactFeedbackGenerator; +import org.robovm.apple.uikit.UIImpactFeedbackStyle; +import org.robovm.apple.uikit.UIUserInterfaceIdiom; +import org.robovm.objc.block.VoidBlock1; + +/** DO NOT EDIT THIS FILE - it is machine generated */ +public class IOSHaptics { + + private CHHapticEngine hapticEngine; + + private boolean hapticsSupport; + + private final boolean vibratorSupport; + + public IOSHaptics (boolean useHaptics) { + vibratorSupport = useHaptics && UIDevice.getCurrentDevice().getUserInterfaceIdiom() == UIUserInterfaceIdiom.Phone; + if (NSProcessInfo.getSharedProcessInfo().getOperatingSystemVersion().getMajorVersion() >= 13) { + hapticsSupport = useHaptics && CHHapticEngine.capabilitiesForHardware().supportsHaptics(); + if (hapticsSupport) { + try { + hapticEngine = new CHHapticEngine(); + } catch (NSErrorException e) { + Gdx.app.error("IOSHaptics", "Error creating CHHapticEngine. Haptics will be disabled. " + e); + hapticsSupport = false; + return; + } + hapticEngine.setPlaysHapticsOnly(true); + hapticEngine.setAutoShutdownEnabled(true); + // The reset handler provides an opportunity to restart the engine. + hapticEngine.setResetHandler(new Runnable() { + + @Override + public void run () { + // Try restarting the engine. + hapticEngine.start(new VoidBlock1() { + + @Override + public void invoke (NSError nsError) { + if (nsError != null) { + Gdx.app.error("IOSHaptics", "Error restarting CHHapticEngine. Haptics will be disabled."); + hapticsSupport = false; + } + } + }); + } + }); + } + } + } + + public void vibrate (int milliseconds, boolean fallback) { + if (hapticsSupport) { + CHHapticPatternDict hapticDict = getChHapticPatternDict(milliseconds, 0.5f); + try { + CHHapticPattern pattern = new CHHapticPattern(hapticDict); + NSError.NSErrorPtr ptr = new NSError.NSErrorPtr(); + hapticEngine.createPlayer(pattern).start(0, ptr); + if (ptr.get() != null) { + Gdx.app.error("IOSHaptics", "Error starting haptics player. Error code: " + ptr.get().getLocalizedDescription()); + } + } catch (NSErrorException e) { + Gdx.app.error("IOSHaptics", "Error creating haptics pattern or player. " + e.getMessage()); + } + } else if (fallback) { + AudioServices.playSystemSound(4095); + } + } + + public void vibrate (int milliseconds, int amplitude, boolean fallback) { + if (hapticsSupport) { + float intensity = MathUtils.clamp(amplitude / 255f, 0, 1); + CHHapticPatternDict hapticDict = getChHapticPatternDict(milliseconds, intensity); + try { + CHHapticPattern pattern = new CHHapticPattern(hapticDict); + NSError.NSErrorPtr ptr = new NSError.NSErrorPtr(); + hapticEngine.createPlayer(pattern).start(0, ptr); + if (ptr.get() != null) { + Gdx.app.error("IOSHaptics", "Error starting haptics pattern."); + } + } catch (NSErrorException e) { + Gdx.app.error("IOSHaptics", "Error creating haptics player. " + e.getMessage()); + } + } else { + vibrate(milliseconds, fallback); + } + } + + private CHHapticPatternDict getChHapticPatternDict (int milliseconds, float intensity) { + return new CHHapticPatternDict().setPattern(new NSArray(new CHHapticPatternDict() + .setEvent(new CHHapticPatternDict().setEventType(CHHapticEventType.HapticContinuous).setTime(0.0) + .setEventDuration(milliseconds / 1000f) + .setEventParameters(new NSArray(new CHHapticPatternDict() + .setParameterID(CHHapticEventParameterID.HapticIntensity).setParameterValue(intensity).getDictionary()))) + .getDictionary())); + } + + public void vibrate (Input.VibrationType vibrationType) { + if (hapticsSupport) { + UIImpactFeedbackStyle uiImpactFeedbackStyle; + switch (vibrationType) { + case LIGHT: + uiImpactFeedbackStyle = UIImpactFeedbackStyle.Soft; + break; + case MEDIUM: + uiImpactFeedbackStyle = UIImpactFeedbackStyle.Medium; + break; + case HEAVY: + uiImpactFeedbackStyle = UIImpactFeedbackStyle.Heavy; + break; + default: + throw new IllegalArgumentException("Unknown VibrationType " + vibrationType); + } + UIImpactFeedbackGenerator uiImpactFeedbackGenerator = new UIImpactFeedbackGenerator(uiImpactFeedbackStyle); + uiImpactFeedbackGenerator.impactOccurred(); + } + } + + public boolean isHapticsSupported () { + return hapticsSupport; + } + + public boolean isVibratorSupported () { + return vibratorSupport; + } +} diff --git a/backends/gdx-backend-robovm/build-objectal.sh b/backends/gdx-backend-robovm/build-objectal.sh index 47a6514fa83..a4d699fc0c7 100755 --- a/backends/gdx-backend-robovm/build-objectal.sh +++ b/backends/gdx-backend-robovm/build-objectal.sh @@ -12,10 +12,10 @@ tar xvfz $BUILD_DIR/objectal.tar.gz -C $BUILD_DIR --strip-components 1 XCODEPROJ=$BUILD_DIR/ObjectAL/ObjectAL.xcodeproj -xcodebuild -project $XCODEPROJ -arch armv7 -sdk iphoneos CONFIGURATION_BUILD_DIR=$BUILD_DIR/armv7 OTHER_CFLAGS="-fembed-bitcode -target armv7-apple-ios9.0.0" -xcodebuild -project $XCODEPROJ -arch arm64 -sdk iphoneos CONFIGURATION_BUILD_DIR=$BUILD_DIR/arm64 OTHER_CFLAGS="-fembed-bitcode -target arm64-apple-ios9.0.0" +xcodebuild -project $XCODEPROJ -arch armv7 -sdk iphoneos CONFIGURATION_BUILD_DIR=$BUILD_DIR/armv7 OTHER_CFLAGS="-target armv7-apple-ios9.0.0" +xcodebuild -project $XCODEPROJ -arch arm64 -sdk iphoneos CONFIGURATION_BUILD_DIR=$BUILD_DIR/arm64 OTHER_CFLAGS="-target arm64-apple-ios9.0.0" xcodebuild -project $XCODEPROJ -arch x86_64 -sdk iphonesimulator CONFIGURATION_BUILD_DIR=$BUILD_DIR/x86_64 OTHER_CFLAGS="-target x86_64-simulator-apple-ios9.0.0" -xcodebuild -project $XCODEPROJ -arch arm64 -sdk iphonesimulator CONFIGURATION_BUILD_DIR=$BUILD_DIR/arm64-simulator OTHER_CFLAGS="-fembed-bitcode -target arm64-simulator-apple-ios9.0.0" +xcodebuild -project $XCODEPROJ -arch arm64 -sdk iphonesimulator CONFIGURATION_BUILD_DIR=$BUILD_DIR/arm64-simulator OTHER_CFLAGS="-target arm64-simulator-apple-ios9.0.0" mkdir $BUILD_DIR/real/ diff --git a/backends/gdx-backend-robovm/build.gradle b/backends/gdx-backend-robovm/build.gradle index e50bda7d487..e0ca3545bc2 100644 --- a/backends/gdx-backend-robovm/build.gradle +++ b/backends/gdx-backend-robovm/build.gradle @@ -24,3 +24,6 @@ sourceSets { dependencies { api libraries.robovm } + +processResources.duplicatesStrategy = DuplicatesStrategy.EXCLUDE +sourcesJar.duplicatesStrategy = DuplicatesStrategy.EXCLUDE diff --git a/backends/gdx-backend-robovm/src/com/badlogic/gdx/backends/iosrobovm/DefaultIOSInput.java b/backends/gdx-backend-robovm/src/com/badlogic/gdx/backends/iosrobovm/DefaultIOSInput.java index e71590f0100..1a6a6571232 100644 --- a/backends/gdx-backend-robovm/src/com/badlogic/gdx/backends/iosrobovm/DefaultIOSInput.java +++ b/backends/gdx-backend-robovm/src/com/badlogic/gdx/backends/iosrobovm/DefaultIOSInput.java @@ -28,7 +28,6 @@ import com.badlogic.gdx.utils.GdxRuntimeException; import com.badlogic.gdx.utils.Pool; -import org.robovm.apple.audiotoolbox.AudioServices; import org.robovm.apple.coregraphics.CGPoint; import org.robovm.apple.coregraphics.CGRect; import org.robovm.apple.foundation.Foundation; @@ -40,7 +39,6 @@ import org.robovm.apple.uikit.UIAlertActionStyle; import org.robovm.apple.uikit.UIAlertController; import org.robovm.apple.uikit.UIAlertControllerStyle; -import org.robovm.apple.uikit.UIDevice; import org.robovm.apple.uikit.UIForceTouchCapability; import org.robovm.apple.uikit.UIKey; import org.robovm.apple.uikit.UIKeyboardHIDUsage; @@ -123,7 +121,7 @@ protected KeyEvent newObject () { float[] R = new float[9]; InputProcessor inputProcessor = null; - boolean hasVibrator; + private IOSHaptics haptics; // CMMotionManager motionManager; protected UIAccelerometerDelegate accelerometerDelegate; boolean compassSupported; @@ -143,13 +141,8 @@ public void setupPeripherals () { // motionManager = new CMMotionManager(); setupAccelerometer(); setupCompass(); - UIDevice device = UIDevice.getCurrentDevice(); - if (device.getModel().equalsIgnoreCase("iphone")) hasVibrator = true; - - if (app.getVersion() >= 9) { - UIForceTouchCapability forceTouchCapability = UIScreen.getMainScreen().getTraitCollection().getForceTouchCapability(); - pressureSupported = forceTouchCapability == UIForceTouchCapability.Available; - } + setupHaptics(); + setupPressure(); } protected void setupCompass () { @@ -179,6 +172,17 @@ public void didAccelerate (UIAccelerometer accelerometer, @Pointer long valuesPt } } + protected void setupHaptics () { + haptics = new IOSHaptics(config.useHaptics); + } + + protected void setupPressure () { + if (app.getVersion() >= 9) { + UIForceTouchCapability forceTouchCapability = UIScreen.getMainScreen().getTraitCollection().getForceTouchCapability(); + pressureSupported = forceTouchCapability == UIForceTouchCapability.Available; + } + } + // need to retain a reference so GC doesn't get right of the // object passed to the native thread // VoidBlock2 accelVoid = null; @@ -447,9 +451,9 @@ public void setOnscreenKeyboardVisible (boolean visible, OnscreenKeyboardType ty if (textfield == null) createDefaultTextField(); softkeyboardActive = visible; if (visible) { - UIKeyboardType preferredInputType; if (type == null) type = OnscreenKeyboardType.Default; textfield.setKeyboardType(getIosInputType(type)); + textfield.reloadInputViews(); textfield.becomeFirstResponder(); textfield.setDelegate(textDelegate); } else { @@ -547,17 +551,22 @@ public void invoke (UIAlertAction uiAlertAction) { @Override public void vibrate (int milliseconds) { - AudioServices.playSystemSound(4095); + haptics.vibrate(milliseconds, true); + } + + @Override + public void vibrate (int milliseconds, boolean fallback) { + haptics.vibrate(milliseconds, fallback); } @Override - public void vibrate (long[] pattern, int repeat) { - // FIXME implement this + public void vibrate (int milliseconds, int amplitude, boolean fallback) { + haptics.vibrate(milliseconds, amplitude, fallback); } @Override - public void cancelVibrate () { - // FIXME implement this + public void vibrate (VibrationType vibrationType) { + haptics.vibrate(vibrationType); } @Override @@ -579,7 +588,8 @@ public InputProcessor getInputProcessor () { public boolean isPeripheralAvailable (Peripheral peripheral) { if (peripheral == Peripheral.Accelerometer && config.useAccelerometer) return true; if (peripheral == Peripheral.MultitouchScreen) return true; - if (peripheral == Peripheral.Vibrator) return hasVibrator; + if (peripheral == Peripheral.Vibrator) return haptics.isVibratorSupported(); + if (peripheral == Peripheral.HapticFeedback) return haptics.isHapticsSupported(); if (peripheral == Peripheral.Compass) return compassSupported; if (peripheral == Peripheral.OnscreenKeyboard) return true; if (peripheral == Peripheral.Pressure) return pressureSupported; diff --git a/backends/gdx-backend-robovm/src/com/badlogic/gdx/backends/iosrobovm/IOSApplicationConfiguration.java b/backends/gdx-backend-robovm/src/com/badlogic/gdx/backends/iosrobovm/IOSApplicationConfiguration.java index df674afb1f3..4d4312e7bdb 100644 --- a/backends/gdx-backend-robovm/src/com/badlogic/gdx/backends/iosrobovm/IOSApplicationConfiguration.java +++ b/backends/gdx-backend-robovm/src/com/badlogic/gdx/backends/iosrobovm/IOSApplicationConfiguration.java @@ -1,12 +1,12 @@ /******************************************************************************* * Copyright 2011 See AUTHORS file. - * + * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -60,6 +60,9 @@ public class IOSApplicationConfiguration { /** whether to use the compass, default true **/ public boolean useCompass = true; + /** whether to use the haptics engine, default false. **/ + public boolean useHaptics = false; + /** whether or not to allow background music from iPod **/ public boolean allowIpod = true; @@ -74,15 +77,16 @@ public class IOSApplicationConfiguration { /** whether the status bar should be visible or not **/ public boolean statusBarVisible = false; - /** whether the home indicator should be hidden or not **/ - public boolean hideHomeIndicator = true; + /** whether the home indicator should auto-hide or not. Be careful that if enabled, leaving the app only takes one swipe + * gesture instead of two and the indicator is never semitransparent. **/ + public boolean hideHomeIndicator = false; /** Whether to override the ringer/mute switch, see https://github.com/libgdx/libgdx/issues/4430 */ public boolean overrideRingerSwitch = false; /** Edges where app gestures must be fired over system gestures. Prior to iOS 11, UIRectEdge.All was default behaviour if * status bar hidden, see https://github.com/libgdx/libgdx/issues/5110 **/ - public UIRectEdge screenEdgesDeferringSystemGestures = UIRectEdge.None; + public UIRectEdge screenEdgesDeferringSystemGestures = UIRectEdge.All; /** The maximum number of threads to use for network requests. Default is {@link Integer#MAX_VALUE}. */ public int maxNetThreads = Integer.MAX_VALUE; diff --git a/backends/gdx-backend-robovm/src/com/badlogic/gdx/backends/iosrobovm/IOSHaptics.java b/backends/gdx-backend-robovm/src/com/badlogic/gdx/backends/iosrobovm/IOSHaptics.java new file mode 100644 index 00000000000..99d1219f4d5 --- /dev/null +++ b/backends/gdx-backend-robovm/src/com/badlogic/gdx/backends/iosrobovm/IOSHaptics.java @@ -0,0 +1,154 @@ +/******************************************************************************* + * Copyright 2011 See AUTHORS file. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ******************************************************************************/ + +package com.badlogic.gdx.backends.iosrobovm; + +import com.badlogic.gdx.Gdx; +import com.badlogic.gdx.Input; +import com.badlogic.gdx.math.MathUtils; +import org.robovm.apple.audiotoolbox.AudioServices; +import org.robovm.apple.corehaptic.CHHapticEngine; +import org.robovm.apple.corehaptic.CHHapticEventParameterID; +import org.robovm.apple.corehaptic.CHHapticEventType; +import org.robovm.apple.corehaptic.CHHapticPattern; +import org.robovm.apple.corehaptic.CHHapticPatternDict; +import org.robovm.apple.foundation.NSArray; +import org.robovm.apple.foundation.NSError; +import org.robovm.apple.foundation.NSErrorException; +import org.robovm.apple.foundation.NSObject; +import org.robovm.apple.foundation.NSProcessInfo; +import org.robovm.apple.uikit.UIDevice; +import org.robovm.apple.uikit.UIImpactFeedbackGenerator; +import org.robovm.apple.uikit.UIImpactFeedbackStyle; +import org.robovm.apple.uikit.UIUserInterfaceIdiom; +import org.robovm.objc.block.VoidBlock1; + +public class IOSHaptics { + + private CHHapticEngine hapticEngine; + private boolean hapticsSupport; + private final boolean vibratorSupport; + + public IOSHaptics (boolean useHaptics) { + vibratorSupport = useHaptics && UIDevice.getCurrentDevice().getUserInterfaceIdiom() == UIUserInterfaceIdiom.Phone; + if (NSProcessInfo.getSharedProcessInfo().getOperatingSystemVersion().getMajorVersion() >= 13) { + hapticsSupport = useHaptics && CHHapticEngine.capabilitiesForHardware().supportsHaptics(); + if (hapticsSupport) { + try { + hapticEngine = new CHHapticEngine(); + } catch (NSErrorException e) { + Gdx.app.error("IOSHaptics", "Error creating CHHapticEngine. Haptics will be disabled. " + e); + hapticsSupport = false; + return; + } + hapticEngine.setPlaysHapticsOnly(true); + hapticEngine.setAutoShutdownEnabled(true); + // The reset handler provides an opportunity to restart the engine. + hapticEngine.setResetHandler(new Runnable() { + @Override + public void run () { + // Try restarting the engine. + hapticEngine.start(new VoidBlock1() { + @Override + public void invoke (NSError nsError) { + if (nsError != null) { + Gdx.app.error("IOSHaptics", "Error restarting CHHapticEngine. Haptics will be disabled."); + hapticsSupport = false; + } + } + }); + } + }); + } + } + } + + public void vibrate (int milliseconds, boolean fallback) { + if (hapticsSupport) { + CHHapticPatternDict hapticDict = getChHapticPatternDict(milliseconds, 0.5f); + try { + CHHapticPattern pattern = new CHHapticPattern(hapticDict); + NSError.NSErrorPtr ptr = new NSError.NSErrorPtr(); + hapticEngine.createPlayer(pattern).start(0, ptr); + if (ptr.get() != null) { + Gdx.app.error("IOSHaptics", "Error starting haptics player. Error code: " + ptr.get().getLocalizedDescription()); + } + } catch (NSErrorException e) { + Gdx.app.error("IOSHaptics", "Error creating haptics pattern or player. " + e.getMessage()); + } + } else if (fallback) { + AudioServices.playSystemSound(4095); + } + } + + public void vibrate (int milliseconds, int amplitude, boolean fallback) { + if (hapticsSupport) { + float intensity = MathUtils.clamp(amplitude / 255f, 0, 1); + CHHapticPatternDict hapticDict = getChHapticPatternDict(milliseconds, intensity); + try { + CHHapticPattern pattern = new CHHapticPattern(hapticDict); + NSError.NSErrorPtr ptr = new NSError.NSErrorPtr(); + hapticEngine.createPlayer(pattern).start(0, ptr); + if (ptr.get() != null) { + Gdx.app.error("IOSHaptics", "Error starting haptics pattern."); + } + } catch (NSErrorException e) { + Gdx.app.error("IOSHaptics", "Error creating haptics player. " + e.getMessage()); + } + } else { + vibrate(milliseconds, fallback); + } + } + + private CHHapticPatternDict getChHapticPatternDict (int milliseconds, float intensity) { + return new CHHapticPatternDict().setPattern(new NSArray(new CHHapticPatternDict() + .setEvent(new CHHapticPatternDict().setEventType(CHHapticEventType.HapticContinuous).setTime(0.0) + .setEventDuration(milliseconds / 1000f) + .setEventParameters(new NSArray(new CHHapticPatternDict() + .setParameterID(CHHapticEventParameterID.HapticIntensity).setParameterValue(intensity).getDictionary()))) + .getDictionary())); + } + + public void vibrate (Input.VibrationType vibrationType) { + if (hapticsSupport) { + UIImpactFeedbackStyle uiImpactFeedbackStyle; + switch (vibrationType) { + case LIGHT: + uiImpactFeedbackStyle = UIImpactFeedbackStyle.Soft; + break; + case MEDIUM: + uiImpactFeedbackStyle = UIImpactFeedbackStyle.Medium; + break; + case HEAVY: + uiImpactFeedbackStyle = UIImpactFeedbackStyle.Heavy; + break; + default: + throw new IllegalArgumentException("Unknown VibrationType " + vibrationType); + } + UIImpactFeedbackGenerator uiImpactFeedbackGenerator = new UIImpactFeedbackGenerator(uiImpactFeedbackStyle); + uiImpactFeedbackGenerator.impactOccurred(); + } + } + + public boolean isHapticsSupported () { + return hapticsSupport; + } + + public boolean isVibratorSupported () { + return vibratorSupport; + } + +} diff --git a/backends/gdx-backends-gwt/src/com/badlogic/gdx/backends/gwt/DefaultGwtInput.java b/backends/gdx-backends-gwt/src/com/badlogic/gdx/backends/gwt/DefaultGwtInput.java index aa8d559be0e..2146692ca92 100644 --- a/backends/gdx-backends-gwt/src/com/badlogic/gdx/backends/gwt/DefaultGwtInput.java +++ b/backends/gdx-backends-gwt/src/com/badlogic/gdx/backends/gwt/DefaultGwtInput.java @@ -1,12 +1,12 @@ /******************************************************************************* * Copyright 2011 See AUTHORS file. - * + * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -297,11 +297,15 @@ public void vibrate (int milliseconds) { } @Override - public void vibrate (long[] pattern, int repeat) { + public void vibrate (int milliseconds, boolean fallback) { } @Override - public void cancelVibrate () { + public void vibrate (int milliseconds, int amplitude, boolean fallback) { + } + + @Override + public void vibrate (VibrationType vibrationType) { } @Override diff --git a/extensions/gdx-setup/res/com/badlogic/gdx/setup/resources/android/build.gradle b/extensions/gdx-setup/res/com/badlogic/gdx/setup/resources/android/build.gradle index ab30e7c0810..f44795878d8 100644 --- a/extensions/gdx-setup/res/com/badlogic/gdx/setup/resources/android/build.gradle +++ b/extensions/gdx-setup/res/com/badlogic/gdx/setup/resources/android/build.gradle @@ -37,7 +37,6 @@ android { // so they get packed with the APK. task copyAndroidNatives { doFirst { - file("libs/armeabi/").mkdirs() file("libs/armeabi-v7a/").mkdirs() file("libs/arm64-v8a/").mkdirs() file("libs/x86_64/").mkdirs() @@ -47,7 +46,6 @@ task copyAndroidNatives { def outputDir = null if (jar.name.endsWith("natives-arm64-v8a.jar")) outputDir = file("libs/arm64-v8a") if (jar.name.endsWith("natives-armeabi-v7a.jar")) outputDir = file("libs/armeabi-v7a") - if(jar.name.endsWith("natives-armeabi.jar")) outputDir = file("libs/armeabi") if(jar.name.endsWith("natives-x86_64.jar")) outputDir = file("libs/x86_64") if(jar.name.endsWith("natives-x86.jar")) outputDir = file("libs/x86") if(outputDir != null) { @@ -62,7 +60,7 @@ task copyAndroidNatives { } tasks.whenTaskAdded { packageTask -> - if (packageTask.name.contains("package")) { + if (packageTask.name.contains("merge") && packageTask.name.contains("JniLibFolders")) { packageTask.dependsOn 'copyAndroidNatives' } } diff --git a/extensions/gdx-setup/res/com/badlogic/gdx/setup/resources/gitignore b/extensions/gdx-setup/res/com/badlogic/gdx/setup/resources/gitignore index a4b5d51c0d9..3d9d087db26 100644 --- a/extensions/gdx-setup/res/com/badlogic/gdx/setup/resources/gitignore +++ b/extensions/gdx-setup/res/com/badlogic/gdx/setup/resources/gitignore @@ -18,7 +18,6 @@ www-test/ .gwt-tmp/ ## Android Studio and Intellij and Android in general -/android/libs/armeabi/ /android/libs/armeabi-v7a/ /android/libs/arm64-v8a/ /android/libs/x86/ diff --git a/extensions/gdx-setup/res/com/badlogic/gdx/setup/resources/ios/robovm.xml b/extensions/gdx-setup/res/com/badlogic/gdx/setup/resources/ios/robovm.xml index 8a0f689bc68..a75cf776e33 100644 --- a/extensions/gdx-setup/res/com/badlogic/gdx/setup/resources/ios/robovm.xml +++ b/extensions/gdx-setup/res/com/badlogic/gdx/setup/resources/ios/robovm.xml @@ -37,6 +37,7 @@ UIKit + OpenGLES QuartzCore CoreGraphics OpenAL diff --git a/extensions/gdx-setup/res/com/badlogic/gdx/setup/resources/ios/src/IOSLauncher b/extensions/gdx-setup/res/com/badlogic/gdx/setup/resources/ios/src/IOSLauncher index 0fb4424f393..25bc06b97e3 100644 --- a/extensions/gdx-setup/res/com/badlogic/gdx/setup/resources/ios/src/IOSLauncher +++ b/extensions/gdx-setup/res/com/badlogic/gdx/setup/resources/ios/src/IOSLauncher @@ -19,4 +19,4 @@ public class IOSLauncher extends IOSApplication.Delegate { UIApplication.main(argv, null, IOSLauncher.class); pool.close(); } -} \ No newline at end of file +} diff --git a/extensions/gdx-setup/src/com/badlogic/gdx/setup/DependencyBank.java b/extensions/gdx-setup/src/com/badlogic/gdx/setup/DependencyBank.java index 0978746ddf3..4b80f4e6ecf 100644 --- a/extensions/gdx-setup/src/com/badlogic/gdx/setup/DependencyBank.java +++ b/extensions/gdx-setup/src/com/badlogic/gdx/setup/DependencyBank.java @@ -25,8 +25,8 @@ public class DependencyBank { // Temporary snapshot version, we need a more dynamic solution for pointing to the latest nightly static String libgdxNightlyVersion = "1.11.1-SNAPSHOT"; static String roboVMVersion = "2.3.16"; - static String buildToolsVersion = "29.0.3"; - static String androidAPILevel = "30"; + static String buildToolsVersion = "31.0.0"; + static String androidAPILevel = "31"; static String androidMinAPILevel = "14"; static String gwtVersion = "2.8.2"; @@ -42,7 +42,7 @@ public class DependencyBank { // Project plugins static String gwtPluginImport = "org.wisepersist:gwt-gradle-plugin:1.1.16"; static String grettyPluginImport = "org.gretty:gretty:3.0.7"; - static String androidPluginImport = "com.android.tools.build:gradle:4.1.3"; + static String androidPluginImport = "com.android.tools.build:gradle:7.0.4"; static String roboVMPluginImport = "com.mobidevelop.robovm:robovm-gradle-plugin:" + roboVMVersion; // Extension versions diff --git a/extensions/gdx-tools/build.gradle b/extensions/gdx-tools/build.gradle index 1af14b406d1..70b4e128bbd 100644 --- a/extensions/gdx-tools/build.gradle +++ b/extensions/gdx-tools/build.gradle @@ -33,6 +33,8 @@ ext { } task dist2DParticles (type: Jar) { + duplicatesStrategy(DuplicatesStrategy.EXCLUDE) + from files(sourceSets.main.java.outputDir) from files(sourceSets.main.output.resourcesDir) from {configurations.runtimeClasspath.collect {zipTree(it)}} @@ -46,6 +48,8 @@ task dist2DParticles (type: Jar) { } task dist3DParticles (type: Jar) { + duplicatesStrategy(DuplicatesStrategy.EXCLUDE) + from files(sourceSets.main.java.outputDir) from files(sourceSets.main.output.resourcesDir) from {configurations.runtimeClasspath.collect {zipTree(it)}} @@ -59,6 +63,8 @@ task dist3DParticles (type: Jar) { } task distHiero (type: Jar) { + duplicatesStrategy(DuplicatesStrategy.EXCLUDE) + from files(sourceSets.main.java.outputDir) from files(sourceSets.main.output.resourcesDir) from {configurations.runtimeClasspath.collect {zipTree(it)}} @@ -72,6 +78,8 @@ task distHiero (type: Jar) { } task distTexturePacker (type: Jar) { + duplicatesStrategy(DuplicatesStrategy.EXCLUDE) + from files(sourceSets.main.java.outputDir) from files(sourceSets.main.output.resourcesDir) from {configurations.runtimeClasspath.collect {zipTree(it)}} diff --git a/gdx/build.gradle b/gdx/build.gradle index e8895214856..bb7ba20e4e9 100644 --- a/gdx/build.gradle +++ b/gdx/build.gradle @@ -54,6 +54,7 @@ task copyIdeaResources(type: Copy) { from "${projectDir}/res" into "${buildDir}/classes/java/main/" } +processResources.duplicatesStrategy = DuplicatesStrategy.EXCLUDE processResources.dependsOn copyIdeaResources apply plugin: "com.badlogicgames.gdx.gdx-jnigen" diff --git a/gdx/src/com/badlogic/gdx/Input.java b/gdx/src/com/badlogic/gdx/Input.java index 864fd4654f8..5e37dc10874 100644 --- a/gdx/src/com/badlogic/gdx/Input.java +++ b/gdx/src/com/badlogic/gdx/Input.java @@ -642,7 +642,7 @@ private static void initializeKeyNames () { /** Enumeration of potentially available peripherals. Use with {@link Input#isPeripheralAvailable(Peripheral)}. * @author mzechner */ public enum Peripheral { - HardwareKeyboard, OnscreenKeyboard, MultitouchScreen, Accelerometer, Compass, Vibrator, Gyroscope, RotationVector, Pressure + HardwareKeyboard, OnscreenKeyboard, MultitouchScreen, Accelerometer, Compass, Vibrator, HapticFeedback, Gyroscope, RotationVector, Pressure } /** @return The acceleration force in m/s^2 applied to the device in the X axis, including the force of gravity */ @@ -790,21 +790,46 @@ public enum OnscreenKeyboardType { Default, NumberPad, PhonePad, Email, Password, URI } - /** Vibrates for the given amount of time. Note that you'll need the permission + /** Generates a simple haptic effect of a given duration or a vibration effect on devices without haptic capabilities. Note + * that on Android backend you'll need the permission * in your manifest file in order for this to work. + * On iOS backend you'll need to set useHaptics = true for devices with haptics capabilities to use them. * * @param milliseconds the number of milliseconds to vibrate. */ public void vibrate (int milliseconds); - /** Vibrate with a given pattern. Pass in an array of ints that are the times at which to turn on or off the vibrator. The - * first one is how long to wait before turning it on, and then after that it alternates. If you want to repeat, pass the index - * into the pattern at which to start the repeat. - * @param pattern an array of longs of times to turn the vibrator on or off. - * @param repeat the index into pattern at which to repeat, or -1 if you don't want to repeat. */ - public void vibrate (long[] pattern, int repeat); + /** Generates a simple haptic effect of a given duration and default amplitude. Note that on Android backend you'll need the + * permission in your manifest file in order for + * this to work. On iOS backend you'll need to set useHaptics = true for devices with haptics capabilities to use + * them. + * + * @param milliseconds the duration of the haptics effect + * @param fallback whether to use non-haptic vibrator on devices without haptics capabilities (or haptics disabled). Fallback + * non-haptic vibrations may ignore length parameter in some backends. */ + public void vibrate (int milliseconds, boolean fallback); + + /** Generates a simple haptic effect of a given duration and amplitude. Note that on Android backend you'll need the permission + * in your manifest file in order for this to work. + * On iOS backend you'll need to set useHaptics = true for devices with haptics capabilities to use them. + * + * @param milliseconds the duration of the haptics effect + * @param amplitude the amplitude/strength of the haptics effect. Valid values in the range [0, 255]. + * @param fallback whether to use non-haptic vibrator on devices without haptics capabilities (or haptics disabled). Fallback + * non-haptic vibrations may ignore length and/or amplitude parameters in some backends. */ + public void vibrate (int milliseconds, int amplitude, boolean fallback); + + /** Generates a simple haptic effect of a type. VibrationTypes are length/amplitude haptic effect presets that depend on each + * device and are defined by manufacturers. Should give most consistent results across devices and OSs. Note that on Android + * backend you'll need the permission in your + * manifest file in order for this to work. On iOS backend you'll need to set useHaptics = true for devices with + * haptics capabilities to use them. + * + * @param vibrationType the type of vibration */ + public void vibrate (VibrationType vibrationType); - /** Stops the vibrator */ - public void cancelVibrate (); + public enum VibrationType { + LIGHT, MEDIUM, HEAVY; + } /** The azimuth is the angle of the device's orientation around the z-axis. The positive z-axis points towards the earths * center. diff --git a/gdx/src/com/badlogic/gdx/assets/AssetManager.java b/gdx/src/com/badlogic/gdx/assets/AssetManager.java index 830c9c327f4..bc97b9f7a89 100644 --- a/gdx/src/com/badlogic/gdx/assets/AssetManager.java +++ b/gdx/src/com/badlogic/gdx/assets/AssetManager.java @@ -17,6 +17,7 @@ package com.badlogic.gdx.assets; import com.badlogic.gdx.Application; +import com.badlogic.gdx.Gdx; import com.badlogic.gdx.assets.loaders.AssetLoader; import com.badlogic.gdx.assets.loaders.BitmapFontLoader; import com.badlogic.gdx.assets.loaders.CubemapLoader; @@ -429,9 +430,11 @@ public synchronized boolean update () { /** Updates the AssetManager continuously for the specified number of milliseconds, yielding the CPU to the loading thread * between updates. This may block for less time if all loading tasks are complete. This may block for more time if the portion - * of a single task that happens in the GL thread takes a long time. + * of a single task that happens in the GL thread takes a long time. On GWT, updates for a single task instead (see + * {@link #update()}). * @return true if all loading is finished. */ public boolean update (int millis) { + if (Gdx.app.getType() == Application.ApplicationType.WebGL) return update(); long endTime = TimeUtils.millis() + millis; while (true) { boolean done = update(); diff --git a/gdx/src/com/badlogic/gdx/input/RemoteInput.java b/gdx/src/com/badlogic/gdx/input/RemoteInput.java index 22da2130ff0..17bf1141593 100644 --- a/gdx/src/com/badlogic/gdx/input/RemoteInput.java +++ b/gdx/src/com/badlogic/gdx/input/RemoteInput.java @@ -467,17 +467,18 @@ public void setOnscreenKeyboardVisible (boolean visible, OnscreenKeyboardType ty @Override public void vibrate (int milliseconds) { - } @Override - public void vibrate (long[] pattern, int repeat) { - + public void vibrate (int milliseconds, boolean fallback) { } @Override - public void cancelVibrate () { + public void vibrate (int milliseconds, int amplitude, boolean fallback) { + } + @Override + public void vibrate (VibrationType vibrationType) { } @Override diff --git a/gdx/src/com/badlogic/gdx/utils/QuadTreeFloat.java b/gdx/src/com/badlogic/gdx/utils/QuadTreeFloat.java index 7637ea7cd92..74dd977ebe2 100644 --- a/gdx/src/com/badlogic/gdx/utils/QuadTreeFloat.java +++ b/gdx/src/com/badlogic/gdx/utils/QuadTreeFloat.java @@ -1,12 +1,12 @@ /******************************************************************************* * Copyright 2011 See AUTHORS file. - * + * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -18,6 +18,7 @@ import java.util.Arrays; +import com.badlogic.gdx.math.Rectangle; import com.badlogic.gdx.utils.Pool.Poolable; /** A quad tree that stores a float for each point. @@ -156,6 +157,29 @@ private void query (float centerX, float centerY, float radiusSqr, float rectX, } } + /** @param results For each entry found within the rectangle, if any, the value, x, and y of the entry are added to this array. + * See {@link #VALUE}, {@link #X}, and {@link #Y}. */ + public void query (Rectangle rect, FloatArray results) { + if (x >= rect.x + rect.width || x + width <= rect.x || y >= rect.y + rect.height || y + height <= rect.y) return; + int count = this.count; + if (count != -1) { + float[] values = this.values; + for (int i = 1; i < count; i += 3) { + float px = values[i], py = values[i + 1]; + if (rect.contains(px, py)) { + results.add(values[i - 1]); + results.add(px); + results.add(py); + } + } + } else { + if (nw != null) nw.query(rect, results); + if (sw != null) sw.query(rect, results); + if (ne != null) ne.query(rect, results); + if (se != null) se.query(rect, results); + } + } + /** @param result For the entry nearest to the specified point, the value, x, y, and square of the distance to the value are * added to this array after it is cleared. See {@link #VALUE}, {@link #X}, {@link #Y}, and {@link #DISTSQR}. * @return false if no entry was found because the quad tree was empty or the specified point is farther than the larger of the diff --git a/gdx/src/com/badlogic/gdx/utils/SortedIntList.java b/gdx/src/com/badlogic/gdx/utils/SortedIntList.java index 8d9e8ca332c..5f1653654a1 100644 --- a/gdx/src/com/badlogic/gdx/utils/SortedIntList.java +++ b/gdx/src/com/badlogic/gdx/utils/SortedIntList.java @@ -115,7 +115,7 @@ public boolean isEmpty () { * Use the {@link Iterator} constructor for nested or multithreaded iteration. */ public java.util.Iterator> iterator () { if (Collections.allocateIterators) return new Iterator(); - if (iterator == null) iterator = new Iterator(); + if (iterator == null) return iterator = new Iterator(); return iterator.reset(); } @@ -123,6 +123,10 @@ public class Iterator implements java.util.Iterator> { private Node position; private Node previousPosition; + public Iterator () { + reset(); + } + @Override public boolean hasNext () { return position != null; diff --git a/gdx/test/com/badlogic/gdx/utils/SortedIntListTest.java b/gdx/test/com/badlogic/gdx/utils/SortedIntListTest.java new file mode 100644 index 00000000000..baa4b74c03a --- /dev/null +++ b/gdx/test/com/badlogic/gdx/utils/SortedIntListTest.java @@ -0,0 +1,22 @@ + +package com.badlogic.gdx.utils; + +import org.junit.Assert; +import org.junit.Test; + +public class SortedIntListTest { + + @Test + public void testIteratorWithAllocation () { + Collections.allocateIterators = true; + try { + SortedIntList list = new SortedIntList(); + list.insert(0, "hello"); + Assert.assertEquals(1, list.size); + Assert.assertEquals("hello", list.get(0)); + Assert.assertEquals("hello", list.iterator().next().value); + } finally { + Collections.allocateIterators = false; + } + } +} diff --git a/gradle/dependencies.gradle b/gradle/dependencies.gradle index 32df551d6f6..d06e4f9400a 100644 --- a/gradle/dependencies.gradle +++ b/gradle/dependencies.gradle @@ -33,15 +33,15 @@ versions.lwjgl3 = "3.3.1" versions.jlayer = "1.0.1-gdx" versions.jorbis = "0.0.17" versions.junit = "4.13.2" -versions.androidPlugin = "4.1.3" +versions.androidPlugin = "7.0.4" versions.multiDex = "2.0.1" -versions.androidCompileSdk = 30 -versions.androidTargetSdk = 30 +versions.androidCompileSdk = 31 +versions.androidTargetSdk = 31 versions.androidMinSdk = 14 -versions.androidBuildTools = "29.0.3" +versions.androidBuildTools = "31.0.0" versions.androidFragment = "1.2.3" versions.javaparser = "2.3.0" -versions.spotless = "5.14.3" +versions.spotless = "6.7.1" libraries.compileOnly = [:] diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 7ff3e5c3ac3..6b7fd26c83d 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,5 @@ -#Fri Sep 30 05:10:25 BST 2016 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-6.9.1-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-7.3.3-bin.zip diff --git a/tests/gdx-tests-android/AndroidManifest.xml b/tests/gdx-tests-android/AndroidManifest.xml index 8709e99400a..7431eefb8a1 100644 --- a/tests/gdx-tests-android/AndroidManifest.xml +++ b/tests/gdx-tests-android/AndroidManifest.xml @@ -15,7 +15,8 @@ android:allowBackup="false" tools:ignore="GoogleAppIndexingWarning"> + android:configChanges="keyboard|keyboardHidden|navigation|orientation|screenSize|screenLayout" + android:exported="true"> @@ -25,7 +26,8 @@ + android:screenOrientation="landscape" + android:exported="true"> @@ -38,14 +40,16 @@ android:screenOrientation="landscape"/> + android:label="Livewallpaper Settings" + android:exported="true"> + android:label="Daydream Settings" + android:exported="true"> @@ -54,7 +58,8 @@ + android:permission="android.permission.BIND_WALLPAPER" + android:exported="true"> diff --git a/tests/gdx-tests-android/build.gradle b/tests/gdx-tests-android/build.gradle index 3a4a7fee4ad..a4839e5c5b5 100644 --- a/tests/gdx-tests-android/build.gradle +++ b/tests/gdx-tests-android/build.gradle @@ -106,7 +106,7 @@ task copyAndroidNatives() { } tasks.whenTaskAdded { packageTask -> - if (packageTask.name.contains("package")) { + if (packageTask.name.contains("merge") && packageTask.name.contains("JniLibFolders")) { packageTask.dependsOn 'copyAndroidNatives' } } diff --git a/tests/gdx-tests-gwt/src/com/badlogic/gdx/tests/gwt/client/GwtTestWrapper.java b/tests/gdx-tests-gwt/src/com/badlogic/gdx/tests/gwt/client/GwtTestWrapper.java index a98ddd212fc..93fbcdaa6c7 100644 --- a/tests/gdx-tests-gwt/src/com/badlogic/gdx/tests/gwt/client/GwtTestWrapper.java +++ b/tests/gdx-tests-gwt/src/com/badlogic/gdx/tests/gwt/client/GwtTestWrapper.java @@ -95,6 +95,7 @@ import com.badlogic.gdx.tests.gles2.VertexArrayTest; import com.badlogic.gdx.tests.gwt.GwtInputTest; import com.badlogic.gdx.tests.gwt.GwtWindowModeTest; +import com.badlogic.gdx.tests.math.CollisionPlaygroundTest; import com.badlogic.gdx.tests.math.OctreeTest; import com.badlogic.gdx.tests.net.OpenBrowserExample; import com.badlogic.gdx.tests.superkoalio.SuperKoalio; @@ -187,6 +188,10 @@ public GdxTest instance () { public GdxTest instance () { return new ComplexActionTest(); } + }, new GwtInstancer() { + public GdxTest instance () { + return new CollisionPlaygroundTest(); + } }, new GwtInstancer() { public GdxTest instance () { return new CustomShaderSpriteBatchTest(); diff --git a/tests/gdx-tests-iosrobovm/src/com/badlogic/gdx/tests/IOSRobovmTests.java b/tests/gdx-tests-iosrobovm/src/com/badlogic/gdx/tests/IOSRobovmTests.java index bf1a9599c09..0c532dbec23 100644 --- a/tests/gdx-tests-iosrobovm/src/com/badlogic/gdx/tests/IOSRobovmTests.java +++ b/tests/gdx-tests-iosrobovm/src/com/badlogic/gdx/tests/IOSRobovmTests.java @@ -27,6 +27,7 @@ public class IOSRobovmTests extends IOSApplication.Delegate { @Override protected IOSApplication createApplication () { IOSApplicationConfiguration config = new IOSApplicationConfiguration(); + config.useHaptics = true; return new IOSApplication(new IosTestWrapper(), config); } diff --git a/tests/gdx-tests/src/com/badlogic/gdx/tests/AbstractTestWrapper.java b/tests/gdx-tests/src/com/badlogic/gdx/tests/AbstractTestWrapper.java index aa4360c723a..a09b87d7b20 100644 --- a/tests/gdx-tests/src/com/badlogic/gdx/tests/AbstractTestWrapper.java +++ b/tests/gdx-tests/src/com/badlogic/gdx/tests/AbstractTestWrapper.java @@ -1,12 +1,12 @@ /******************************************************************************* * Copyright 2011 See AUTHORS file. - * + * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -305,13 +305,18 @@ public void vibrate (int milliseconds) { } @Override - public void vibrate (long[] pattern, int repeat) { - input.vibrate(pattern, repeat); + public void vibrate (int milliseconds, boolean fallback) { + input.vibrate(milliseconds, fallback); } @Override - public void cancelVibrate () { - input.cancelVibrate(); + public void vibrate (int milliseconds, int amplitude, boolean fallback) { + input.vibrate(milliseconds, amplitude, fallback); + } + + @Override + public void vibrate (VibrationType vibrationType) { + input.vibrate(vibrationType); } @Override diff --git a/tests/gdx-tests/src/com/badlogic/gdx/tests/VibratorTest.java b/tests/gdx-tests/src/com/badlogic/gdx/tests/VibratorTest.java index 9f2c6981c2b..10a0904223e 100644 --- a/tests/gdx-tests/src/com/badlogic/gdx/tests/VibratorTest.java +++ b/tests/gdx-tests/src/com/badlogic/gdx/tests/VibratorTest.java @@ -17,29 +17,98 @@ package com.badlogic.gdx.tests; import com.badlogic.gdx.Gdx; +import com.badlogic.gdx.Input; import com.badlogic.gdx.graphics.GL20; -import com.badlogic.gdx.graphics.g2d.BitmapFont; import com.badlogic.gdx.graphics.g2d.SpriteBatch; +import com.badlogic.gdx.math.MathUtils; +import com.badlogic.gdx.scenes.scene2d.Actor; +import com.badlogic.gdx.scenes.scene2d.Stage; +import com.badlogic.gdx.scenes.scene2d.ui.Button; +import com.badlogic.gdx.scenes.scene2d.ui.CheckBox; +import com.badlogic.gdx.scenes.scene2d.ui.Label; +import com.badlogic.gdx.scenes.scene2d.ui.Skin; +import com.badlogic.gdx.scenes.scene2d.ui.Table; +import com.badlogic.gdx.scenes.scene2d.utils.ChangeListener; import com.badlogic.gdx.tests.utils.GdxTest; public class VibratorTest extends GdxTest { + Stage stage; SpriteBatch batch; - BitmapFont font; + Skin skin; @Override public void create () { batch = new SpriteBatch(); - font = new BitmapFont(); + stage = new Stage(); + skin = new Skin(Gdx.files.internal("data/uiskin.json")); + Gdx.input.setInputProcessor(stage); + + // Create a table that fills the screen. Everything else will go inside this table. + Table table = new Table(); + table.setFillParent(true); + stage.addActor(table); + + final CheckBox fallbackCheckbox = new CheckBox("Fallback", skin); + final Button button = getButton("Vibrate"); + button.addListener(new ChangeListener() { + @Override + public void changed (ChangeEvent event, Actor actor) { + Gdx.input.vibrate(50); + } + }); + final Button buttonVibrateAmplitude = getButton("Vibrate \n Amplitude \n Random"); + buttonVibrateAmplitude.addListener(new ChangeListener() { + @Override + public void changed (ChangeEvent event, Actor actor) { + int randomLength = MathUtils.random(10, 200); + int randomAmplitude = MathUtils.random(0, 255); + Gdx.input.vibrate(randomLength, randomAmplitude, fallbackCheckbox.isChecked()); + Gdx.app.log("VibratorTest", "Length: " + randomLength + "ms, Amplitude: " + randomAmplitude); + } + }); + final Button buttonVibrateType = getButton("Vibrate \n Type \n Random"); + buttonVibrateType.addListener(new ChangeListener() { + @Override + public void changed (ChangeEvent event, Actor actor) { + Input.VibrationType vibrationType = Input.VibrationType.values()[MathUtils.random(0, + Input.VibrationType.values().length - 1)]; + Gdx.input.vibrate(vibrationType); + Gdx.app.log("VibratorTest", "VibrationType: " + vibrationType.name()); + } + }); + + table.defaults().pad(20f); + table.add(button).size(120f); + table.add(buttonVibrateAmplitude).size(120f); + table.add(buttonVibrateType).size(120f); + table.row(); + table.add(fallbackCheckbox).colspan(3).height(120f); + + } + + private Button getButton (String text) { + final Button button = new Button(skin); + Label label = new Label(text, skin); + button.add(label); + return button; } @Override public void render () { Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT); - batch.begin(); - font.draw(batch, "Touch screen to vibrate", 100, 100); - batch.end(); + stage.act(Math.min(Gdx.graphics.getDeltaTime(), 1 / 30f)); + stage.draw(); + } - if (Gdx.input.justTouched()) Gdx.input.vibrate(100); + @Override + public void resize (int width, int height) { + stage.getViewport().update(width, height, true); + } + + @Override + public void dispose () { + stage.dispose(); + skin.dispose(); } } diff --git a/tests/gdx-tests/src/com/badlogic/gdx/tests/math/CollisionPlaygroundTest.java b/tests/gdx-tests/src/com/badlogic/gdx/tests/math/CollisionPlaygroundTest.java new file mode 100644 index 00000000000..9ec9d101604 --- /dev/null +++ b/tests/gdx-tests/src/com/badlogic/gdx/tests/math/CollisionPlaygroundTest.java @@ -0,0 +1,299 @@ +/******************************************************************************* + * Copyright 2020 See AUTHORS file. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ******************************************************************************/ + +package com.badlogic.gdx.tests.math; + +import com.badlogic.gdx.ApplicationListener; +import com.badlogic.gdx.Gdx; +import com.badlogic.gdx.Input; +import com.badlogic.gdx.InputMultiplexer; +import com.badlogic.gdx.graphics.Color; +import com.badlogic.gdx.graphics.GL20; +import com.badlogic.gdx.graphics.PerspectiveCamera; +import com.badlogic.gdx.graphics.VertexAttributes; +import com.badlogic.gdx.graphics.g2d.BitmapFont; +import com.badlogic.gdx.graphics.g2d.SpriteBatch; +import com.badlogic.gdx.graphics.g3d.Material; +import com.badlogic.gdx.graphics.g3d.ModelBatch; +import com.badlogic.gdx.graphics.g3d.ModelInstance; +import com.badlogic.gdx.graphics.g3d.attributes.ColorAttribute; +import com.badlogic.gdx.graphics.g3d.utils.CameraInputController; +import com.badlogic.gdx.graphics.g3d.utils.MeshPartBuilder; +import com.badlogic.gdx.graphics.g3d.utils.shapebuilders.BoxShapeBuilder; +import com.badlogic.gdx.graphics.g3d.utils.shapebuilders.FrustumShapeBuilder; +import com.badlogic.gdx.graphics.g3d.utils.shapebuilders.SphereShapeBuilder; +import com.badlogic.gdx.math.Frustum; +import com.badlogic.gdx.math.Intersector; +import com.badlogic.gdx.math.MathUtils; +import com.badlogic.gdx.math.Matrix4; +import com.badlogic.gdx.math.Vector3; +import com.badlogic.gdx.math.collision.BoundingBox; +import com.badlogic.gdx.math.collision.Ray; +import com.badlogic.gdx.tests.utils.GdxTest; + +import java.util.ArrayList; +import java.util.List; + +public class CollisionPlaygroundTest extends GdxTest implements ApplicationListener { + + private static final int NUM_SHAPES = 30; + private static final int RANGE = 4; + + private int PRIMITIVE_TYPE = GL20.GL_LINES; + + private static final Color COLOR_STANDARD = Color.BLUE; + private static final Color COLOR_MOUSE_OVER = Color.GREEN; + private static final Color COLOR_INTERSECTION = Color.GOLD; + + private PerspectiveCamera camera; + private CameraInputController cameraController; + private PerspectiveCamera collisionCamera; + + private ModelBatch modelBatch; + + private SpriteBatch batch; + private BitmapFont font; + + private ModelInstance frustum; + private List shapes = new ArrayList<>(); + + private long seed; + + @Override + public void create () { + font = new BitmapFont(Gdx.files.internal("data/lsans-15.fnt"), false); + batch = new SpriteBatch(); + modelBatch = new ModelBatch(); + + setupCamera(); + setupScene(); + InputMultiplexer inputMultiplexer = new InputMultiplexer(); + inputMultiplexer.addProcessor(cameraController); + inputMultiplexer.addProcessor(this); + Gdx.input.setInputProcessor(inputMultiplexer); + } + + private void setupScene () { + seed = MathUtils.random.nextLong(); + MathUtils.random.setSeed(seed); + + if (frustum != null) { + frustum.model.dispose(); + } + + for (Shape shape : shapes) { + shape.dispose(); + } + shapes.clear(); + + for (int i = 0; i < NUM_SHAPES; i++) { + createRandomShape(); + } + + frustum = createFrustum(collisionCamera); + } + + private void setupCamera () { + camera = new PerspectiveCamera(60f, Gdx.graphics.getWidth(), Gdx.graphics.getHeight()); + camera.near = 0.01f; + camera.far = 100f; + camera.position.set(0, 5, 0); + camera.lookAt(Vector3.Zero); + camera.update(); + cameraController = new CameraInputController(camera); + + collisionCamera = new PerspectiveCamera(60f, Gdx.graphics.getWidth(), Gdx.graphics.getHeight()); + collisionCamera.near = 0.01f; + collisionCamera.far = 3f; + collisionCamera.position.set(1, 0, 0); + collisionCamera.lookAt(0, 0, -1); + collisionCamera.update(true); + } + + @Override + public void render () { + Gdx.gl.glClearColor(0, 0, 0, 0); + Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT | GL20.GL_DEPTH_BUFFER_BIT); + + // Draw FPS + batch.getProjectionMatrix().setToOrtho2D(0, 0, Gdx.graphics.getWidth(), Gdx.graphics.getHeight()); + batch.begin(); + font.draw(batch, "fps: " + Gdx.graphics.getFramesPerSecond(), 0, 30); + font.draw(batch, "seed: " + seed, 0, 50); + batch.end(); + + checkCollision(); + + // Draw Box + modelBatch.begin(camera); + modelBatch.render(frustum); + for (Shape shape : shapes) { + modelBatch.render(shape.instance); + } + modelBatch.end(); + } + + private void checkCollision () { + Ray ray = camera.getPickRay(Gdx.input.getX(), Gdx.input.getY()); + + for (Shape shape : shapes) { + if (shape.isColliding(ray)) { + shape.updateColor(COLOR_MOUSE_OVER); + } else if (shape.isColliding(collisionCamera.frustum)) { + shape.updateColor(COLOR_INTERSECTION); + } else { + shape.updateColor(COLOR_STANDARD); + } + } + } + + @Override + public void dispose () { + batch.dispose(); + font.dispose(); + modelBatch.dispose(); + frustum.model.dispose(); + for (Shape shape : shapes) { + shape.dispose(); + } + } + + @Override + public boolean keyUp (int keycode) { + if (Input.Keys.SPACE == keycode) { + setupScene(); + } + return super.keyUp(keycode); + } + + private void createRandomShape () { + int shape = MathUtils.random.nextInt(2); + + switch (shape) { + case 1: + shapes.add(new Sphere()); + break; + default: + shapes.add(new AABB()); + } + + } + + private ModelInstance createFrustum (PerspectiveCamera camera) { + Material material = new Material(ColorAttribute.createDiffuse(1, 0, 0, 0)); + com.badlogic.gdx.graphics.g3d.utils.ModelBuilder mb = new com.badlogic.gdx.graphics.g3d.utils.ModelBuilder(); + mb.begin(); + + MeshPartBuilder meshPartBuilder = mb.part("frustum", PRIMITIVE_TYPE, VertexAttributes.Usage.Position, material); + + FrustumShapeBuilder.build(meshPartBuilder, camera); + + return new ModelInstance(mb.end()); + } + + abstract class Shape { + ModelInstance instance; + + abstract boolean isColliding (Frustum frustum); + + abstract boolean isColliding (Ray ray); + + void updateColor (Color color) { + Material material = instance.materials.get(0); + ColorAttribute attribute = (ColorAttribute)material.get(ColorAttribute.Diffuse); + attribute.color.set(color); + } + + void dispose () { + instance.model.dispose(); + } + + Vector3 randomPosition () { + return new Vector3(MathUtils.random(-RANGE, RANGE), MathUtils.random(-RANGE, RANGE), MathUtils.random(-RANGE, RANGE)); + } + } + + class AABB extends Shape { + private final BoundingBox aabb; + + @Override + public boolean isColliding (Frustum frustum) { + return Intersector.intersectFrustumBounds(frustum, aabb); + } + + @Override + public boolean isColliding (Ray ray) { + return Intersector.intersectRayBoundsFast(ray, aabb); + } + + AABB () { + Vector3 position = randomPosition(); + + float width = MathUtils.random(0.01f, 1f); + float height = MathUtils.random(0.01f, 1f); + float depth = MathUtils.random(0.01f, 1f); + + Vector3 min = new Vector3(position.x - width / 2, position.y - height / 2, position.z - depth / 2); + Vector3 max = new Vector3(position.x + width / 2, position.y + height / 2, position.z + depth / 2); + aabb = new BoundingBox(min, max); + + Matrix4 transform = new Matrix4().setToTranslation(position); + + Material material = new Material(ColorAttribute.createDiffuse(COLOR_STANDARD)); + com.badlogic.gdx.graphics.g3d.utils.ModelBuilder mb = new com.badlogic.gdx.graphics.g3d.utils.ModelBuilder(); + mb.begin(); + MeshPartBuilder meshPartBuilder = mb.part("aabb", PRIMITIVE_TYPE, VertexAttributes.Usage.Position, material); + meshPartBuilder.setVertexTransform(transform); + BoxShapeBuilder.build(meshPartBuilder, width, height, depth); + + instance = new ModelInstance(mb.end()); + } + } + + class Sphere extends Shape { + private final com.badlogic.gdx.math.collision.Sphere sphere; + + @Override + public boolean isColliding (Frustum frustum) { + return frustum.sphereInFrustum(sphere.center, sphere.radius); + } + + @Override + public boolean isColliding (Ray ray) { + return Intersector.intersectRaySphere(ray, sphere.center, sphere.radius, null); + } + + Sphere () { + Vector3 position = randomPosition(); + + float diameter = MathUtils.random(0.01f, 1f); + + sphere = new com.badlogic.gdx.math.collision.Sphere(position, diameter / 2); + + Matrix4 transform = new Matrix4().setToTranslation(position); + + Material material = new Material(ColorAttribute.createDiffuse(COLOR_STANDARD)); + com.badlogic.gdx.graphics.g3d.utils.ModelBuilder mb = new com.badlogic.gdx.graphics.g3d.utils.ModelBuilder(); + mb.begin(); + MeshPartBuilder meshPartBuilder = mb.part("sphere", PRIMITIVE_TYPE, VertexAttributes.Usage.Position, material); + meshPartBuilder.setVertexTransform(transform); + SphereShapeBuilder.build(meshPartBuilder, diameter, diameter, diameter, 16, 16); + + instance = new ModelInstance(mb.end()); + } + } + +} diff --git a/tests/gdx-tests/src/com/badlogic/gdx/tests/utils/GdxTests.java b/tests/gdx-tests/src/com/badlogic/gdx/tests/utils/GdxTests.java index dcf8d832d09..10dfe1113d7 100644 --- a/tests/gdx-tests/src/com/badlogic/gdx/tests/utils/GdxTests.java +++ b/tests/gdx-tests/src/com/badlogic/gdx/tests/utils/GdxTests.java @@ -49,6 +49,7 @@ import com.badlogic.gdx.tests.extensions.FreeTypePackTest; import com.badlogic.gdx.tests.extensions.FreeTypeTest; import com.badlogic.gdx.tests.extensions.InternationalFontsTest; +import com.badlogic.gdx.tests.math.CollisionPlaygroundTest; import com.badlogic.gdx.tests.math.OctreeTest; import com.badlogic.gdx.tests.g3d.Animation3DTest; import com.badlogic.gdx.tests.g3d.AnisotropyTest; @@ -125,6 +126,7 @@ public class GdxTests { BulletTestCollection.class, ClipboardTest.class, CollectionsTest.class, + CollisionPlaygroundTest.class, ColorTest.class, ContainerTest.class, CoordinatesTest.class,